| 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 |