1# Copyright 2016 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 re
6
7from telemetry.timeline import chrome_trace_category_filter
8
9TRACE_BUFFER_SIZE_IN_KB = 'trace_buffer_size_in_kb'
10
11RECORD_MODE = 'record_mode'
12
13RECORD_CONTINUOUSLY = 'record-continuously'
14RECORD_UNTIL_FULL = 'record-until-full'
15RECORD_AS_MUCH_AS_POSSIBLE = 'record-as-much-as-possible'
16ECHO_TO_CONSOLE = 'trace-to-console'
17
18RECORD_MODES = {
19    RECORD_UNTIL_FULL,
20    RECORD_CONTINUOUSLY,
21    RECORD_AS_MUCH_AS_POSSIBLE,
22    ECHO_TO_CONSOLE,
23}
24
25ENABLE_SYSTRACE_PARAM = 'enable_systrace'
26UMA_HISTOGRAM_NAMES_PARAM = 'histogram_names'
27
28def _ConvertStringToCamelCase(string):
29  """Convert an underscore/hyphen-case string to its camel-case counterpart.
30
31  This function is the inverse of Chromium's ConvertFromCamelCase function
32  in src/content/browser/devtools/protocol/tracing_handler.cc.
33  """
34  parts = re.split(r'[-_]', string)
35  return parts[0] + ''.join([p.title() for p in parts[1:]])
36
37# TODO(crbug.com/971471): Don't do this anymore.
38def _ConvertDictKeysToCamelCaseRecursively(data):
39  """Recursively convert dictionary keys from underscore/hyphen- to camel-case.
40
41  This function is the inverse of Chromium's ConvertDictKeyStyle function
42  in src/content/browser/devtools/protocol/tracing_handler.cc.
43  """
44  if isinstance(data, dict):
45    return {_ConvertStringToCamelCase(k):
46            _ConvertDictKeysToCamelCaseRecursively(v)
47            for k, v in data.iteritems()}
48  elif isinstance(data, list):
49    return map(_ConvertDictKeysToCamelCaseRecursively, data)
50  else:
51    return data
52
53
54class ChromeTraceConfig(object):
55  """Stores configuration options specific to the Chrome tracing agent.
56
57  This produces the trace config JSON string for tracing in Chrome.
58
59  Attributes:
60    record_mode: can be any mode in RECORD_MODES. This corresponds to
61        record modes in chrome.
62    category_filter: Object that specifies which tracing categories to trace.
63  """
64
65  def __init__(self):
66    self._record_mode = RECORD_CONTINUOUSLY
67    self._category_filter = (
68        chrome_trace_category_filter.ChromeTraceCategoryFilter())
69    self._memory_dump_config = None
70    self._enable_systrace = False
71    self._uma_histogram_names = []
72    self._trace_buffer_size_in_kb = None
73    self._trace_format = None
74
75  @property
76  def trace_format(self):
77    return self._trace_format
78
79  def SetProtoTraceFormat(self):
80    self._trace_format = 'proto'
81
82  def SetJsonTraceFormat(self):
83    self._trace_format = 'json'
84
85  def SetLowOverheadFilter(self):
86    self._category_filter = (
87        chrome_trace_category_filter.CreateLowOverheadFilter())
88
89  def SetDefaultOverheadFilter(self):
90    self._category_filter = (
91        chrome_trace_category_filter.CreateDefaultOverheadFilter())
92
93  def SetDebugOverheadFilter(self):
94    self._category_filter = (
95        chrome_trace_category_filter.CreateDebugOverheadFilter())
96
97  @property
98  def category_filter(self):
99    return self._category_filter
100
101  @property
102  def enable_systrace(self):
103    return self._enable_systrace
104
105  def SetCategoryFilter(self, cf):
106    if isinstance(cf, chrome_trace_category_filter.ChromeTraceCategoryFilter):
107      self._category_filter = cf
108    else:
109      raise TypeError(
110          'Must pass SetCategoryFilter a ChromeTraceCategoryFilter instance')
111
112  def SetMemoryDumpConfig(self, dump_config):
113    """Memory dump config stores the triggers for memory dumps."""
114    if isinstance(dump_config, MemoryDumpConfig):
115      self._memory_dump_config = dump_config
116    else:
117      raise TypeError(
118          'Must pass SetMemoryDumpConfig a MemoryDumpConfig instance')
119
120  def SetEnableSystrace(self):
121    self._enable_systrace = True
122
123  def SetTraceBufferSizeInKb(self, size):
124    self._trace_buffer_size_in_kb = size
125
126  def EnableUMAHistograms(self, *args):
127    for uma_histogram_name in args:
128      self._uma_histogram_names.append(uma_histogram_name)
129
130  def HasUMAHistograms(self):
131    return len(self._uma_histogram_names) != 0
132
133  @property
134  def record_mode(self):
135    return self._record_mode
136
137  @record_mode.setter
138  def record_mode(self, value):
139    assert value in RECORD_MODES
140    self._record_mode = value
141
142  def GetChromeTraceConfigForStartupTracing(self):
143    """Map the config to a JSON string for startup tracing.
144
145    All keys in the returned dictionary use underscore-case (e.g.
146    'record_mode'). In addition, the 'record_mode' value uses hyphen-case
147    (e.g. 'record-until-full').
148    """
149    result = {
150        RECORD_MODE: self._record_mode
151    }
152    result.update(self._category_filter.GetDictForChromeTracing())
153    if self._memory_dump_config:
154      result.update(self._memory_dump_config.GetDictForChromeTracing())
155    if self._enable_systrace:
156      result[ENABLE_SYSTRACE_PARAM] = True
157    if self._uma_histogram_names:
158      result[UMA_HISTOGRAM_NAMES_PARAM] = self._uma_histogram_names
159    if self._trace_buffer_size_in_kb:
160      result[TRACE_BUFFER_SIZE_IN_KB] = self._trace_buffer_size_in_kb
161    return result
162
163  def GetChromeTraceConfigForDevTools(self):
164    """Map the config to a DevTools API config dictionary.
165
166    All keys in the returned dictionary use camel-case (e.g. 'recordMode').
167    In addition, the 'recordMode' value also uses camel-case (e.g.
168    'recordUntilFull'). This is to invert the camel-case ->
169    underscore/hyphen-delimited mapping performed in Chromium devtools.
170    """
171    result = self.GetChromeTraceConfigForStartupTracing()
172    if result[RECORD_MODE]:
173      result[RECORD_MODE] = _ConvertStringToCamelCase(
174          result[RECORD_MODE])
175    if self._enable_systrace:
176      result.update({ENABLE_SYSTRACE_PARAM: True})
177    return _ConvertDictKeysToCamelCaseRecursively(result)
178
179
180class MemoryDumpConfig(object):
181  """Stores the triggers for memory dumps in ChromeTraceConfig."""
182  def __init__(self):
183    self._triggers = []
184
185  def AddTrigger(self, mode, periodic_interval_ms):
186    """Adds a new trigger to config.
187
188    Args:
189      periodic_interval_ms: Dump time period in milliseconds.
190      level_of_detail: Memory dump level of detail string.
191          Valid arguments are "background", "light" and "detailed".
192    """
193    assert mode in ['background', 'light', 'detailed']
194    assert periodic_interval_ms > 0
195    self._triggers.append({'mode': mode,
196                           'periodic_interval_ms': periodic_interval_ms})
197
198  def GetDictForChromeTracing(self):
199    """Returns the dump config as dictionary for chrome tracing."""
200    # An empty trigger list would mean no periodic memory dumps.
201    return {'memory_dump_config': {'triggers': self._triggers}}
202