Home | History | Annotate | Download | only in metrics
      1 # Copyright 2013 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 import collections
      5 
      6 from metrics import Metric
      7 
      8 TRACING_MODE = 'tracing-mode'
      9 TIMELINE_MODE = 'timeline-mode'
     10 
     11 class TimelineMetric(Metric):
     12   def __init__(self, mode):
     13     ''' Initializes a TimelineMetric object.
     14 
     15     mode: TRACING_MODE or TIMELINE_MODE
     16     thread_filter: list of thread names to include.
     17         Empty list or None includes all threads.
     18         In TIMELINE_MODE, only 'thread 0' is available, which is the renderer
     19         main thread.
     20         In TRACING_MODE, the following renderer process threads are available:
     21         'CrRendererMain', 'Chrome_ChildIOThread', and 'Compositor'.
     22         CompositorRasterWorker threads are available with impl-side painting
     23         enabled.
     24     '''
     25     assert mode in (TRACING_MODE, TIMELINE_MODE)
     26     super(TimelineMetric, self).__init__()
     27     self._mode = mode
     28     self._model = None
     29 
     30   def Start(self, page, tab):
     31     self._model = None
     32     if self._mode == TRACING_MODE:
     33       if not tab.browser.supports_tracing:
     34         raise Exception('Not supported')
     35       tab.browser.StartTracing()
     36     else:
     37       assert self._mode == TIMELINE_MODE
     38       tab.StartTimelineRecording()
     39 
     40   def Stop(self, page, tab):
     41     if self._mode == TRACING_MODE:
     42       trace_result = tab.browser.StopTracing()
     43       self._model = trace_result.AsTimelineModel()
     44     else:
     45       tab.StopTimelineRecording()
     46       self._model = tab.timeline_model
     47 
     48   def GetRendererProcess(self, tab):
     49     if self._mode == TRACING_MODE:
     50       return self._model.GetRendererProcessFromTab(tab)
     51     else:
     52       return self._model.GetAllProcesses()[0]
     53 
     54   def AddResults(self, tab, results):
     55     return
     56 
     57 
     58 class LoadTimesTimelineMetric(TimelineMetric):
     59   def __init__(self, mode, thread_filter = None):
     60     super(LoadTimesTimelineMetric, self).__init__(mode)
     61     self._thread_filter = thread_filter
     62 
     63   def AddResults(self, tab, results):
     64     assert self._model
     65 
     66     renderer_process = self.GetRendererProcess(tab)
     67     events_by_name = collections.defaultdict(list)
     68 
     69     for thread in renderer_process.threads.itervalues():
     70 
     71       if self._thread_filter and not thread.name in self._thread_filter:
     72         continue
     73 
     74       thread_name = thread.name.replace('/','_')
     75       events = thread.all_slices
     76 
     77       for e in events:
     78         events_by_name[e.name].append(e)
     79 
     80       for event_name, event_group in events_by_name.iteritems():
     81         times = [event.self_time for event in event_group]
     82         total = sum(times)
     83         biggest_jank = max(times)
     84         full_name = thread_name + '|' + event_name
     85         results.Add(full_name, 'ms', total)
     86         results.Add(full_name + '_max', 'ms', biggest_jank)
     87         results.Add(full_name + '_avg', 'ms', total / len(times))
     88 
     89     for counter_name, counter in renderer_process.counters.iteritems():
     90       total = sum(counter.totals)
     91       results.Add(counter_name, 'count', total)
     92       results.Add(counter_name + '_avg', 'count', total / len(counter.totals))
     93 
     94 
     95 # We want to generate a consistant picture of our thread usage, despite
     96 # having several process configurations (in-proc-gpu/single-proc).
     97 # Since we can't isolate renderer threads in single-process mode, we
     98 # always sum renderer-process threads' times. We also sum all io-threads
     99 # for simplicity.
    100 TimelineThreadCategories =  {
    101   # These are matched exactly
    102   "Chrome_InProcGpuThread": "GPU",
    103   "CrGPUMain"             : "GPU",
    104   "AsyncTransferThread"   : "GPU_transfer",
    105   "CrBrowserMain"         : "browser_main",
    106   "Browser Compositor"    : "browser_compositor",
    107   "CrRendererMain"        : "renderer_main",
    108   "Compositor"            : "renderer_compositor",
    109   # These are matched by substring
    110   "IOThread"              : "IO",
    111   "CompositorRasterWorker": "raster"
    112 }
    113 
    114 def ThreadTimePercentageName(category):
    115   return "thread_" + category + "_clock_time_percentage"
    116 
    117 def ThreadCPUTimePercentageName(category):
    118   return "thread_" + category + "_cpu_time_percentage"
    119 
    120 class ThreadTimesTimelineMetric(TimelineMetric):
    121   def __init__(self):
    122     super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE)
    123 
    124   def AddResults(self, tab, results):
    125     # Default each category to zero for consistant results.
    126     category_clock_times = collections.defaultdict(float)
    127     category_cpu_times = collections.defaultdict(float)
    128 
    129     for category in TimelineThreadCategories.values():
    130       category_clock_times[category] = 0
    131       category_cpu_times[category] = 0
    132 
    133     # Add up thread time for all threads we care about.
    134     for thread in self._model.GetAllThreads():
    135       # First determine if we care about this thread.
    136       # Check substrings first, followed by exact matches
    137       thread_category = None
    138       for substring, category in TimelineThreadCategories.iteritems():
    139         if substring in thread.name:
    140           thread_category = category
    141       if thread.name in TimelineThreadCategories:
    142         thread_category = TimelineThreadCategories[thread.name]
    143       if thread_category == None:
    144         thread_category = "other"
    145 
    146       # Sum and add top-level slice durations
    147       clock = sum([event.duration for event in thread.toplevel_slices])
    148       category_clock_times[thread_category] += clock
    149       cpu = sum([event.thread_duration for event in thread.toplevel_slices])
    150       category_cpu_times[thread_category] += cpu
    151 
    152     # Now report each category. We report the percentage of time that
    153     # the thread is running rather than absolute time, to represent how
    154     # busy the thread is. This needs to be interpretted when throughput
    155     # is changed due to scheduling changes (eg. more frames produced
    156     # in the same time period). It would be nice if we could correct
    157     # for that somehow.
    158     for category, category_time in category_clock_times.iteritems():
    159       report_name = ThreadTimePercentageName(category)
    160       time_as_percentage = (category_time / self._model.bounds.bounds) * 100
    161       results.Add(report_name, '%', time_as_percentage)
    162 
    163     # Do the same for CPU (scheduled) time.
    164     for category, category_time in category_cpu_times.iteritems():
    165       report_name = ThreadCPUTimePercentageName(category)
    166       time_as_percentage = (category_time / self._model.bounds.bounds) * 100
    167       results.Add(report_name, '%', time_as_percentage)
    168