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.
4
5import collections
6
7from telemetry.value import list_of_scalar_values
8from telemetry.value import scalar
9
10
11class TraceEventStatsInput(object):
12  """Input for the TraceEventStats.
13  Using this object with TraceEventStats will include two metrics, one with a
14  list of times of the given event, and one for the count of the events, named
15  `metric_name + '-count'`.
16  Args:
17    event_category: The category of the event to track.
18    event_name: The name of the event to track.
19    metric_name: The name of the metric name, which accumulates all of the
20                 times of the events.
21    metric_description: Description of the metric.
22    units: Units for the metric.
23    process_name: (optional) The name of the process to inspect for the trace
24                  events. Defaults to 'Renderer'.
25  """
26  def __init__(self, event_category, event_name, metric_name,
27               metric_description, units, process_name='Renderer'):
28    self.event_category = event_category
29    self.event_name = event_name
30    self.metric_name = metric_name
31    self.metric_description = metric_description
32    self.units = units
33    self.process_name = process_name
34    self.event_id = TraceEventStatsInput.GetEventId(event_category, event_name)
35    assert process_name is not None
36
37  @staticmethod
38  def GetEventId(event_category, event_name):
39    return event_category + '^SERIALIZE-DELIM^' + event_name
40
41class TraceEventStats(object):
42  """Reports durations and counts of given trace events.
43  """
44
45  def __init__(self, trace_event_aggregator_inputs=None):
46    self._inputs_by_process_name = collections.defaultdict(list)
47    self._metrics = set()
48    self._IndexNewInputs(trace_event_aggregator_inputs)
49
50  def AddInput(self, trace_event_aggregator_input):
51    self._IndexNewInputs([trace_event_aggregator_input])
52
53  def _IndexNewInputs(self, input_list):
54    if not input_list:
55      return
56    for input_obj in input_list:
57      name = input_obj.metric_name
58      # We check here to make sure we don't have a duplicate metric
59      assert name not in self._metrics
60      assert (name + '-count') not in self._metrics
61      self._metrics.add(name)
62      self._metrics.add(name + '-count')
63
64      self._inputs_by_process_name[input_obj.process_name].append(input_obj)
65
66  @staticmethod
67  def ThreadDurationIfPresent(event):
68    if event.thread_duration:
69      return event.thread_duration
70    else:
71      return event.duration
72
73  def AddResults(self, model, renderer_process, interactions, results):
74    del renderer_process  # unused
75    assert interactions
76    for p in model.GetAllProcesses():
77      if p.name not in self._inputs_by_process_name:
78        continue
79
80      inputs = self._inputs_by_process_name[p.name]
81      input_ids = {i.event_id for i in inputs}
82
83      def InputIdPredicate(e, ids):
84        return TraceEventStatsInput.GetEventId(e.category, e.name) in ids
85
86      self._AddResultsInternal(
87          p.IterAllEvents(
88              recursive=True,
89              event_type_predicate=lambda t: True,
90              event_predicate=
91                  lambda e, ids=input_ids: InputIdPredicate(e, ids)),
92          interactions,
93          results,
94          inputs)
95
96  # We assume events have been filtered already. 'events' is an iterator.
97  def _AddResultsInternal(self, events, interactions, results, inputs):
98    times_by_event_id = collections.defaultdict(list)
99
100    for event in events:
101      if not any(interaction.start <= event.start <= interaction.end
102                 for interaction in interactions):
103        continue
104      event_id = TraceEventStatsInput.GetEventId(event.category, event.name)
105      times_by_event_id[event_id].append(self.ThreadDurationIfPresent(event))
106
107    if not times_by_event_id:
108      return
109
110    inputs_by_event_id = dict([[input_obj.event_id, input_obj]
111                                for input_obj in inputs])
112
113    for (event_name, times) in times_by_event_id.iteritems():
114      input_for_event = inputs_by_event_id[event_name]
115      name = input_for_event.metric_name
116      results.AddValue(scalar.ScalarValue(
117        page=results.current_page,
118        tir_label=interactions[0].label,
119        name=name + '-count',
120        units='count',
121        value=len(times),
122        description='The number of times ' + name + ' was recorded.'))
123      if len(times) == 0:
124        continue
125      results.AddValue(list_of_scalar_values.ListOfScalarValues(
126        page=results.current_page,
127        tir_label=interactions[0].label,
128        name=name,
129        units=input_for_event.units,
130        values=times,
131        description=input_for_event.metric_description))
132