OLD | NEW |
| (Empty) |
1 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """Module for performance testing using the psutil library. | |
6 | |
7 Ref: http://code.google.com/p/psutil/wiki/Documentation | |
8 | |
9 Most part of this module is from chrome/test/perf/startup_test.cc and | |
10 chrome/test/ui/ui_perf_test.[h,cc] So, we try to preserve the original C++ code | |
11 here in case when there is change in original C++ code, it is easy to update | |
12 this. | |
13 """ | |
14 | |
15 # Standard library imports. | |
16 import logging | |
17 import re | |
18 import time | |
19 | |
20 # Third-party imports. | |
21 import psutil | |
22 | |
23 | |
24 class UIPerfTestUtils: | |
25 """Static utility functions for performance testing.""" | |
26 | |
27 @staticmethod | |
28 def ConvertDataListToString(data_list): | |
29 """Convert data array to string that can be used for results on BuildBot. | |
30 | |
31 Full accuracy of the results coming from the psutil library is not needed | |
32 for Perf on BuildBot. For now, we show 5 digits here. This function goes | |
33 through the elements in the data_list and does the conversion as well as | |
34 adding a prefix and suffix. | |
35 | |
36 Args: | |
37 data_list: data list contains measured data from perf test. | |
38 | |
39 Returns: | |
40 a string that can be used for perf result shown on Buildbot. | |
41 """ | |
42 output = '[' | |
43 for data in data_list: | |
44 output += ('%.5f' % data) + ', ' | |
45 # Remove the last ', '. | |
46 if output.endswith(', '): | |
47 output = output[:-2] | |
48 output += ']' | |
49 return output | |
50 | |
51 @staticmethod | |
52 def GetResultStringForPerfBot(measurement, modifier, trace, values, units): | |
53 """Get a result string in a format that can be displayed on the PerfBot. | |
54 | |
55 The following are acceptable (it can be shown in PerfBot) format: | |
56 <*>RESULT <graph_name>: <trace_name>= <value> <units> | |
57 <*>RESULT <graph_name>: <trace_name>= {<mean>, <std deviation>} <units> | |
58 <*>RESULT <graph_name>: <trace_name>= [<value>,value,value,...,] <units> | |
59 | |
60 Args: | |
61 measurement: measurement string (such as a parameter list). | |
62 modifier: modifier string (such as a file name). | |
63 trace: trace string used for PerfBot graph name (such as 't' or 't_ref'). | |
64 values: list of values that displayed as "[value1,value2....]". | |
65 units: units of values such as "sec" or "msec". | |
66 | |
67 Returns: | |
68 An output string that contains all information, or the empty string if | |
69 there is no information available. | |
70 """ | |
71 if not values: | |
72 return '' | |
73 output_string = '%sRESULT %s%s: %s= %s %s' % ( | |
74 '', measurement, modifier, trace, | |
75 UIPerfTestUtils.ConvertDataListToString(values), units) | |
76 return output_string | |
77 | |
78 @staticmethod | |
79 def FindProcesses(process_name): | |
80 """Find processes for a given process name. | |
81 | |
82 Args: | |
83 process_name: a process name string to find. | |
84 | |
85 Returns: | |
86 a list of psutil process instances that are associated with the given | |
87 process name. | |
88 """ | |
89 target_process_list = [] | |
90 for pid in psutil.get_pid_list(): | |
91 try: | |
92 p = psutil.Process(pid) | |
93 # Exact match does not work | |
94 if process_name in p.name: | |
95 target_process_list.append(p) | |
96 except psutil.NoSuchProcess: | |
97 # Do nothing since the process is already terminated | |
98 pass | |
99 return target_process_list | |
100 | |
101 @staticmethod | |
102 def GetResourceInfo(process, start_time): | |
103 """Get resource information coming from psutil. | |
104 | |
105 This calls corresponding functions in psutil and parses the results. | |
106 | |
107 TODO(imasaki@chromium.org): Modify this function so that it's not | |
108 hard-coded to return 7 pieces of information. Instead, you have the | |
109 caller somehow indicate the number and types of information it needs. | |
110 Then the function finds and returns the requested info. | |
111 | |
112 Args: | |
113 start_time: the time when the program starts (used for recording | |
114 measured_time). | |
115 process: psutil's Process instance. | |
116 | |
117 Returns: | |
118 a process info tuple: measured_time, cpu_time in percent, | |
119 user cpu time, system cpu time, resident memory size, | |
120 virtual memory size, and memory usage. None is returned if the | |
121 resource info cannot be identified. | |
122 """ | |
123 try: | |
124 measured_time = time.time() | |
125 cpu_percent = process.get_cpu_percent(interval=1.0) | |
126 memory_percent = process.get_memory_percent() | |
127 m1 = re.search(r'cputimes\(user=(\S+),\s+system=(\S+)\)', | |
128 str(process.get_cpu_times())) | |
129 m2 = re.search(r'meminfo\(rss=(\S+),\s+vms=(\S+)\)', | |
130 str(process.get_memory_info())) | |
131 | |
132 cputimes_user = float(m1.group(1)) | |
133 cputimes_system = float(m1.group(2)) | |
134 | |
135 # Convert Bytes to MBytes. | |
136 memory_rss = float(m2.group(1)) / 1000000 | |
137 memory_vms = float(m2.group(2)) / 1000000 | |
138 | |
139 return (measured_time - start_time, cpu_percent, cputimes_user, | |
140 cputimes_system, memory_rss, memory_vms, memory_percent) | |
141 | |
142 except psutil.NoSuchProcess: | |
143 # Do nothing since the process is already terminated. | |
144 # This may happen due to race condition. | |
145 return None | |
146 | |
147 @staticmethod | |
148 def IsChromeRendererProcess(process): | |
149 """Check whether the given process is a Chrome Renderer process. | |
150 | |
151 Args: | |
152 process: a psutil's Process instance. | |
153 | |
154 Returns: | |
155 True if process is a Chrome renderer process. False otherwise. | |
156 """ | |
157 for line in process.cmdline: | |
158 if 'type=renderer' in line: | |
159 return True | |
160 return False | |
161 | |
162 @staticmethod | |
163 def GetChromeRendererProcessInfo(start_time): | |
164 """Get Chrome renderer process information by psutil. | |
165 | |
166 Returns: | |
167 a renderer process info tuple: measured_time, cpu_time in | |
168 percent, user cpu time, system cpu time, resident memory size, virtual | |
169 memory size, and memory usage. Or returns an empty list if the Chrome | |
170 renderer process is not found or more than one renderer process exists. | |
171 In this case, an error message is written to the log. | |
172 """ | |
173 chrome_process_list = UIPerfTestUtils.FindProcesses('chrome') | |
174 chrome_process_info_list = [] | |
175 for p in chrome_process_list: | |
176 if UIPerfTestUtils.IsChromeRendererProcess(p): | |
177 # Return the first renderer process's resource info. | |
178 resource_info = UIPerfTestUtils.GetResourceInfo(p, start_time) | |
179 if resource_info is not None: | |
180 chrome_process_info_list.append(resource_info) | |
181 if not chrome_process_info_list: | |
182 logging.error('Chrome renderer process does not exist') | |
183 return [] | |
184 if len(chrome_process_info_list) > 1: | |
185 logging.error('More than one Chrome renderer processes exists') | |
186 return [] | |
187 return chrome_process_info_list[0] | |
188 | |
189 @staticmethod | |
190 def _GetMaxDataLength(chrome_renderer_process_infos): | |
191 """Get max data length of process render info. | |
192 | |
193 This method is necessary since reach run may have different data length. | |
194 So, you have to get maximum to prevent data from missing. | |
195 | |
196 Args: | |
197 measured_data_list : measured_data_list that | |
198 contain a list of measured data (CPU and memory) at certain intervals | |
199 over several runs. Each run contains several time data. | |
200 info -> 0th run -> 0th time -> time stamp, CPU data and memory data | |
201 -> 1th time -> time stamp, CPU data and memory data | |
202 ..... | |
203 -> 1th run -> 0th time -> time stamp, CPU data and memory data | |
204 each run may have different number of measurement. | |
205 | |
206 Returns: | |
207 max data length among all runs. | |
208 """ | |
209 maximum = len(chrome_renderer_process_infos[0]) | |
210 for info in chrome_renderer_process_infos: | |
211 if maximum < len(info): | |
212 maximum = len(info) | |
213 return maximum | |
214 | |
215 @staticmethod | |
216 def PrintMeasuredData(measured_data_list, measured_data_name_list, | |
217 measured_data_unit_list, parameter_string, trace_list, | |
218 remove_first_result=True, show_time_index=False, | |
219 reference_build=False, display_filter=None): | |
220 """Calculate statistics over all results and print them in the format that | |
221 can be shown on BuildBot. | |
222 | |
223 Args: | |
224 measured_data_list: measured_data_list that contains a list of measured | |
225 data at certain intervals over several runs. Each run should contain | |
226 the timestamp of the measured time as well. | |
227 info -> 0th run -> 0th time -> list of measured data | |
228 (defined in measured_data_name_list) | |
229 -> 1st time -> list of measured data | |
230 ..... | |
231 -> 1st run -> 0th time -> list of measured data | |
232 each run may have different number of measurement. | |
233 measured_data_name_list: a list of the names for an element of | |
234 measured_data_list (such as 'measured-time','cpu'). The size of this | |
235 list should be same as the size of measured_data_unit_list. | |
236 measured_data_unit_list: a list of the names of the units for an element | |
237 of measured_data_list. The size of this list should be same as the size | |
238 of measured_data_name_list. | |
239 parameter_string: a string that contains all parameters used. | |
240 (currently not used). | |
241 trace_list: a list of trace names used for legends in perf graph | |
242 (for example, ['t','c']). Generally, a trace name is one letter. | |
243 remove_first_result: a boolean for removing the first result | |
244 (the first result contains browser startup time). | |
245 show_time_index: a boolean for showing time index (such as '0' or '1' in | |
246 'procutil-0' or 'procutil-1') if it is true. Time index is necessary | |
247 when the same kind of data is measured over a period of time. | |
248 For example, 'procutil-0' is the first result and 'procutil-1' is the | |
249 second result. If this is false, the results are aggregated into one | |
250 result (such as 'procutil', which includes the results from | |
251 'procutil-0' and 'procutil-1'). | |
252 reference_build: a boolean for indicating this result is computed with | |
253 reference build binaries. '_ref' is added in trace in the case of | |
254 reference build. | |
255 display_filter: a list of names that you want to display in the results. | |
256 The names should be in |measured_data_name_list|. | |
257 Returns: | |
258 An output string that contains all information, or the empty string | |
259 if there is no information to output. | |
260 """ | |
261 output_string = '' | |
262 for measured_data_index in range(len(measured_data_name_list)): | |
263 if not display_filter or (display_filter and ( | |
264 measured_data_name_list[measured_data_index] in display_filter)): | |
265 max_data_length = UIPerfTestUtils._GetMaxDataLength( | |
266 measured_data_list) | |
267 trace_name = trace_list[measured_data_index] | |
268 if reference_build: | |
269 trace_name += '_ref' | |
270 if show_time_index: | |
271 for time_index in range(max_data_length): | |
272 psutil_data = [] | |
273 UIPerfTestUtils._AppendPsUtilData(psutil_data, measured_data_list, | |
274 remove_first_result, time_index, | |
275 measured_data_index) | |
276 name = '%s-%s' % (measured_data_name_list[measured_data_index], | |
277 str(time_index)) | |
278 output_string += UIPerfTestUtils._GenerateOutputString( | |
279 name, output_string, trace_name, psutil_data, | |
280 measured_data_unit_list[measured_data_index]) | |
281 else: | |
282 psutil_data = [] | |
283 for time_index in range(max_data_length): | |
284 UIPerfTestUtils._AppendPsUtilData(psutil_data, measured_data_list, | |
285 remove_first_result, time_index, | |
286 measured_data_index) | |
287 name = measured_data_name_list[measured_data_index] | |
288 output_string += UIPerfTestUtils._GenerateOutputString( | |
289 name, output_string, trace_name, psutil_data, | |
290 measured_data_unit_list[measured_data_index]) | |
291 return output_string | |
292 | |
293 @staticmethod | |
294 def _AppendPsUtilData(psutil_data, measured_data_list, | |
295 remove_first_result, time_index, measured_data_index): | |
296 """Append data measured by psutil to a list. | |
297 | |
298 Args: | |
299 psutil_data: a list of data measured by psutil. | |
300 measured_data_list: current data measured by psutil, which will be | |
301 appended to |psutil_data|. | |
302 remove_first_result: a boolean indicating whether or not to remove | |
303 the first result. | |
304 time_index: a integer that shows time-wise index for measured data | |
305 (For example, '0' or '1' in 'procutil-0' or 'procutil-1'). | |
306 measured_data_index: the loop index for |measured_data_list|. | |
307 """ | |
308 for counter in range(len(measured_data_list)): | |
309 if not remove_first_result or counter > 0: | |
310 data_length_for_each = (len(measured_data_list[counter])) | |
311 if (data_length_for_each > time_index): | |
312 data = measured_data_list[counter][time_index][measured_data_index] | |
313 psutil_data.append(data) | |
314 | |
315 @staticmethod | |
316 def _GenerateOutputString(name, output_string, trace_name, psutil_data, | |
317 measured_data_unit): | |
318 """Generates the output string that will be used for perf result. | |
319 | |
320 Args: | |
321 name: a string for the graph name. | |
322 output_string: the whole string for displaying results. | |
323 trace_name: the name for legend in the performance graph. | |
324 psutil_data: the measured list of data measured by psutil. | |
325 measured_data_unit: the measurement unit name. | |
326 | |
327 Returns: | |
328 a string for performance results that are used for PerfBot if there | |
329 is any result. Otherwise, returns an empty string. | |
330 """ | |
331 output_string_line = UIPerfTestUtils.GetResultStringForPerfBot( | |
332 '', name, trace_name, psutil_data, measured_data_unit) | |
333 if output_string_line: | |
334 return output_string_line + '\n' | |
335 else: | |
336 return '' | |
OLD | NEW |