• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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