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