| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 | 6 |
| 7 from telemetry.util import perf_tests_helper | 7 from telemetry.util import perf_tests_helper |
| 8 from telemetry.util import statistics | 8 from telemetry.util import statistics |
| 9 from telemetry.value import improvement_direction |
| 9 from telemetry.value import list_of_scalar_values | 10 from telemetry.value import list_of_scalar_values |
| 10 from telemetry.value import scalar | 11 from telemetry.value import scalar |
| 11 from telemetry.web_perf.metrics import rendering_stats | 12 from telemetry.web_perf.metrics import rendering_stats |
| 12 from telemetry.web_perf.metrics import timeline_based_metric | 13 from telemetry.web_perf.metrics import timeline_based_metric |
| 13 | 14 |
| 14 | 15 |
| 15 NOT_ENOUGH_FRAMES_MESSAGE = ( | 16 NOT_ENOUGH_FRAMES_MESSAGE = ( |
| 16 'Not enough frames for smoothness metrics (at least two are required).\n' | 17 'Not enough frames for smoothness metrics (at least two are required).\n' |
| 17 'Issues that have caused this in the past:\n' | 18 'Issues that have caused this in the past:\n' |
| 18 '- Browser bugs that prevents the page from redrawing\n' | 19 '- Browser bugs that prevents the page from redrawing\n' |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 134 max_frame_delay = round(max(normalized_frame_lengths)) | 135 max_frame_delay = round(max(normalized_frame_lengths)) |
| 135 frame_lengths = normalized_frame_lengths | 136 frame_lengths = normalized_frame_lengths |
| 136 else: | 137 else: |
| 137 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE | 138 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE |
| 138 | 139 |
| 139 return ( | 140 return ( |
| 140 scalar.ScalarValue( | 141 scalar.ScalarValue( |
| 141 page, 'avg_surface_fps', 'fps', avg_surface_fps, | 142 page, 'avg_surface_fps', 'fps', avg_surface_fps, |
| 142 description='Average frames per second as measured by the ' | 143 description='Average frames per second as measured by the ' |
| 143 'platform\'s SurfaceFlinger.', | 144 'platform\'s SurfaceFlinger.', |
| 144 none_value_reason=none_value_reason), | 145 none_value_reason=none_value_reason, |
| 146 improvement_direction=improvement_direction.UP), |
| 145 scalar.ScalarValue( | 147 scalar.ScalarValue( |
| 146 page, 'jank_count', 'janks', jank_count, | 148 page, 'jank_count', 'janks', jank_count, |
| 147 description='Number of changes in frame rate as measured by the ' | 149 description='Number of changes in frame rate as measured by the ' |
| 148 'platform\'s SurfaceFlinger.', | 150 'platform\'s SurfaceFlinger.', |
| 149 none_value_reason=none_value_reason), | 151 none_value_reason=none_value_reason, |
| 152 improvement_direction=improvement_direction.DOWN), |
| 150 scalar.ScalarValue( | 153 scalar.ScalarValue( |
| 151 page, 'max_frame_delay', 'vsyncs', max_frame_delay, | 154 page, 'max_frame_delay', 'vsyncs', max_frame_delay, |
| 152 description='Largest frame time as measured by the platform\'s ' | 155 description='Largest frame time as measured by the platform\'s ' |
| 153 'SurfaceFlinger.', | 156 'SurfaceFlinger.', |
| 154 none_value_reason=none_value_reason), | 157 none_value_reason=none_value_reason, |
| 158 improvement_direction=improvement_direction.DOWN), |
| 155 list_of_scalar_values.ListOfScalarValues( | 159 list_of_scalar_values.ListOfScalarValues( |
| 156 page, 'frame_lengths', 'vsyncs', frame_lengths, | 160 page, 'frame_lengths', 'vsyncs', frame_lengths, |
| 157 description='Frame time in vsyncs as measured by the platform\'s ' | 161 description='Frame time in vsyncs as measured by the platform\'s ' |
| 158 'SurfaceFlinger.', | 162 'SurfaceFlinger.', |
| 159 none_value_reason=none_value_reason) | 163 none_value_reason=none_value_reason, |
| 164 improvement_direction=improvement_direction.DOWN) |
| 160 ) | 165 ) |
| 161 | 166 |
| 162 def _ComputeLatencyMetric(self, page, stats, name, list_of_latency_lists): | 167 def _ComputeLatencyMetric(self, page, stats, name, list_of_latency_lists): |
| 163 """Returns Values for the mean and discrepancy for given latency stats.""" | 168 """Returns Values for the mean and discrepancy for given latency stats.""" |
| 164 mean_latency = None | 169 mean_latency = None |
| 165 latency_discrepancy = None | 170 latency_discrepancy = None |
| 166 none_value_reason = None | 171 none_value_reason = None |
| 167 if self._HasEnoughFrames(stats.frame_timestamps): | 172 if self._HasEnoughFrames(stats.frame_timestamps): |
| 168 latency_list = perf_tests_helper.FlattenList(list_of_latency_lists) | 173 latency_list = perf_tests_helper.FlattenList(list_of_latency_lists) |
| 169 if len(latency_list) == 0: | 174 if len(latency_list) == 0: |
| 170 return () | 175 return () |
| 171 mean_latency = round(statistics.ArithmeticMean(latency_list), 3) | 176 mean_latency = round(statistics.ArithmeticMean(latency_list), 3) |
| 172 latency_discrepancy = ( | 177 latency_discrepancy = ( |
| 173 round(statistics.DurationsDiscrepancy(latency_list), 4)) | 178 round(statistics.DurationsDiscrepancy(latency_list), 4)) |
| 174 else: | 179 else: |
| 175 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE | 180 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE |
| 176 return ( | 181 return ( |
| 177 scalar.ScalarValue( | 182 scalar.ScalarValue( |
| 178 page, 'mean_%s' % name, 'ms', mean_latency, | 183 page, 'mean_%s' % name, 'ms', mean_latency, |
| 179 description='Arithmetic mean of the raw %s values' % name, | 184 description='Arithmetic mean of the raw %s values' % name, |
| 180 none_value_reason=none_value_reason), | 185 none_value_reason=none_value_reason, |
| 186 improvement_direction=improvement_direction.DOWN), |
| 181 scalar.ScalarValue( | 187 scalar.ScalarValue( |
| 182 page, '%s_discrepancy' % name, 'ms', latency_discrepancy, | 188 page, '%s_discrepancy' % name, 'ms', latency_discrepancy, |
| 183 description='Discrepancy of the raw %s values' % name, | 189 description='Discrepancy of the raw %s values' % name, |
| 184 none_value_reason=none_value_reason) | 190 none_value_reason=none_value_reason, |
| 191 improvement_direction=improvement_direction.DOWN) |
| 185 ) | 192 ) |
| 186 | 193 |
| 187 def _ComputeFirstGestureScrollUpdateLatencies(self, page, stats): | 194 def _ComputeFirstGestureScrollUpdateLatencies(self, page, stats): |
| 188 """Returns a ListOfScalarValuesValues of gesture scroll update latencies. | 195 """Returns a ListOfScalarValuesValues of gesture scroll update latencies. |
| 189 | 196 |
| 190 Returns a Value for the first gesture scroll update latency for each | 197 Returns a Value for the first gesture scroll update latency for each |
| 191 interaction record in |stats|. | 198 interaction record in |stats|. |
| 192 """ | 199 """ |
| 193 none_value_reason = None | 200 none_value_reason = None |
| 194 first_gesture_scroll_update_latencies = [round(latencies[0], 4) | 201 first_gesture_scroll_update_latencies = [round(latencies[0], 4) |
| 195 for latencies in stats.gesture_scroll_update_latency | 202 for latencies in stats.gesture_scroll_update_latency |
| 196 if len(latencies)] | 203 if len(latencies)] |
| 197 if (not self._HasEnoughFrames(stats.frame_timestamps) or | 204 if (not self._HasEnoughFrames(stats.frame_timestamps) or |
| 198 not first_gesture_scroll_update_latencies): | 205 not first_gesture_scroll_update_latencies): |
| 199 first_gesture_scroll_update_latencies = None | 206 first_gesture_scroll_update_latencies = None |
| 200 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE | 207 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE |
| 201 return list_of_scalar_values.ListOfScalarValues( | 208 return list_of_scalar_values.ListOfScalarValues( |
| 202 page, 'first_gesture_scroll_update_latency', 'ms', | 209 page, 'first_gesture_scroll_update_latency', 'ms', |
| 203 first_gesture_scroll_update_latencies, | 210 first_gesture_scroll_update_latencies, |
| 204 description='First gesture scroll update latency measures the time it ' | 211 description='First gesture scroll update latency measures the time it ' |
| 205 'takes to process the very first gesture scroll update ' | 212 'takes to process the very first gesture scroll update ' |
| 206 'input event. The first scroll gesture can often get ' | 213 'input event. The first scroll gesture can often get ' |
| 207 'delayed by work related to page loading.', | 214 'delayed by work related to page loading.', |
| 208 none_value_reason=none_value_reason) | 215 none_value_reason=none_value_reason, |
| 216 improvement_direction=improvement_direction.DOWN) |
| 209 | 217 |
| 210 def _ComputeQueueingDuration(self, page, stats): | 218 def _ComputeQueueingDuration(self, page, stats): |
| 211 """Returns a Value for the frame queueing durations.""" | 219 """Returns a Value for the frame queueing durations.""" |
| 212 queueing_durations = None | 220 queueing_durations = None |
| 213 none_value_reason = None | 221 none_value_reason = None |
| 214 if 'frame_queueing_durations' in stats.errors: | 222 if 'frame_queueing_durations' in stats.errors: |
| 215 none_value_reason = stats.errors['frame_queueing_durations'] | 223 none_value_reason = stats.errors['frame_queueing_durations'] |
| 216 elif self._HasEnoughFrames(stats.frame_timestamps): | 224 elif self._HasEnoughFrames(stats.frame_timestamps): |
| 217 queueing_durations = perf_tests_helper.FlattenList( | 225 queueing_durations = perf_tests_helper.FlattenList( |
| 218 stats.frame_queueing_durations) | 226 stats.frame_queueing_durations) |
| 219 if len(queueing_durations) == 0: | 227 if len(queueing_durations) == 0: |
| 220 queueing_durations = None | 228 queueing_durations = None |
| 221 none_value_reason = 'No frame queueing durations recorded.' | 229 none_value_reason = 'No frame queueing durations recorded.' |
| 222 else: | 230 else: |
| 223 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE | 231 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE |
| 224 return list_of_scalar_values.ListOfScalarValues( | 232 return list_of_scalar_values.ListOfScalarValues( |
| 225 page, 'queueing_durations', 'ms', queueing_durations, | 233 page, 'queueing_durations', 'ms', queueing_durations, |
| 226 description='The frame queueing duration quantifies how out of sync ' | 234 description='The frame queueing duration quantifies how out of sync ' |
| 227 'the compositor and renderer threads are. It is the amount ' | 235 'the compositor and renderer threads are. It is the amount ' |
| 228 'of wall time that elapses between a ' | 236 'of wall time that elapses between a ' |
| 229 'ScheduledActionSendBeginMainFrame event in the compositor ' | 237 'ScheduledActionSendBeginMainFrame event in the compositor ' |
| 230 'thread and the corresponding BeginMainFrame event in the ' | 238 'thread and the corresponding BeginMainFrame event in the ' |
| 231 'main thread.', | 239 'main thread.', |
| 232 none_value_reason=none_value_reason) | 240 none_value_reason=none_value_reason, |
| 241 improvement_direction=improvement_direction.DOWN) |
| 233 | 242 |
| 234 def _ComputeFrameTimeMetric(self, page, stats): | 243 def _ComputeFrameTimeMetric(self, page, stats): |
| 235 """Returns Values for the frame time metrics. | 244 """Returns Values for the frame time metrics. |
| 236 | 245 |
| 237 This includes the raw and mean frame times, as well as the percentage of | 246 This includes the raw and mean frame times, as well as the percentage of |
| 238 frames that were hitting 60 fps. | 247 frames that were hitting 60 fps. |
| 239 """ | 248 """ |
| 240 frame_times = None | 249 frame_times = None |
| 241 mean_frame_time = None | 250 mean_frame_time = None |
| 242 percentage_smooth = None | 251 percentage_smooth = None |
| 243 none_value_reason = None | 252 none_value_reason = None |
| 244 if self._HasEnoughFrames(stats.frame_timestamps): | 253 if self._HasEnoughFrames(stats.frame_timestamps): |
| 245 frame_times = perf_tests_helper.FlattenList(stats.frame_times) | 254 frame_times = perf_tests_helper.FlattenList(stats.frame_times) |
| 246 mean_frame_time = round(statistics.ArithmeticMean(frame_times), 3) | 255 mean_frame_time = round(statistics.ArithmeticMean(frame_times), 3) |
| 247 # We use 17ms as a somewhat looser threshold, instead of 1000.0/60.0. | 256 # We use 17ms as a somewhat looser threshold, instead of 1000.0/60.0. |
| 248 smooth_threshold = 17.0 | 257 smooth_threshold = 17.0 |
| 249 smooth_count = sum(1 for t in frame_times if t < smooth_threshold) | 258 smooth_count = sum(1 for t in frame_times if t < smooth_threshold) |
| 250 percentage_smooth = float(smooth_count) / len(frame_times) * 100.0 | 259 percentage_smooth = float(smooth_count) / len(frame_times) * 100.0 |
| 251 else: | 260 else: |
| 252 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE | 261 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE |
| 253 return ( | 262 return ( |
| 254 list_of_scalar_values.ListOfScalarValues( | 263 list_of_scalar_values.ListOfScalarValues( |
| 255 page, 'frame_times', 'ms', frame_times, | 264 page, 'frame_times', 'ms', frame_times, |
| 256 description='List of raw frame times, helpful to understand the ' | 265 description='List of raw frame times, helpful to understand the ' |
| 257 'other metrics.', | 266 'other metrics.', |
| 258 none_value_reason=none_value_reason), | 267 none_value_reason=none_value_reason, |
| 268 improvement_direction=improvement_direction.DOWN), |
| 259 scalar.ScalarValue( | 269 scalar.ScalarValue( |
| 260 page, 'mean_frame_time', 'ms', mean_frame_time, | 270 page, 'mean_frame_time', 'ms', mean_frame_time, |
| 261 description='Arithmetic mean of frame times.', | 271 description='Arithmetic mean of frame times.', |
| 262 none_value_reason=none_value_reason), | 272 none_value_reason=none_value_reason, |
| 273 improvement_direction=improvement_direction.DOWN), |
| 263 scalar.ScalarValue( | 274 scalar.ScalarValue( |
| 264 page, 'percentage_smooth', 'score', percentage_smooth, | 275 page, 'percentage_smooth', 'score', percentage_smooth, |
| 265 description='Percentage of frames that were hitting 60 fps.', | 276 description='Percentage of frames that were hitting 60 fps.', |
| 266 none_value_reason=none_value_reason) | 277 none_value_reason=none_value_reason, |
| 278 improvement_direction=improvement_direction.DOWN) |
| 267 ) | 279 ) |
| 268 | 280 |
| 269 def _ComputeFrameTimeDiscrepancy(self, page, stats): | 281 def _ComputeFrameTimeDiscrepancy(self, page, stats): |
| 270 """Returns a Value for the absolute discrepancy of frame time stamps.""" | 282 """Returns a Value for the absolute discrepancy of frame time stamps.""" |
| 271 | 283 |
| 272 frame_discrepancy = None | 284 frame_discrepancy = None |
| 273 none_value_reason = None | 285 none_value_reason = None |
| 274 if self._HasEnoughFrames(stats.frame_timestamps): | 286 if self._HasEnoughFrames(stats.frame_timestamps): |
| 275 frame_discrepancy = round(statistics.TimestampsDiscrepancy( | 287 frame_discrepancy = round(statistics.TimestampsDiscrepancy( |
| 276 stats.frame_timestamps), 4) | 288 stats.frame_timestamps), 4) |
| 277 else: | 289 else: |
| 278 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE | 290 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE |
| 279 return scalar.ScalarValue( | 291 return scalar.ScalarValue( |
| 280 page, 'frame_time_discrepancy', 'ms', frame_discrepancy, | 292 page, 'frame_time_discrepancy', 'ms', frame_discrepancy, |
| 281 description='Absolute discrepancy of frame time stamps, where ' | 293 description='Absolute discrepancy of frame time stamps, where ' |
| 282 'discrepancy is a measure of irregularity. It quantifies ' | 294 'discrepancy is a measure of irregularity. It quantifies ' |
| 283 'the worst jank. For a single pause, discrepancy ' | 295 'the worst jank. For a single pause, discrepancy ' |
| 284 'corresponds to the length of this pause in milliseconds. ' | 296 'corresponds to the length of this pause in milliseconds. ' |
| 285 'Consecutive pauses increase the discrepancy. This metric ' | 297 'Consecutive pauses increase the discrepancy. This metric ' |
| 286 'is important because even if the mean and 95th ' | 298 'is important because even if the mean and 95th ' |
| 287 'percentile are good, one long pause in the middle of an ' | 299 'percentile are good, one long pause in the middle of an ' |
| 288 'interaction is still bad.', | 300 'interaction is still bad.', |
| 289 none_value_reason=none_value_reason) | 301 none_value_reason=none_value_reason, |
| 302 improvement_direction=improvement_direction.DOWN) |
| 290 | 303 |
| 291 def _ComputeMeanPixelsApproximated(self, page, stats): | 304 def _ComputeMeanPixelsApproximated(self, page, stats): |
| 292 """Add the mean percentage of pixels approximated. | 305 """Add the mean percentage of pixels approximated. |
| 293 | 306 |
| 294 This looks at tiles which are missing or of low or non-ideal resolution. | 307 This looks at tiles which are missing or of low or non-ideal resolution. |
| 295 """ | 308 """ |
| 296 mean_pixels_approximated = None | 309 mean_pixels_approximated = None |
| 297 none_value_reason = None | 310 none_value_reason = None |
| 298 if self._HasEnoughFrames(stats.frame_timestamps): | 311 if self._HasEnoughFrames(stats.frame_timestamps): |
| 299 mean_pixels_approximated = round(statistics.ArithmeticMean( | 312 mean_pixels_approximated = round(statistics.ArithmeticMean( |
| 300 perf_tests_helper.FlattenList( | 313 perf_tests_helper.FlattenList( |
| 301 stats.approximated_pixel_percentages)), 3) | 314 stats.approximated_pixel_percentages)), 3) |
| 302 else: | 315 else: |
| 303 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE | 316 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE |
| 304 return scalar.ScalarValue( | 317 return scalar.ScalarValue( |
| 305 page, 'mean_pixels_approximated', 'percent', mean_pixels_approximated, | 318 page, 'mean_pixels_approximated', 'percent', mean_pixels_approximated, |
| 306 description='Percentage of pixels that were approximated ' | 319 description='Percentage of pixels that were approximated ' |
| 307 '(checkerboarding, low-resolution tiles, etc.).', | 320 '(checkerboarding, low-resolution tiles, etc.).', |
| 308 none_value_reason=none_value_reason) | 321 none_value_reason=none_value_reason, |
| 322 improvement_direction=improvement_direction.DOWN) |
| 309 | 323 |
| 310 def _ComputeMeanPixelsCheckerboarded(self, page, stats): | 324 def _ComputeMeanPixelsCheckerboarded(self, page, stats): |
| 311 """Add the mean percentage of pixels checkerboarded. | 325 """Add the mean percentage of pixels checkerboarded. |
| 312 | 326 |
| 313 This looks at tiles which are only missing. | 327 This looks at tiles which are only missing. |
| 314 It does not take into consideration tiles which are of low or | 328 It does not take into consideration tiles which are of low or |
| 315 non-ideal resolution. | 329 non-ideal resolution. |
| 316 """ | 330 """ |
| 317 mean_pixels_checkerboarded = None | 331 mean_pixels_checkerboarded = None |
| 318 none_value_reason = None | 332 none_value_reason = None |
| 319 if self._HasEnoughFrames(stats.frame_timestamps): | 333 if self._HasEnoughFrames(stats.frame_timestamps): |
| 320 if rendering_stats.CHECKERBOARDED_PIXEL_ERROR in stats.errors: | 334 if rendering_stats.CHECKERBOARDED_PIXEL_ERROR in stats.errors: |
| 321 none_value_reason = stats.errors[ | 335 none_value_reason = stats.errors[ |
| 322 rendering_stats.CHECKERBOARDED_PIXEL_ERROR] | 336 rendering_stats.CHECKERBOARDED_PIXEL_ERROR] |
| 323 else: | 337 else: |
| 324 mean_pixels_checkerboarded = round(statistics.ArithmeticMean( | 338 mean_pixels_checkerboarded = round(statistics.ArithmeticMean( |
| 325 perf_tests_helper.FlattenList( | 339 perf_tests_helper.FlattenList( |
| 326 stats.checkerboarded_pixel_percentages)), 3) | 340 stats.checkerboarded_pixel_percentages)), 3) |
| 327 else: | 341 else: |
| 328 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE | 342 none_value_reason = NOT_ENOUGH_FRAMES_MESSAGE |
| 329 return scalar.ScalarValue( | 343 return scalar.ScalarValue( |
| 330 page, 'mean_pixels_checkerboarded', 'percent', | 344 page, 'mean_pixels_checkerboarded', 'percent', |
| 331 mean_pixels_checkerboarded, | 345 mean_pixels_checkerboarded, |
| 332 description='Percentage of pixels that were checkerboarded.', | 346 description='Percentage of pixels that were checkerboarded.', |
| 333 none_value_reason=none_value_reason) | 347 none_value_reason=none_value_reason, |
| 348 improvement_direction=improvement_direction.DOWN) |
| OLD | NEW |