Skip to content

Commit

Permalink
ftrace: Support trace-cmd record mode
Browse files Browse the repository at this point in the history
The current implementation of the FtraceCollector only supports using
ftrace in its start-stop mode which keeps the traces in memory and then
writes to file at the end. Trace-cmd also supports a different mode -
record which writes to file periodically meaning that it allows
recording traces that would exceed the 'start' mode buffer size in
exchange for added noise.

This commit adds support for selecting which mode trace-cmd should be
run in. It adds a new collector parameter - 'trace_cmd_mode' which is
used to distinguish between the two approaches.

While at it, it also cleans up the collector in some places, e.g. the
teardown() method.

Signed-off-by: Kajetan Puchalski <[email protected]>
  • Loading branch information
mrkajetanp committed Aug 23, 2023
1 parent 6b09571 commit 6048492
Showing 1 changed file with 51 additions and 24 deletions.
75 changes: 51 additions & 24 deletions devlib/collector/ftrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import re
import subprocess
import sys
import signal
import contextlib
from shlex import quote

Expand Down Expand Up @@ -51,6 +52,7 @@
CPU_RE = re.compile(r' Function \(CPU([0-9]+)\)')
STATS_RE = re.compile(r'([^ ]*) +([0-9]+) +([0-9.]+) us +([0-9.]+) us +([0-9.]+) us')


class FtraceCollector(CollectorBase):

# pylint: disable=too-many-locals,too-many-branches,too-many-statements
Expand All @@ -71,6 +73,7 @@ def __init__(self, target,
report_on_target=False,
trace_clock='local',
saved_cmdlines_nr=4096,
trace_cmd_mode='start',
):
super(FtraceCollector, self).__init__(target)
self.events = events if events is not None else DEFAULT_EVENTS
Expand All @@ -85,6 +88,7 @@ def __init__(self, target,
self.autoview = autoview
self.strict = strict
self.report_on_target = report_on_target
self.trace_cmd_mode = trace_cmd_mode
self.target_output_file = target.path.join(self.target.working_directory, OUTPUT_TRACE_FILE)
text_file_name = target.path.splitext(OUTPUT_TRACE_FILE)[0] + '.txt'
self.target_text_file = target.path.join(self.target.working_directory, text_file_name)
Expand All @@ -98,6 +102,8 @@ def __init__(self, target,
self.trace_clock = trace_clock
self.saved_cmdlines_nr = saved_cmdlines_nr
self._reset_needed = True
self.bg_cmd = None
self.tmp_output_dir = None

# pylint: disable=bad-whitespace
# Setup tracing paths
Expand Down Expand Up @@ -276,18 +282,35 @@ async def start(self):
with contextlib.suppress(TargetStableError):
self.target.write_value('/proc/sys/kernel/kptr_restrict', 0)

self.target.execute(
'{} start -B devlib {buffer_size} {cmdlines_size} {clock} {events} {tracer} {functions}'.format(
self.target_binary,
events=self.event_string,
tracer=tracer_string,
functions=tracecmd_functions,
buffer_size='-b {}'.format(self.buffer_size) if self.buffer_size is not None else '',
clock='-C {}'.format(self.trace_clock) if self.trace_clock else '',
cmdlines_size='--cmdlines-size {}'.format(self.saved_cmdlines_nr) if self.saved_cmdlines_nr is not None else '',
),
as_root=True,
trace_cmd = '{} {command} -B devlib {buffer_size} {cmdlines_size} {clock} {events} {tracer} {functions}'.format(
self.target_binary,
command=self.trace_cmd_mode,
events=self.event_string,
tracer=tracer_string,
functions=tracecmd_functions,
buffer_size='-b {}'.format(self.buffer_size) if self.buffer_size is not None else '',
clock='-C {}'.format(self.trace_clock) if self.trace_clock else '',
cmdlines_size='--cmdlines-size {}'.format(self.saved_cmdlines_nr) if self.saved_cmdlines_nr is not None else '',
)

if self.trace_cmd_mode == 'record':
# trace-cmd record needs to be run from a writeable directory
self.tmp_output_dir = self.target.execute("{} mktemp -p {output_dir} -d".format(
self.target.busybox,
output_dir=self.target.working_directory,
)).strip()
trace_cmd = "cd {} && {command} {output}".format(
self.tmp_output_dir,
command=trace_cmd,
output="-o {}".format(self.target_output_file)
)
if self.bg_cmd is None:
self.bg_cmd = self.target.background(trace_cmd, as_root=True)
else:
raise TargetStableError('ftrace collector is not re-entrant')
else:
self.target.execute(trace_cmd, as_root=True)

if self.automark:
self.mark_start()
if 'cpufreq' in self.target.modules:
Expand All @@ -310,7 +333,6 @@ async def start(self):
as_root=True),
)


def stop(self):
# Disable kernel function profiling
if self.functions and self.tracer is None:
Expand All @@ -322,8 +344,11 @@ def stop(self):
self.stop_time = time.time()
if self.automark:
self.mark_stop()
self.target.execute('{} stop -B devlib'.format(self.target_binary),
timeout=TIMEOUT, as_root=True)
if self.trace_cmd_mode == 'record':
self.bg_cmd.send_signal(signal.SIGINT)
self.bg_cmd = None
else:
self.target.execute('{} stop -B devlib'.format(self.target_binary), timeout=TIMEOUT, as_root=True)
self._reset_needed = True

def set_output(self, output_path):
Expand All @@ -334,15 +359,13 @@ def set_output(self, output_path):
def get_data(self):
if self.output_path is None:
raise RuntimeError("Output path was not set.")
self.target.execute('{0} extract -B devlib -o {1}; chmod 666 {1}'.format(self.target_binary,
self.target_output_file),
timeout=TIMEOUT, as_root=True)

# The size of trace.dat will depend on how long trace-cmd was running.
# Therefore timout for the pull command must also be adjusted
# accordingly.
pull_timeout = 10 * (self.stop_time - self.start_time)
self.target.pull(self.target_output_file, self.output_path, timeout=pull_timeout)

if self.trace_cmd_mode == 'start':
self.target.execute('{0} extract -B devlib -o {1}; chmod 666 {1}'.format(
self.target_binary, self.target_output_file
), timeout=TIMEOUT, as_root=True)

self.target.pull(self.target_output_file, self.output_path)
output = CollectorOutput()
if not os.path.isfile(self.output_path):
self.logger.warning('Binary trace not pulled from device.')
Expand Down Expand Up @@ -440,7 +463,11 @@ def view(self, binfile):
check_output('{} {}'.format(self.kernelshark, binfile), shell=True)

def teardown(self):
self.target.remove(self.target.path.join(self.target.working_directory, OUTPUT_TRACE_FILE))
self.target.remove(self.target_output_file)
if self.report_on_target:
self.target.remove(self.target_text_file)
if self.tmp_output_dir is not None:
self.target.remove(self.tmp_output_dir)

def mark_start(self):
self.target.write_value(self.marker_file, TRACE_MARKER_START, verify=False)
Expand Down

0 comments on commit 6048492

Please sign in to comment.