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, fix_threads,
104               fix_tgids, fix_circular):
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    self.fix_threads = fix_threads
110    self.fix_tgids = fix_tgids
111    self.fix_circular = fix_circular
112
113
114def add_options(parser):
115  options = optparse.OptionGroup(parser, 'Ftrace options')
116  options.add_option('--ftrace-categories', dest='ftrace_categories',
117                     help='Select ftrace categories with a comma-delimited '
118                     'list, e.g. --ftrace-categories=cat1,cat2,cat3')
119  return options
120
121
122def get_config(options):
123  return FtraceConfig(options.ftrace_categories, options.target,
124                      options.trace_buf_size, options.fix_threads,
125                      options.fix_tgids, options.fix_circular)
126
127
128class FtraceAgent(tracing_agents.TracingAgent):
129
130  def __init__(self, fio=FtraceAgentIo):
131    """Initialize a systrace agent.
132
133    Args:
134      config: The command-line config.
135      categories: The trace categories to capture.
136    """
137    super(FtraceAgent, self).__init__()
138    self._fio = fio
139    self._config = None
140    self._categories = None
141
142  def _get_trace_buffer_size(self):
143    buffer_size = 4096
144    if ((self._config.trace_buf_size is not None)
145        and (self._config.trace_buf_size > 0)):
146      buffer_size = self._config.trace_buf_size
147    return buffer_size
148
149  def _fix_categories(self, categories):
150    """
151    Applies the default category (sched) if there are no categories
152    in the list and removes unavailable categories from the list.
153    Args:
154        categories: List of categories.
155    """
156    if not categories:
157      categories = ["sched"]
158    return [x for x in categories
159            if self._is_category_available(x)]
160
161  @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
162  def StartAgentTracing(self, config, timeout=None):
163    """Start tracing.
164    """
165    self._config = config
166    categories = self._fix_categories(config.ftrace_categories)
167    self._fio.writeFile(FT_BUFFER_SIZE,
168                        str(self._get_trace_buffer_size()))
169    self._fio.writeFile(FT_CLOCK, 'global')
170    self._fio.writeFile(FT_TRACER, 'nop')
171    self._fio.writeFile(FT_OVERWRITE, "0")
172
173    # TODO: riandrews to push necessary patches for TGID option to upstream
174    # linux kernel
175    # self._fio.writeFile(FT_PRINT_TGID, '1')
176
177    for category in categories:
178      self._category_enable(category)
179
180    self._categories = categories # need to store list of categories to disable
181    print 'starting tracing.'
182
183    self._fio.writeFile(FT_TRACE, '')
184    self._fio.writeFile(FT_TRACE_ON, '1')
185    return True
186
187  @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
188  def StopAgentTracing(self, timeout=None):
189    """Collect the result of tracing.
190
191    This function will block while collecting the result. For sync mode, it
192    reads the data, e.g., from stdout, until it finishes. For async mode, it
193    blocks until the agent is stopped and the data is ready.
194    """
195    self._fio.writeFile(FT_TRACE_ON, '0')
196    for category in self._categories:
197      self._category_disable(category)
198    if self._config.fix_threads:
199      print "WARN: thread name fixing is not yet supported."
200    if self._config.fix_tgids:
201      print "WARN: tgid fixing is not yet supported."
202    if self._config.fix_circular:
203      print "WARN: circular buffer fixups are not yet supported."
204    return True
205
206  @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT)
207  def GetResults(self, timeout=None):
208    # get the output
209    d = self._fio.readFile(FT_TRACE)
210    self._fio.writeFile(FT_BUFFER_SIZE, "1")
211    return trace_result.TraceResult('trace-data', d)
212
213  def SupportsExplicitClockSync(self):
214    return False
215
216  def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback):
217    # No implementation, but need to have this to support the API
218    # pylint: disable=unused-argument
219    return False
220
221  def _is_category_available(self, category):
222    if category not in all_categories:
223      return False
224    events_dir = FT_DIR + "events/"
225    req_events = all_categories[category]["req"]
226    for event in req_events:
227      event_full_path = events_dir + event + "enable"
228      if not self._fio.haveWritePermissions(event_full_path):
229        return False
230    return True
231
232  def _avail_categories(self):
233    ret = []
234    for event in all_categories:
235      if self._is_category_available(event):
236        ret.append(event)
237    return ret
238
239  def _print_avail_categories(self):
240    avail = self._avail_categories()
241    if len(avail):
242      print "tracing config:"
243      for category in self._avail_categories():
244        desc = all_categories[category]["desc"]
245        print "{0: <16}".format(category), ": ", desc
246    else:
247      print "No tracing categories available - perhaps you need root?"
248
249  def _category_enable_paths(self, category):
250    events_dir = FT_DIR + "events/"
251    req_events = all_categories[category]["req"]
252    for event in req_events:
253      event_full_path = events_dir + event + "enable"
254      yield event_full_path
255    if "opt" in all_categories[category]:
256      opt_events = all_categories[category]["opt"]
257      for event in opt_events:
258        event_full_path = events_dir + event + "enable"
259        if self._fio.haveWritePermissions(event_full_path):
260          yield event_full_path
261
262  def _category_enable(self, category):
263    for path in self._category_enable_paths(category):
264      self._fio.writeFile(path, "1")
265
266  def _category_disable(self, category):
267    for path in self._category_enable_paths(category):
268      self._fio.writeFile(path, "0")
269