1# Copyright 2015 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. 4import collections 5import math 6import sys 7 8from telemetry.timeline import model as model_module 9from telemetry.value import improvement_direction 10from telemetry.value import list_of_scalar_values 11from telemetry.value import scalar 12from telemetry.web_perf.metrics import timeline_based_metric 13 14TOPLEVEL_GL_CATEGORY = 'gpu_toplevel' 15TOPLEVEL_SERVICE_CATEGORY = 'disabled-by-default-gpu.service' 16TOPLEVEL_DEVICE_CATEGORY = 'disabled-by-default-gpu.device' 17 18SERVICE_FRAME_END_MARKER = (TOPLEVEL_SERVICE_CATEGORY, 'SwapBuffer') 19DEVICE_FRAME_END_MARKER = (TOPLEVEL_DEVICE_CATEGORY, 'SwapBuffer') 20 21TRACKED_GL_CONTEXT_NAME = {'RenderCompositor': 'render_compositor', 22 'BrowserCompositor': 'browser_compositor', 23 'Compositor': 'browser_compositor'} 24 25 26def _CalculateFrameTimes(events_per_frame, event_data_func): 27 """Given a list of events per frame and a function to extract event time data, 28 returns a list of frame times.""" 29 times_per_frame = [] 30 for event_list in events_per_frame: 31 event_times = [event_data_func(event) for event in event_list] 32 times_per_frame.append(sum(event_times)) 33 return times_per_frame 34 35 36def _CPUFrameTimes(events_per_frame): 37 """Given a list of events per frame, returns a list of CPU frame times.""" 38 # CPU event frames are calculated using the event thread duration. 39 # Some platforms do not support thread_duration, convert those to 0. 40 return _CalculateFrameTimes(events_per_frame, 41 lambda event: event.thread_duration or 0) 42 43 44def _GPUFrameTimes(events_per_frame): 45 """Given a list of events per frame, returns a list of GPU frame times.""" 46 # GPU event frames are asynchronous slices which use the event duration. 47 return _CalculateFrameTimes(events_per_frame, 48 lambda event: event.duration) 49 50 51def TimelineName(name, source_type, value_type): 52 """Constructs the standard name given in the timeline. 53 54 Args: 55 name: The name of the timeline, for example "total", or "render_compositor". 56 source_type: One of "cpu", "gpu" or None. None is only used for total times. 57 value_type: the type of value. For example "mean", "stddev"...etc. 58 """ 59 if source_type: 60 return '%s_%s_%s_time' % (name, value_type, source_type) 61 else: 62 return '%s_%s_time' % (name, value_type) 63 64 65class GPUTimelineMetric(timeline_based_metric.TimelineBasedMetric): 66 """Computes GPU based metrics.""" 67 68 def __init__(self): 69 super(GPUTimelineMetric, self).__init__() 70 71 def AddResults(self, model, _, interaction_records, results): 72 self.VerifyNonOverlappedRecords(interaction_records) 73 service_times = self._CalculateGPUTimelineData(model) 74 for value_item, durations in service_times.iteritems(): 75 count = len(durations) 76 avg = 0.0 77 stddev = 0.0 78 maximum = 0.0 79 if count: 80 avg = sum(durations) / count 81 stddev = math.sqrt(sum((d - avg) ** 2 for d in durations) / count) 82 maximum = max(durations) 83 84 name, src = value_item 85 86 if src: 87 frame_times_name = '%s_%s_frame_times' % (name, src) 88 else: 89 frame_times_name = '%s_frame_times' % (name) 90 91 if durations: 92 results.AddValue(list_of_scalar_values.ListOfScalarValues( 93 results.current_page, frame_times_name, 'ms', durations, 94 tir_label=interaction_records[0].label, 95 improvement_direction=improvement_direction.DOWN)) 96 97 results.AddValue(scalar.ScalarValue( 98 results.current_page, TimelineName(name, src, 'max'), 'ms', maximum, 99 tir_label=interaction_records[0].label, 100 improvement_direction=improvement_direction.DOWN)) 101 results.AddValue(scalar.ScalarValue( 102 results.current_page, TimelineName(name, src, 'mean'), 'ms', avg, 103 tir_label=interaction_records[0].label, 104 improvement_direction=improvement_direction.DOWN)) 105 results.AddValue(scalar.ScalarValue( 106 results.current_page, TimelineName(name, src, 'stddev'), 'ms', stddev, 107 tir_label=interaction_records[0].label, 108 improvement_direction=improvement_direction.DOWN)) 109 110 def _CalculateGPUTimelineData(self, model): 111 """Uses the model and calculates the times for various values for each 112 frame. The return value will be a dictionary of the following format: 113 { 114 (EVENT_NAME1, SRC1_TYPE): [FRAME0_TIME, FRAME1_TIME...etc.], 115 (EVENT_NAME2, SRC2_TYPE): [FRAME0_TIME, FRAME1_TIME...etc.], 116 } 117 118 Events: 119 swap - The time in milliseconds between each swap marker. 120 total - The amount of time spent in the renderer thread. 121 TRACKED_NAMES: Using the TRACKED_GL_CONTEXT_NAME dict, we 122 include the traces per frame for the 123 tracked name. 124 Source Types: 125 None - This will only be valid for the "swap" event. 126 cpu - For an event, the "cpu" source type signifies time spent on the 127 gpu thread using the CPU. This uses the "gpu.service" markers. 128 gpu - For an event, the "gpu" source type signifies time spent on the 129 gpu thread using the GPU. This uses the "gpu.device" markers. 130 """ 131 all_service_events = [] 132 current_service_frame_end = sys.maxint 133 current_service_events = [] 134 135 all_device_events = [] 136 current_device_frame_end = sys.maxint 137 current_device_events = [] 138 139 tracked_events = {} 140 tracked_events.update( 141 dict([((value, 'cpu'), []) 142 for value in TRACKED_GL_CONTEXT_NAME.itervalues()])) 143 tracked_events.update( 144 dict([((value, 'gpu'), []) 145 for value in TRACKED_GL_CONTEXT_NAME.itervalues()])) 146 147 # These will track traces within the current frame. 148 current_tracked_service_events = collections.defaultdict(list) 149 current_tracked_device_events = collections.defaultdict(list) 150 151 event_iter = model.IterAllEvents( 152 event_type_predicate=model_module.IsSliceOrAsyncSlice) 153 for event in event_iter: 154 # Look for frame end markers 155 if (event.category, event.name) == SERVICE_FRAME_END_MARKER: 156 current_service_frame_end = event.end 157 elif (event.category, event.name) == DEVICE_FRAME_END_MARKER: 158 current_device_frame_end = event.end 159 160 # Track all other toplevel gl category markers 161 elif event.args.get('gl_category', None) == TOPLEVEL_GL_CATEGORY: 162 base_name = event.name 163 dash_index = base_name.rfind('-') 164 if dash_index != -1: 165 base_name = base_name[:dash_index] 166 tracked_name = TRACKED_GL_CONTEXT_NAME.get(base_name, None) 167 168 if event.category == TOPLEVEL_SERVICE_CATEGORY: 169 # Check if frame has ended. 170 if event.start >= current_service_frame_end: 171 if current_service_events: 172 all_service_events.append(current_service_events) 173 for value in TRACKED_GL_CONTEXT_NAME.itervalues(): 174 tracked_events[(value, 'cpu')].append( 175 current_tracked_service_events[value]) 176 current_service_events = [] 177 current_service_frame_end = sys.maxint 178 current_tracked_service_events.clear() 179 180 current_service_events.append(event) 181 if tracked_name: 182 current_tracked_service_events[tracked_name].append(event) 183 184 elif event.category == TOPLEVEL_DEVICE_CATEGORY: 185 # Check if frame has ended. 186 if event.start >= current_device_frame_end: 187 if current_device_events: 188 all_device_events.append(current_device_events) 189 for value in TRACKED_GL_CONTEXT_NAME.itervalues(): 190 tracked_events[(value, 'gpu')].append( 191 current_tracked_device_events[value]) 192 current_device_events = [] 193 current_device_frame_end = sys.maxint 194 current_tracked_device_events.clear() 195 196 current_device_events.append(event) 197 if tracked_name: 198 current_tracked_device_events[tracked_name].append(event) 199 200 # Append Data for Last Frame. 201 if current_service_events: 202 all_service_events.append(current_service_events) 203 for value in TRACKED_GL_CONTEXT_NAME.itervalues(): 204 tracked_events[(value, 'cpu')].append( 205 current_tracked_service_events[value]) 206 if current_device_events: 207 all_device_events.append(current_device_events) 208 for value in TRACKED_GL_CONTEXT_NAME.itervalues(): 209 tracked_events[(value, 'gpu')].append( 210 current_tracked_device_events[value]) 211 212 # Calculate Mean Frame Time for the CPU side. 213 frame_times = [] 214 if all_service_events: 215 prev_frame_end = all_service_events[0][0].start 216 for event_list in all_service_events: 217 last_service_event_in_frame = event_list[-1] 218 frame_times.append(last_service_event_in_frame.end - prev_frame_end) 219 prev_frame_end = last_service_event_in_frame.end 220 221 # Create the timeline data dictionary for service side traces. 222 total_frame_value = ('swap', None) 223 cpu_frame_value = ('total', 'cpu') 224 gpu_frame_value = ('total', 'gpu') 225 timeline_data = {} 226 timeline_data[total_frame_value] = frame_times 227 timeline_data[cpu_frame_value] = _CPUFrameTimes(all_service_events) 228 for value in TRACKED_GL_CONTEXT_NAME.itervalues(): 229 cpu_value = (value, 'cpu') 230 timeline_data[cpu_value] = _CPUFrameTimes(tracked_events[cpu_value]) 231 232 # Add in GPU side traces if it was supported (IE. device traces exist). 233 if all_device_events: 234 timeline_data[gpu_frame_value] = _GPUFrameTimes(all_device_events) 235 for value in TRACKED_GL_CONTEXT_NAME.itervalues(): 236 gpu_value = (value, 'gpu') 237 tracked_gpu_event = tracked_events[gpu_value] 238 timeline_data[gpu_value] = _GPUFrameTimes(tracked_gpu_event) 239 240 return timeline_data 241