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
9
10RECORD_MODE_PARAM = 'record_mode'
11
12ECHO_TO_CONSOLE = 'trace-to-console'
13RECORD_AS_MUCH_AS_POSSIBLE = 'record-as-much-as-possible'
14RECORD_CONTINUOUSLY = 'record-continuously'
15RECORD_UNTIL_FULL = 'record-until-full'
16
17# Map telemetry's tracing record_mode to the DevTools API string.
18# (The keys happen to be the same as the values.)
19RECORD_MODE_MAP = {
20  RECORD_UNTIL_FULL: 'record-until-full',
21  RECORD_CONTINUOUSLY: 'record-continuously',
22  RECORD_AS_MUCH_AS_POSSIBLE: 'record-as-much-as-possible',
23  ECHO_TO_CONSOLE: 'trace-to-console'
24}
25
26
27def ConvertStringToCamelCase(string):
28  """Convert an underscore/hyphen-case string to its camel-case counterpart.
29
30  This function is the inverse of Chromium's ConvertFromCamelCase function
31  in src/content/browser/devtools/protocol/tracing_handler.cc.
32  """
33  parts = re.split(r'[-_]', string)
34  return parts[0] + ''.join([p.title() for p in parts[1:]])
35
36
37def ConvertDictKeysToCamelCaseRecursively(data):
38  """Recursively convert dictionary keys from underscore/hyphen- to camel-case.
39
40  This function is the inverse of Chromium's ConvertDictKeyStyle function
41  in src/content/browser/devtools/protocol/tracing_handler.cc.
42  """
43  if isinstance(data, dict):
44    return {ConvertStringToCamelCase(k):
45            ConvertDictKeysToCamelCaseRecursively(v)
46            for k, v in data.iteritems()}
47  elif isinstance(data, list):
48    return map(ConvertDictKeysToCamelCaseRecursively, data)
49  else:
50    return data
51
52
53class ChromeTraceConfig(object):
54  """Stores configuration options specific to the Chrome tracing agent.
55
56    This produces the trace config JSON string for tracing in Chrome.
57
58    record_mode: can be any mode in RECORD_MODE_MAP. This corresponds to
59        record modes in chrome.
60    category_filter: Object that specifies which tracing categories to trace.
61    memory_dump_config: Stores the triggers for memory dumps.
62  """
63
64  def __init__(self):
65    self._record_mode = RECORD_AS_MUCH_AS_POSSIBLE
66    self._category_filter = (
67        chrome_trace_category_filter.ChromeTraceCategoryFilter())
68    self._memory_dump_config = None
69
70  def SetLowOverheadFilter(self):
71    self._category_filter = (
72        chrome_trace_category_filter.CreateLowOverheadFilter())
73
74  def SetDefaultOverheadFilter(self):
75    self._category_filter = (
76        chrome_trace_category_filter.CreateDefaultOverheadFilter())
77
78  def SetDebugOverheadFilter(self):
79    self._category_filter = (
80        chrome_trace_category_filter.CreateDebugOverheadFilter())
81
82  @property
83  def category_filter(self):
84    return self._category_filter
85
86  def SetCategoryFilter(self, cf):
87    if isinstance(cf, chrome_trace_category_filter.ChromeTraceCategoryFilter):
88      self._category_filter = cf
89    else:
90      raise TypeError(
91          'Must pass SetCategoryFilter a ChromeTraceCategoryFilter instance')
92
93  def SetMemoryDumpConfig(self, dump_config):
94    if isinstance(dump_config, MemoryDumpConfig):
95      self._memory_dump_config = dump_config
96    else:
97      raise TypeError(
98          'Must pass SetMemoryDumpConfig a MemoryDumpConfig instance')
99
100  @property
101  def record_mode(self):
102    return self._record_mode
103
104  @record_mode.setter
105  def record_mode(self, value):
106    assert value in RECORD_MODE_MAP
107    self._record_mode = value
108
109  def GetChromeTraceConfigForStartupTracing(self):
110    """Map the config to a JSON string for startup tracing.
111
112    All keys in the returned dictionary use underscore-case (e.g.
113    'record_mode'). In addition, the 'record_mode' value uses hyphen-case
114    (e.g. 'record-until-full').
115    """
116    result = {
117        RECORD_MODE_PARAM: RECORD_MODE_MAP[self._record_mode]
118    }
119    result.update(self._category_filter.GetDictForChromeTracing())
120    if self._memory_dump_config:
121      result.update(self._memory_dump_config.GetDictForChromeTracing())
122    return result
123
124  @property
125  def requires_modern_devtools_tracing_start_api(self):
126    """Returns True iff the config CANNOT be passed via the legacy DevTools API.
127
128    Legacy DevTools Tracing.start API:
129      Available since:    the introduction of the Tracing.start request.
130      Parameters:         categories (string), options (string),
131                          bufferUsageReportingInterval (number),
132                          transferMode (enum).
133      TraceConfig method: GetChromeTraceCategoriesAndOptionsStringsForDevTools()
134
135    Modern DevTools Tracing.start API:
136      Available since:    Chrome 51.0.2683.0.
137      Parameters:         traceConfig (dict),
138                          bufferUsageReportingInterval (number),
139                          transferMode (enum).
140      TraceConfig method: GetChromeTraceConfigDictForDevTools()
141    """
142    # Memory dump config cannot be passed via the 'options' string (legacy API)
143    # in the DevTools Tracing.start request.
144    return bool(self._memory_dump_config)
145
146  def GetChromeTraceConfigForDevTools(self):
147    """Map the config to a DevTools API config dictionary.
148
149    All keys in the returned dictionary use camel-case (e.g. 'recordMode').
150    In addition, the 'recordMode' value also uses camel-case (e.g.
151    'recordUntilFull'). This is to invert the camel-case ->
152    underscore/hyphen-delimited mapping performed in Chromium devtools.
153    """
154    result = self.GetChromeTraceConfigForStartupTracing()
155    if result[RECORD_MODE_PARAM]:
156      result[RECORD_MODE_PARAM] = ConvertStringToCamelCase(
157          result[RECORD_MODE_PARAM])
158    return ConvertDictKeysToCamelCaseRecursively(result)
159
160  def GetChromeTraceCategoriesAndOptionsForDevTools(self):
161    """Map the categories and options to their DevTools API counterparts."""
162    assert not self.requires_modern_devtools_tracing_start_api
163    options_parts = [RECORD_MODE_MAP[self._record_mode]]
164    return (self._category_filter.stable_filter_string,
165            ','.join(options_parts))
166
167
168class MemoryDumpConfig(object):
169  """Stores the triggers for memory dumps in ChromeTraceConfig."""
170  def __init__(self):
171    self._triggers = []
172
173  def AddTrigger(self, mode, periodic_interval_ms):
174    """Adds a new trigger to config.
175
176    Args:
177      periodic_interval_ms: Dump time period in milliseconds.
178      level_of_detail: Memory dump level of detail string.
179          Valid arguments are "background", "light" and "detailed".
180    """
181    assert mode in ['background', 'light', 'detailed']
182    assert periodic_interval_ms > 0
183    self._triggers.append({'mode': mode,
184                           'periodic_interval_ms': periodic_interval_ms})
185
186  def GetDictForChromeTracing(self):
187    """Returns the dump config as dictionary for chrome tracing."""
188    # An empty trigger list would mean no periodic memory dumps.
189    return {'memory_dump_config': {'triggers': self._triggers}}
190