# Copyright 2013 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import collections from metrics import Metric TRACING_MODE = 'tracing-mode' TIMELINE_MODE = 'timeline-mode' class TimelineMetric(Metric): def __init__(self, mode): ''' Initializes a TimelineMetric object. mode: TRACING_MODE or TIMELINE_MODE thread_filter: list of thread names to include. Empty list or None includes all threads. In TIMELINE_MODE, only 'thread 0' is available, which is the renderer main thread. In TRACING_MODE, the following renderer process threads are available: 'CrRendererMain', 'Chrome_ChildIOThread', and 'Compositor'. CompositorRasterWorker threads are available with impl-side painting enabled. ''' assert mode in (TRACING_MODE, TIMELINE_MODE) super(TimelineMetric, self).__init__() self._mode = mode self._model = None def Start(self, page, tab): self._model = None if self._mode == TRACING_MODE: if not tab.browser.supports_tracing: raise Exception('Not supported') tab.browser.StartTracing() else: assert self._mode == TIMELINE_MODE tab.StartTimelineRecording() def Stop(self, page, tab): if self._mode == TRACING_MODE: trace_result = tab.browser.StopTracing() self._model = trace_result.AsTimelineModel() else: tab.StopTimelineRecording() self._model = tab.timeline_model def GetRendererProcess(self, tab): if self._mode == TRACING_MODE: return self._model.GetRendererProcessFromTab(tab) else: return self._model.GetAllProcesses()[0] def AddResults(self, tab, results): return class LoadTimesTimelineMetric(TimelineMetric): def __init__(self, mode, thread_filter = None): super(LoadTimesTimelineMetric, self).__init__(mode) self._thread_filter = thread_filter def AddResults(self, tab, results): assert self._model renderer_process = self.GetRendererProcess(tab) events_by_name = collections.defaultdict(list) for thread in renderer_process.threads.itervalues(): if self._thread_filter and not thread.name in self._thread_filter: continue thread_name = thread.name.replace('/','_') events = thread.all_slices for e in events: events_by_name[e.name].append(e) for event_name, event_group in events_by_name.iteritems(): times = [event.self_time for event in event_group] total = sum(times) biggest_jank = max(times) full_name = thread_name + '|' + event_name results.Add(full_name, 'ms', total) results.Add(full_name + '_max', 'ms', biggest_jank) results.Add(full_name + '_avg', 'ms', total / len(times)) for counter_name, counter in renderer_process.counters.iteritems(): total = sum(counter.totals) results.Add(counter_name, 'count', total) results.Add(counter_name + '_avg', 'count', total / len(counter.totals)) # We want to generate a consistant picture of our thread usage, despite # having several process configurations (in-proc-gpu/single-proc). # Since we can't isolate renderer threads in single-process mode, we # always sum renderer-process threads' times. We also sum all io-threads # for simplicity. TimelineThreadCategories = { # These are matched exactly "Chrome_InProcGpuThread": "GPU", "CrGPUMain" : "GPU", "AsyncTransferThread" : "GPU_transfer", "CrBrowserMain" : "browser_main", "Browser Compositor" : "browser_compositor", "CrRendererMain" : "renderer_main", "Compositor" : "renderer_compositor", # These are matched by substring "IOThread" : "IO", "CompositorRasterWorker": "raster" } def ThreadTimePercentageName(category): return "thread_" + category + "_clock_time_percentage" def ThreadCPUTimePercentageName(category): return "thread_" + category + "_cpu_time_percentage" class ThreadTimesTimelineMetric(TimelineMetric): def __init__(self): super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE) def AddResults(self, tab, results): # Default each category to zero for consistant results. category_clock_times = collections.defaultdict(float) category_cpu_times = collections.defaultdict(float) for category in TimelineThreadCategories.values(): category_clock_times[category] = 0 category_cpu_times[category] = 0 # Add up thread time for all threads we care about. for thread in self._model.GetAllThreads(): # First determine if we care about this thread. # Check substrings first, followed by exact matches thread_category = None for substring, category in TimelineThreadCategories.iteritems(): if substring in thread.name: thread_category = category if thread.name in TimelineThreadCategories: thread_category = TimelineThreadCategories[thread.name] if thread_category == None: thread_category = "other" # Sum and add top-level slice durations clock = sum([event.duration for event in thread.toplevel_slices]) category_clock_times[thread_category] += clock cpu = sum([event.thread_duration for event in thread.toplevel_slices]) category_cpu_times[thread_category] += cpu # Now report each category. We report the percentage of time that # the thread is running rather than absolute time, to represent how # busy the thread is. This needs to be interpretted when throughput # is changed due to scheduling changes (eg. more frames produced # in the same time period). It would be nice if we could correct # for that somehow. for category, category_time in category_clock_times.iteritems(): report_name = ThreadTimePercentageName(category) time_as_percentage = (category_time / self._model.bounds.bounds) * 100 results.Add(report_name, '%', time_as_percentage) # Do the same for CPU (scheduled) time. for category, category_time in category_cpu_times.iteritems(): report_name = ThreadCPUTimePercentageName(category) time_as_percentage = (category_time / self._model.bounds.bounds) * 100 results.Add(report_name, '%', time_as_percentage)