Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 1 | # Copyright (c) 2013 The Chromium OS 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 | |
Allen Li | 3e69447 | 2017-02-07 13:21:54 -0800 | [diff] [blame] | 5 | import logging, os, re |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 6 | |
| 7 | from autotest_lib.client.bin import utils |
| 8 | from autotest_lib.client.common_lib import error |
| 9 | |
| 10 | class KernelTrace(object): |
| 11 | """Allows access and control to Kernel tracing facilities. |
| 12 | |
| 13 | Example code snippet: |
| 14 | trace = KernelTrace(events=['mali_dvfs:mali_dvfs_set_clock']) |
| 15 | results = trace.read(regexp=r'frequency=(\d+)') |
| 16 | |
| 17 | Public methods: |
| 18 | on : Enables tracing |
| 19 | off : Disables tracing |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 20 | is_tracing : Returns Boolean of tracing status. |
| 21 | event_on : Turns event on. Returns boolean of success |
| 22 | event_off : Turns event off. Returns boolean of success |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 23 | flush : Flushes trace buffer |
| 24 | read : Reads trace buffer returns list of |
| 25 | - tuples if regexp provided |
| 26 | - else matching string |
| 27 | uptime_secs : Returns float of current uptime. |
| 28 | |
| 29 | Private functions: |
| 30 | _onoff : Disable/enable tracing |
| 31 | _onoff_event : Disable/enable events |
| 32 | |
| 33 | Private attributes: |
| 34 | _buffer : list to hold parsed results from trace buffer |
| 35 | _buffer_ptr : integer pointing to last byte read |
| 36 | |
| 37 | TODO(tbroch): List of potential enhancements |
| 38 | - currently only supports trace events. Add other tracers. |
| 39 | """ |
| 40 | _TRACE_ROOT = '/sys/kernel/debug/tracing' |
Todd Broch | f98882c | 2019-04-16 21:47:33 -0700 | [diff] [blame] | 41 | _TRACE_ON_PATH = os.path.join(_TRACE_ROOT, 'tracing_on') |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 42 | |
| 43 | def __init__(self, flush=True, events=None, on=True): |
| 44 | """Constructor for KernelTrace class""" |
| 45 | self._buffer = [] |
| 46 | self._buffer_ptr = 0 |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 47 | self._events = [] |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 48 | self._on = on |
| 49 | |
| 50 | if flush: |
| 51 | self.flush() |
| 52 | for event in events: |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 53 | if self.event_on(event): |
| 54 | self._events.append(event) |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 55 | if on: |
| 56 | self.on() |
| 57 | |
| 58 | |
| 59 | def __del__(self, flush=True, events=None, on=True): |
| 60 | """Deconstructor for KernelTrace class""" |
| 61 | for event in self._events: |
| 62 | self.event_off(event) |
| 63 | if self._on: |
| 64 | self.off() |
| 65 | |
| 66 | |
| 67 | def _onoff(self, val): |
Todd Broch | f98882c | 2019-04-16 21:47:33 -0700 | [diff] [blame] | 68 | """Turn tracing on or off. |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 69 | |
| 70 | Arguments: |
| 71 | val: integer, 1 for on, 0 for off |
| 72 | |
| 73 | Raises: |
Todd Broch | f98882c | 2019-04-16 21:47:33 -0700 | [diff] [blame] | 74 | error.TestFail: If unable to turn tracing on or off. |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 75 | """ |
Todd Broch | f98882c | 2019-04-16 21:47:33 -0700 | [diff] [blame] | 76 | utils.write_one_line(self._TRACE_ON_PATH, val) |
| 77 | result = int(utils.read_one_line(self._TRACE_ON_PATH).strip()) |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 78 | if not result == val: |
| 79 | raise error.TestFail("Unable to %sable tracing" % |
| 80 | 'en' if val == 1 else 'dis') |
| 81 | |
| 82 | |
| 83 | def on(self): |
| 84 | """Enable tracing.""" |
| 85 | return self._onoff(1) |
| 86 | |
| 87 | |
| 88 | def off(self): |
| 89 | """Disable tracing.""" |
| 90 | self._onoff(0) |
| 91 | |
| 92 | |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 93 | def is_tracing(self): |
| 94 | """Is tracing on? |
| 95 | |
| 96 | Returns: |
| 97 | True if tracing enabled and at least one event is enabled. |
| 98 | """ |
Todd Broch | f98882c | 2019-04-16 21:47:33 -0700 | [diff] [blame] | 99 | result = int(utils.read_one_line(self._TRACE_ON_PATH).strip()) |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 100 | if result == 1 and len(self._events) > 0: |
| 101 | return True |
| 102 | return False |
| 103 | |
| 104 | |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 105 | def _event_onoff(self, event, val): |
| 106 | """Enable/Disable tracing event. |
| 107 | |
| 108 | TODO(tbroch) Consider allowing wild card enabling of trace events via |
| 109 | /sys/kernel/debug/tracing/set_event although it makes filling buffer |
| 110 | really easy |
| 111 | |
| 112 | Arguments: |
| 113 | event: list of events. |
| 114 | See kernel(Documentation/trace/events.txt) for formatting. |
| 115 | val: integer, 1 for on, 0 for off |
| 116 | |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 117 | Returns: |
| 118 | True if success, false otherwise |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 119 | """ |
| 120 | logging.debug("event_onoff: event:%s val:%d", event, val) |
| 121 | event_path = event.replace(':', '/') |
| 122 | fname = os.path.join(self._TRACE_ROOT, 'events', event_path, 'enable') |
| 123 | |
| 124 | if not os.path.exists(fname): |
Ilja H. Friedel | 04be2bd | 2014-05-07 21:29:59 -0700 | [diff] [blame] | 125 | logging.warning("Unable to locate tracing event %s", fname) |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 126 | return False |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 127 | utils.write_one_line(fname, val) |
| 128 | |
| 129 | fname = os.path.join(self._TRACE_ROOT, "set_event") |
| 130 | found = False |
| 131 | with open(fname) as fd: |
| 132 | for ln in fd.readlines(): |
| 133 | logging.debug("set_event ln:%s", ln) |
| 134 | if re.findall(event, ln): |
| 135 | found = True |
| 136 | break |
| 137 | |
| 138 | if val == 1 and not found: |
Ilja H. Friedel | 04be2bd | 2014-05-07 21:29:59 -0700 | [diff] [blame] | 139 | logging.warning("Event %s not enabled", event) |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 140 | return False |
| 141 | |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 142 | if val == 0 and found: |
Ilja H. Friedel | 04be2bd | 2014-05-07 21:29:59 -0700 | [diff] [blame] | 143 | logging.warning("Event %s not disabled", event) |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 144 | return False |
| 145 | |
| 146 | return True |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 147 | |
| 148 | |
| 149 | def event_on(self, event): |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 150 | return self._event_onoff(event, 1) |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 151 | |
| 152 | |
| 153 | def event_off(self, event): |
Todd Broch | e4deb72 | 2013-09-10 11:37:57 -0700 | [diff] [blame] | 154 | return self._event_onoff(event, 0) |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 155 | |
| 156 | |
| 157 | def flush(self): |
| 158 | """Flush trace buffer. |
| 159 | |
| 160 | Raises: |
| 161 | error.TestFail: If unable to flush |
| 162 | """ |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 163 | self.off() |
| 164 | fname = os.path.join(self._TRACE_ROOT, 'free_buffer') |
| 165 | utils.write_one_line(fname, 1) |
| 166 | self._buffer_ptr = 0 |
| 167 | |
| 168 | fname = os.path.join(self._TRACE_ROOT, 'buffer_size_kb') |
| 169 | result = utils.read_one_line(fname).strip() |
Derek Beckett | 8affba0 | 2020-10-20 14:24:18 -0700 | [diff] [blame] | 170 | if result == '0': |
Todd Broch | 407e829 | 2013-09-05 17:58:33 -0700 | [diff] [blame] | 171 | return True |
| 172 | return False |
| 173 | |
| 174 | |
| 175 | def read(self, regexp=None): |
| 176 | fname = os.path.join(self._TRACE_ROOT, 'trace') |
| 177 | fd = open(fname) |
| 178 | fd.seek(self._buffer_ptr) |
| 179 | for ln in fd.readlines(): |
| 180 | if regexp is None: |
| 181 | self._buffer.append(ln) |
| 182 | continue |
| 183 | results = re.findall(regexp, ln) |
| 184 | if results: |
| 185 | logging.debug(ln) |
| 186 | self._buffer.append(results[0]) |
| 187 | self._buffer_ptr = fd.tell() |
| 188 | fd.close() |
| 189 | return self._buffer |
| 190 | |
| 191 | |
| 192 | @staticmethod |
| 193 | def uptime_secs(): |
| 194 | results = utils.read_one_line("/proc/uptime") |
| 195 | return float(results.split()[0]) |