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