普通文本  |  168行  |  6.19 KB

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