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 optparse 6import os 7import py_utils 8 9from systrace import trace_result 10from systrace import tracing_agents 11 12 13class FtraceAgentIo(object): 14 @staticmethod 15 def writeFile(path, data): 16 if FtraceAgentIo.haveWritePermissions(path): 17 with open(path, 'w') as f: 18 f.write(data) 19 else: 20 raise IOError('Cannot write to %s; did you forget sudo/root?' % path) 21 22 @staticmethod 23 def readFile(path): 24 with open(path, 'r') as f: 25 return f.read() 26 27 @staticmethod 28 def haveWritePermissions(path): 29 return os.access(path, os.W_OK) 30 31 32FT_DIR = "/sys/kernel/debug/tracing/" 33FT_CLOCK = FT_DIR + "trace_clock" 34FT_BUFFER_SIZE = FT_DIR + "buffer_size_kb" 35FT_TRACER = FT_DIR + "current_tracer" 36FT_PRINT_TGID = FT_DIR + "options/print-tgid" 37FT_TRACE_ON = FT_DIR + "tracing_on" 38FT_TRACE = FT_DIR + "trace" 39FT_TRACE_MARKER = FT_DIR + "trace_marker" 40FT_OVERWRITE = FT_DIR + "options/overwrite" 41 42all_categories = { 43 "sched": { 44 "desc": "CPU Scheduling", 45 "req": ["sched/sched_switch/", "sched/sched_wakeup/"] 46 }, 47 "freq": { 48 "desc": "CPU Frequency", 49 "req": ["power/cpu_frequency/"], 50 "opt": ["power/clock_set_rate/", "clk/clk_set_rate/"] 51 }, 52 "irq": { 53 "desc": "CPU IRQS and IPIS", 54 "req": ["irq/"], 55 "opt": ["ipi/"] 56 }, 57 "workq": { 58 "desc": "Kernel workqueues", 59 "req": ["workqueue/"] 60 }, 61 "memreclaim": { 62 "desc": "Kernel Memory Reclaim", 63 "req": ["vmscan/mm_vmscan_direct_reclaim_begin/", 64 "vmscan/mm_vmscan_direct_reclaim_end/", 65 "vmscan/mm_vmscan_kswapd_wake/", 66 "vmscan/mm_vmscan_kswapd_sleep/"] 67 }, 68 "idle": { 69 "desc": "CPU Idle", 70 "req": ["power/cpu_idle/"] 71 }, 72 "regulators": { 73 "desc": "Voltage and Current Regulators", 74 "req": ["regulator/"] 75 }, 76 "disk": { 77 "desc": "Disk I/O", 78 "req": ["block/block_rq_issue/", 79 "block/block_rq_complete/"], 80 "opt": ["f2fs/f2fs_sync_file_enter/", 81 "f2fs/f2fs_sync_file_exit/", 82 "f2fs/f2fs_write_begin/", 83 "f2fs/f2fs_write_end/", 84 "ext4/ext4_da_write_begin/", 85 "ext4/ext4_da_write_end/", 86 "ext4/ext4_sync_file_enter/", 87 "ext4/ext4_sync_file_exit/"] 88 } 89} 90 91 92def try_create_agent(config): 93 if config.target != 'linux': 94 return None 95 return FtraceAgent(FtraceAgentIo) 96 97 98def list_categories(_): 99 agent = FtraceAgent(FtraceAgentIo) 100 agent._print_avail_categories() 101 102 103class FtraceConfig(tracing_agents.TracingConfig): 104 def __init__(self, ftrace_categories, target, trace_buf_size): 105 tracing_agents.TracingConfig.__init__(self) 106 self.ftrace_categories = ftrace_categories 107 self.target = target 108 self.trace_buf_size = trace_buf_size 109 110 111def add_options(parser): 112 options = optparse.OptionGroup(parser, 'Ftrace options') 113 options.add_option('--ftrace-categories', dest='ftrace_categories', 114 help='Select ftrace categories with a comma-delimited ' 115 'list, e.g. --ftrace-categories=cat1,cat2,cat3') 116 return options 117 118 119def get_config(options): 120 return FtraceConfig(options.ftrace_categories, options.target, 121 options.trace_buf_size) 122 123 124class FtraceAgent(tracing_agents.TracingAgent): 125 126 def __init__(self, fio=FtraceAgentIo): 127 """Initialize a systrace agent. 128 129 Args: 130 config: The command-line config. 131 categories: The trace categories to capture. 132 """ 133 super(FtraceAgent, self).__init__() 134 self._fio = fio 135 self._config = None 136 self._categories = None 137 138 def _get_trace_buffer_size(self): 139 buffer_size = 4096 140 if ((self._config.trace_buf_size is not None) 141 and (self._config.trace_buf_size > 0)): 142 buffer_size = self._config.trace_buf_size 143 return buffer_size 144 145 def _fix_categories(self, categories): 146 """ 147 Applies the default category (sched) if there are no categories 148 in the list and removes unavailable categories from the list. 149 Args: 150 categories: List of categories. 151 """ 152 if not categories: 153 categories = ["sched"] 154 return [x for x in categories 155 if self._is_category_available(x)] 156 157 @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) 158 def StartAgentTracing(self, config, timeout=None): 159 """Start tracing. 160 """ 161 self._config = config 162 categories = self._fix_categories(config.ftrace_categories) 163 self._fio.writeFile(FT_BUFFER_SIZE, 164 str(self._get_trace_buffer_size())) 165 self._fio.writeFile(FT_CLOCK, 'global') 166 self._fio.writeFile(FT_TRACER, 'nop') 167 self._fio.writeFile(FT_OVERWRITE, "0") 168 169 # TODO: riandrews to push necessary patches for TGID option to upstream 170 # linux kernel 171 # self._fio.writeFile(FT_PRINT_TGID, '1') 172 173 for category in categories: 174 self._category_enable(category) 175 176 self._categories = categories # need to store list of categories to disable 177 print 'starting tracing.' 178 179 self._fio.writeFile(FT_TRACE, '') 180 self._fio.writeFile(FT_TRACE_ON, '1') 181 return True 182 183 @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) 184 def StopAgentTracing(self, timeout=None): 185 """Collect the result of tracing. 186 187 This function will block while collecting the result. For sync mode, it 188 reads the data, e.g., from stdout, until it finishes. For async mode, it 189 blocks until the agent is stopped and the data is ready. 190 """ 191 self._fio.writeFile(FT_TRACE_ON, '0') 192 for category in self._categories: 193 self._category_disable(category) 194 return True 195 196 @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT) 197 def GetResults(self, timeout=None): 198 # get the output 199 d = self._fio.readFile(FT_TRACE) 200 self._fio.writeFile(FT_BUFFER_SIZE, "1") 201 return trace_result.TraceResult('trace-data', d) 202 203 def SupportsExplicitClockSync(self): 204 return False 205 206 def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback): 207 # No implementation, but need to have this to support the API 208 # pylint: disable=unused-argument 209 return False 210 211 def _is_category_available(self, category): 212 if category not in all_categories: 213 return False 214 events_dir = FT_DIR + "events/" 215 req_events = all_categories[category]["req"] 216 for event in req_events: 217 event_full_path = events_dir + event + "enable" 218 if not self._fio.haveWritePermissions(event_full_path): 219 return False 220 return True 221 222 def _avail_categories(self): 223 ret = [] 224 for event in all_categories: 225 if self._is_category_available(event): 226 ret.append(event) 227 return ret 228 229 def _print_avail_categories(self): 230 avail = self._avail_categories() 231 if len(avail): 232 print "tracing config:" 233 for category in self._avail_categories(): 234 desc = all_categories[category]["desc"] 235 print "{0: <16}".format(category), ": ", desc 236 else: 237 print "No tracing categories available - perhaps you need root?" 238 239 def _category_enable_paths(self, category): 240 events_dir = FT_DIR + "events/" 241 req_events = all_categories[category]["req"] 242 for event in req_events: 243 event_full_path = events_dir + event + "enable" 244 yield event_full_path 245 if "opt" in all_categories[category]: 246 opt_events = all_categories[category]["opt"] 247 for event in opt_events: 248 event_full_path = events_dir + event + "enable" 249 if self._fio.haveWritePermissions(event_full_path): 250 yield event_full_path 251 252 def _category_enable(self, category): 253 for path in self._category_enable_paths(category): 254 self._fio.writeFile(path, "1") 255 256 def _category_disable(self, category): 257 for path in self._category_enable_paths(category): 258 self._fio.writeFile(path, "0") 259