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