| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import optparse |
| import os |
| import py_utils |
| |
| from systrace import trace_result |
| from systrace import tracing_agents |
| |
| |
| class FtraceAgentIo(object): |
| @staticmethod |
| def writeFile(path, data): |
| if FtraceAgentIo.haveWritePermissions(path): |
| with open(path, 'w') as f: |
| f.write(data) |
| else: |
| raise IOError('Cannot write to %s; did you forget sudo/root?' % path) |
| |
| @staticmethod |
| def readFile(path): |
| with open(path, 'r') as f: |
| return f.read() |
| |
| @staticmethod |
| def haveWritePermissions(path): |
| return os.access(path, os.W_OK) |
| |
| |
| FT_DIR = "/sys/kernel/debug/tracing/" |
| FT_CLOCK = FT_DIR + "trace_clock" |
| FT_BUFFER_SIZE = FT_DIR + "buffer_size_kb" |
| FT_TRACER = FT_DIR + "current_tracer" |
| FT_PRINT_TGID = FT_DIR + "options/print-tgid" |
| FT_TRACE_ON = FT_DIR + "tracing_on" |
| FT_TRACE = FT_DIR + "trace" |
| FT_TRACE_MARKER = FT_DIR + "trace_marker" |
| FT_OVERWRITE = FT_DIR + "options/overwrite" |
| |
| all_categories = { |
| "sched": { |
| "desc": "CPU Scheduling", |
| "req": ["sched/sched_switch/", "sched/sched_wakeup/"] |
| }, |
| "freq": { |
| "desc": "CPU Frequency", |
| "req": ["power/cpu_frequency/"], |
| "opt": ["power/clock_set_rate/", "clk/clk_set_rate/"] |
| }, |
| "irq": { |
| "desc": "CPU IRQS and IPIS", |
| "req": ["irq/"], |
| "opt": ["ipi/"] |
| }, |
| "workq": { |
| "desc": "Kernel workqueues", |
| "req": ["workqueue/"] |
| }, |
| "memreclaim": { |
| "desc": "Kernel Memory Reclaim", |
| "req": ["vmscan/mm_vmscan_direct_reclaim_begin/", |
| "vmscan/mm_vmscan_direct_reclaim_end/", |
| "vmscan/mm_vmscan_kswapd_wake/", |
| "vmscan/mm_vmscan_kswapd_sleep/"] |
| }, |
| "idle": { |
| "desc": "CPU Idle", |
| "req": ["power/cpu_idle/"] |
| }, |
| "regulators": { |
| "desc": "Voltage and Current Regulators", |
| "req": ["regulator/"] |
| }, |
| "disk": { |
| "desc": "Disk I/O", |
| "req": ["block/block_rq_issue/", |
| "block/block_rq_complete/"], |
| "opt": ["f2fs/f2fs_sync_file_enter/", |
| "f2fs/f2fs_sync_file_exit/", |
| "f2fs/f2fs_write_begin/", |
| "f2fs/f2fs_write_end/", |
| "ext4/ext4_da_write_begin/", |
| "ext4/ext4_da_write_end/", |
| "ext4/ext4_sync_file_enter/", |
| "ext4/ext4_sync_file_exit/"] |
| } |
| } |
| |
| |
| def try_create_agent(config): |
| if config.target != 'linux': |
| return None |
| return FtraceAgent(FtraceAgentIo) |
| |
| |
| def list_categories(_): |
| agent = FtraceAgent(FtraceAgentIo) |
| agent._print_avail_categories() |
| |
| |
| class FtraceConfig(tracing_agents.TracingConfig): |
| def __init__(self, ftrace_categories, target, trace_buf_size): |
| tracing_agents.TracingConfig.__init__(self) |
| self.ftrace_categories = ftrace_categories |
| self.target = target |
| self.trace_buf_size = trace_buf_size |
| |
| |
| def add_options(parser): |
| options = optparse.OptionGroup(parser, 'Ftrace options') |
| options.add_option('--ftrace-categories', dest='ftrace_categories', |
| help='Select ftrace categories with a comma-delimited ' |
| 'list, e.g. --ftrace-categories=cat1,cat2,cat3') |
| return options |
| |
| |
| def get_config(options): |
| return FtraceConfig(options.ftrace_categories, options.target, |
| options.trace_buf_size) |
| |
| |
| class FtraceAgent(tracing_agents.TracingAgent): |
| |
| def __init__(self, fio=FtraceAgentIo): |
| """Initialize a systrace agent. |
| |
| Args: |
| config: The command-line config. |
| categories: The trace categories to capture. |
| """ |
| super(FtraceAgent, self).__init__() |
| self._fio = fio |
| self._config = None |
| self._categories = None |
| |
| def _get_trace_buffer_size(self): |
| buffer_size = 4096 |
| if ((self._config.trace_buf_size is not None) |
| and (self._config.trace_buf_size > 0)): |
| buffer_size = self._config.trace_buf_size |
| return buffer_size |
| |
| def _fix_categories(self, categories): |
| """ |
| Applies the default category (sched) if there are no categories |
| in the list and removes unavailable categories from the list. |
| Args: |
| categories: List of categories. |
| """ |
| if not categories: |
| categories = ["sched"] |
| return [x for x in categories |
| if self._is_category_available(x)] |
| |
| @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) |
| def StartAgentTracing(self, config, timeout=None): |
| """Start tracing. |
| """ |
| self._config = config |
| categories = self._fix_categories(config.ftrace_categories) |
| self._fio.writeFile(FT_BUFFER_SIZE, |
| str(self._get_trace_buffer_size())) |
| self._fio.writeFile(FT_CLOCK, 'global') |
| self._fio.writeFile(FT_TRACER, 'nop') |
| self._fio.writeFile(FT_OVERWRITE, "0") |
| |
| # TODO: riandrews to push necessary patches for TGID option to upstream |
| # linux kernel |
| # self._fio.writeFile(FT_PRINT_TGID, '1') |
| |
| for category in categories: |
| self._category_enable(category) |
| |
| self._categories = categories # need to store list of categories to disable |
| print 'starting tracing.' |
| |
| self._fio.writeFile(FT_TRACE, '') |
| self._fio.writeFile(FT_TRACE_ON, '1') |
| return True |
| |
| @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) |
| def StopAgentTracing(self, timeout=None): |
| """Collect the result of tracing. |
| |
| This function will block while collecting the result. For sync mode, it |
| reads the data, e.g., from stdout, until it finishes. For async mode, it |
| blocks until the agent is stopped and the data is ready. |
| """ |
| self._fio.writeFile(FT_TRACE_ON, '0') |
| for category in self._categories: |
| self._category_disable(category) |
| return True |
| |
| @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT) |
| def GetResults(self, timeout=None): |
| # get the output |
| d = self._fio.readFile(FT_TRACE) |
| self._fio.writeFile(FT_BUFFER_SIZE, "1") |
| return trace_result.TraceResult('trace-data', d) |
| |
| def SupportsExplicitClockSync(self): |
| return False |
| |
| def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback): |
| # No implementation, but need to have this to support the API |
| # pylint: disable=unused-argument |
| return False |
| |
| def _is_category_available(self, category): |
| if category not in all_categories: |
| return False |
| events_dir = FT_DIR + "events/" |
| req_events = all_categories[category]["req"] |
| for event in req_events: |
| event_full_path = events_dir + event + "enable" |
| if not self._fio.haveWritePermissions(event_full_path): |
| return False |
| return True |
| |
| def _avail_categories(self): |
| ret = [] |
| for event in all_categories: |
| if self._is_category_available(event): |
| ret.append(event) |
| return ret |
| |
| def _print_avail_categories(self): |
| avail = self._avail_categories() |
| if len(avail): |
| print "tracing config:" |
| for category in self._avail_categories(): |
| desc = all_categories[category]["desc"] |
| print "{0: <16}".format(category), ": ", desc |
| else: |
| print "No tracing categories available - perhaps you need root?" |
| |
| def _category_enable_paths(self, category): |
| events_dir = FT_DIR + "events/" |
| req_events = all_categories[category]["req"] |
| for event in req_events: |
| event_full_path = events_dir + event + "enable" |
| yield event_full_path |
| if "opt" in all_categories[category]: |
| opt_events = all_categories[category]["opt"] |
| for event in opt_events: |
| event_full_path = events_dir + event + "enable" |
| if self._fio.haveWritePermissions(event_full_path): |
| yield event_full_path |
| |
| def _category_enable(self, category): |
| for path in self._category_enable_paths(category): |
| self._fio.writeFile(path, "1") |
| |
| def _category_disable(self, category): |
| for path in self._category_enable_paths(category): |
| self._fio.writeFile(path, "0") |