RTLA patches for v7.0

- Remove unused function declarations
 
   Some functions were removed in recent code consolidation 6.18, but
   their prototypes were not removed from headers. Remove them.
 
 - Set stop threshold after enabling instances
 
   Prefer recording samples without stopping on them on the start of
   tracing to stopping on samples that are never recorded. This fixes
   flakiness of some RTLA tests and unifies behavior of sample collection
   between tracefs mode and BPF mode.
 
 - Consolidate usage help message implementation
 
   RTLA tools (osnoise-top, osnoise-hist, timerlat-top, timerlat-hist)
   each implement usage help individually. Move common logic between
   them into a new function to reduce code duplication.
 
 - Add BPF actions feature
 
   Add option --bpf-action to attach a BPF program (passed as filename of
   its ELF representation) to be executed via BPF tail call at latency
   threshold.
 
 - Consolidate command line option parsing
 
   Each RTLA tool implements the parsing of command line options
   individually. Now that we have a common structure for parameters,
   unify the parsing of those options common among all four tools into
   one function.
 
 - De-duplicate cgroup common code
 
   Two functions in utils.c, setting cgroup for comm and setting cgroup
   for pid, duplicate code for constructing the cgroup path. Extract it
   to a new helper function.
 
 - String and error handling fixes and cleanups
 
   There were several instances of unsafe string and error handling that
   could cause invalid memory access; fix them. Also, remove a few unused
   headers, update .gitignore, and deduplicate code.
 -----BEGIN PGP SIGNATURE-----
 
 iIoEABYKADIWIQRRSw7ePDh/lE+zeZMp5XQQmuv6qgUCaY3l2RQccm9zdGVkdEBn
 b29kbWlzLm9yZwAKCRAp5XQQmuv6qrWcAP4lsfIbNhfvmc9htDON/GlCu9FK8PTT
 hmAsO7MeHh29OwD+OS7RZzRza+3G+B4aTcqUXQVfAvx9qenS591LcktQSAo=
 =VUW2
 -----END PGP SIGNATURE-----

Merge tag 'trace-rtla-v7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace

Pull RTLA updates from Steven Rostedt:

 - Remove unused function declarations

   Some functions were removed in recent code consolidation 6.18, but
   their prototypes were not removed from headers. Remove them.

 - Set stop threshold after enabling instances

   Prefer recording samples without stopping on them on the start of
   tracing to stopping on samples that are never recorded. This fixes
   flakiness of some RTLA tests and unifies behavior of sample
   collection between tracefs mode and BPF mode.

 - Consolidate usage help message implementation

   RTLA tools (osnoise-top, osnoise-hist, timerlat-top, timerlat-hist)
   each implement usage help individually. Move common logic between
   them into a new function to reduce code duplication.

 - Add BPF actions feature

   Add option --bpf-action to attach a BPF program (passed as filename
   of its ELF representation) to be executed via BPF tail call at
   latency threshold.

 - Consolidate command line option parsing

   Each RTLA tool implements the parsing of command line options
   individually. Now that we have a common structure for parameters,
   unify the parsing of those options common among all four tools into
   one function.

 - De-duplicate cgroup common code

   Two functions in utils.c, setting cgroup for comm and setting cgroup
   for pid, duplicate code for constructing the cgroup path. Extract it
   to a new helper function.

 - String and error handling fixes and cleanups

   There were several instances of unsafe string and error handling that
   could cause invalid memory access; fix them. Also, remove a few
   unused headers, update .gitignore, and deduplicate code.

* tag 'trace-rtla-v7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (30 commits)
  rtla: Fix parse_cpu_set() bug introduced by strtoi()
  rtla: Fix parse_cpu_set() return value documentation
  rtla: Ensure null termination after read operations in utils.c
  rtla: Make stop_tracing variable volatile
  rtla: Add generated output files to gitignore
  rtla: Fix NULL pointer dereference in actions_parse
  rtla: Remove unused headers
  rtla: Remove redundant memset after calloc
  rtla: Use standard exit codes for result enum
  rtla: Replace atoi() with a robust strtoi()
  rtla: Introduce for_each_action() helper
  tools/rtla: Deduplicate cgroup path opening code
  tools/rtla: Consolidate -H/--house-keeping option parsing
  tools/rtla: Consolidate -P/--priority option parsing
  tools/rtla: Consolidate -e/--event option parsing
  tools/rtla: Consolidate -d/--duration option parsing
  tools/rtla: Consolidate -D/--debug option parsing
  tools/rtla: Consolidate -C/--cgroup option parsing
  tools/rtla: Consolidate -c/--cpus option parsing
  tools/rtla: Add common_parse_options()
  ...
This commit is contained in:
Linus Torvalds 2026-02-12 14:31:02 -08:00
commit 582a1ef360
26 changed files with 507 additions and 356 deletions

View file

@ -64,4 +64,22 @@
Set timerlat to run without workload, waiting for the user to dispatch a per-cpu
task that waits for a new period on the tracing/osnoise/per_cpu/cpu$ID/timerlat_fd.
See linux/tools/rtla/sample/timerlat_load.py for an example of user-load code.
See linux/tools/rtla/example/timerlat_load.py for an example of user-load code.
**--bpf-action** *bpf-program*
Loads a BPF program from an ELF file and executes it when a latency threshold is exceeded.
The BPF program must be a valid ELF file loadable with libbpf. The program must contain
a function named ``action_handler``, stored in an ELF section with the ``tp_`` prefix.
The prefix is used by libbpf to set BPF program type to BPF_PROG_TYPE_TRACEPOINT.
The program receives a ``struct trace_event_raw_timerlat_sample`` parameter
containing timerlat sample data.
An example is provided in ``tools/tracing/rtla/example/timerlat_bpf_action.c``.
This example demonstrates how to create a BPF program that prints latency information using
bpf_trace_printk() when a threshold is exceeded.
**Note**: BPF actions require BPF support to be available. If BPF is not available
or disabled, the tool falls back to tracefs mode and BPF actions are not supported.

View file

@ -5,3 +5,7 @@ fixdep
feature
FEATURE-DUMP
*.skel.h
custom_filename.txt
osnoise_irq_noise_hist.txt
osnoise_trace.txt
timerlat_trace.txt

View file

@ -73,9 +73,21 @@ src/timerlat.bpf.o: src/timerlat.bpf.c
src/timerlat.skel.h: src/timerlat.bpf.o
$(QUIET_GENSKEL)$(SYSTEM_BPFTOOL) gen skeleton $< > $@
example/timerlat_bpf_action.o: example/timerlat_bpf_action.c
$(QUIET_CLANG)$(CLANG) -g -O2 -target bpf -c $(filter %.c,$^) -o $@
tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
$(QUIET_CLANG)$(CLANG) -g -O2 -target bpf -c $(filter %.c,$^) -o $@
else
src/timerlat.skel.h:
$(Q)echo '/* BPF skeleton is disabled */' > src/timerlat.skel.h
example/timerlat_bpf_action.o: example/timerlat_bpf_action.c
$(Q)echo "BPF skeleton support is disabled, skipping example/timerlat_bpf_action.o"
tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
$(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o"
endif
$(RTLA): $(RTLA_IN)
@ -96,7 +108,8 @@ clean: doc_clean fixdep-clean
$(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
$(Q)rm -f rtla rtla-static fixdep FEATURE-DUMP rtla-*
$(Q)rm -rf feature
$(Q)rm -f src/timerlat.bpf.o src/timerlat.skel.h
check: $(RTLA)
RTLA=$(RTLA) prove -o -f tests/
$(Q)rm -f src/timerlat.bpf.o src/timerlat.skel.h example/timerlat_bpf_action.o
check: $(RTLA) tests/bpf/bpf_action_map.o
RTLA=$(RTLA) BPFTOOL=$(SYSTEM_BPFTOOL) prove -o -f -v tests/
examples: example/timerlat_bpf_action.o
.PHONY: FORCE clean check

View file

@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_tracing.h>
char LICENSE[] SEC("license") = "GPL";
struct trace_event_raw_timerlat_sample {
unsigned long long timer_latency;
} __attribute__((preserve_access_index));
SEC("tp/timerlat_action")
int action_handler(struct trace_event_raw_timerlat_sample *tp_args)
{
bpf_printk("Latency: %lld\n", tp_args->timer_latency);
return 0;
}

View file

@ -19,8 +19,6 @@ actions_init(struct actions *self)
self->len = 0;
self->continue_flag = false;
memset(&self->present, 0, sizeof(self->present));
/* This has to be set by the user */
self->trace_output_inst = NULL;
}
@ -32,7 +30,9 @@ void
actions_destroy(struct actions *self)
{
/* Free any action-specific data */
for (struct action *action = self->list; action < self->list + self->len; action++) {
struct action *action;
for_each_action(self, action) {
if (action->type == ACTION_SHELL)
free(action->command);
if (action->type == ACTION_TRACE_OUTPUT)
@ -141,6 +141,8 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn)
strcpy(trigger_c, trigger);
token = strtok(trigger_c, ",");
if (!token)
return -1;
if (strcmp(token, "trace") == 0)
type = ACTION_TRACE_OUTPUT;
@ -179,12 +181,13 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn)
/* Takes two arguments, num (signal) and pid */
while (token != NULL) {
if (strlen(token) > 4 && strncmp(token, "num=", 4) == 0) {
signal = atoi(token + 4);
if (strtoi(token + 4, &signal))
return -1;
} else if (strlen(token) > 4 && strncmp(token, "pid=", 4) == 0) {
if (strncmp(token + 4, "parent", 7) == 0)
pid = -1;
else
pid = atoi(token + 4);
else if (strtoi(token + 4, &pid))
return -1;
} else {
/* Invalid argument */
return -1;
@ -223,7 +226,7 @@ actions_perform(struct actions *self)
int pid, retval;
const struct action *action;
for (action = self->list; action < self->list + self->len; action++) {
for_each_action(self, action) {
switch (action->type) {
case ACTION_TRACE_OUTPUT:
retval = save_trace_to_file(self->trace_output_inst, action->trace_output);

View file

@ -42,6 +42,11 @@ struct actions {
struct tracefs_instance *trace_output_inst;
};
#define for_each_action(actions, action) \
for ((action) = (actions)->list; \
(action) < (actions)->list + (actions)->len; \
(action)++)
void actions_init(struct actions *self);
void actions_destroy(struct actions *self);
int actions_add_trace_output(struct actions *self, const char *trace_output);

View file

@ -4,11 +4,13 @@
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include "common.h"
struct trace_instance *trace_inst;
int stop_tracing;
volatile int stop_tracing;
static void stop_trace(int sig)
{
@ -37,6 +39,84 @@ static void set_signals(struct common_params *params)
}
}
/*
* common_parse_options - parse common command line options
*
* @argc: argument count
* @argv: argument vector
* @common: common parameters structure
*
* Parse command line options that are common to all rtla tools.
*
* Returns: non zero if a common option was parsed, or 0
* if the option should be handled by tool-specific parsing.
*/
int common_parse_options(int argc, char **argv, struct common_params *common)
{
struct trace_events *tevent;
int saved_state = optind;
int c;
static struct option long_options[] = {
{"cpus", required_argument, 0, 'c'},
{"cgroup", optional_argument, 0, 'C'},
{"debug", no_argument, 0, 'D'},
{"duration", required_argument, 0, 'd'},
{"event", required_argument, 0, 'e'},
{"house-keeping", required_argument, 0, 'H'},
{"priority", required_argument, 0, 'P'},
{0, 0, 0, 0}
};
opterr = 0;
c = getopt_long(argc, argv, "c:C::Dd:e:H:P:", long_options, NULL);
opterr = 1;
switch (c) {
case 'c':
if (parse_cpu_set(optarg, &common->monitored_cpus))
fatal("Invalid -c cpu list");
common->cpus = optarg;
break;
case 'C':
common->cgroup = 1;
common->cgroup_name = parse_optional_arg(argc, argv);
break;
case 'D':
config_debug = 1;
break;
case 'd':
common->duration = parse_seconds_duration(optarg);
if (!common->duration)
fatal("Invalid -d duration");
break;
case 'e':
tevent = trace_event_alloc(optarg);
if (!tevent)
fatal("Error alloc trace event");
if (common->events)
tevent->next = common->events;
common->events = tevent;
break;
case 'H':
common->hk_cpus = 1;
if (parse_cpu_set(optarg, &common->hk_cpu_set))
fatal("Error parsing house keeping CPUs");
break;
case 'P':
if (parse_prio(optarg, &common->sched_param) == -1)
fatal("Invalid -P priority");
common->set_sched = 1;
break;
default:
optind = saved_state;
return 0;
}
return c;
}
/*
* common_apply_config - apply common configs to the initialized tool
*/
@ -348,3 +428,61 @@ int hist_main_loop(struct osnoise_tool *tool)
return retval;
}
int osn_set_stop(struct osnoise_tool *tool)
{
struct common_params *params = tool->params;
int retval;
retval = osnoise_set_stop_us(tool->context, params->stop_us);
if (retval) {
err_msg("Failed to set stop us\n");
return retval;
}
retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
if (retval) {
err_msg("Failed to set stop total us\n");
return retval;
}
return 0;
}
static void print_msg_array(const char * const *msgs)
{
if (!msgs)
return;
for (int i = 0; msgs[i]; i++)
fprintf(stderr, "%s\n", msgs[i]);
}
/*
* common_usage - print complete usage information
*/
void common_usage(const char *tool, const char *mode,
const char *desc, const char * const *start_msgs, const char * const *opt_msgs)
{
static const char * const common_options[] = {
" -h/--help: print this menu",
NULL
};
fprintf(stderr, "rtla %s", tool);
if (strcmp(mode, ""))
fprintf(stderr, " %s", mode);
fprintf(stderr, ": %s (version %s)\n\n", desc, VERSION);
fprintf(stderr, " usage: [rtla] %s ", tool);
if (strcmp(mode, "top") == 0)
fprintf(stderr, "[top] [-h] ");
else
fprintf(stderr, "%s [-h] ", mode);
print_msg_array(start_msgs);
fprintf(stderr, "\n");
print_msg_array(common_options);
print_msg_array(opt_msgs);
exit(EXIT_SUCCESS);
}

View file

@ -54,7 +54,7 @@ struct osnoise_context {
};
extern struct trace_instance *trace_inst;
extern int stop_tracing;
extern volatile int stop_tracing;
struct hist_params {
char no_irq;
@ -152,7 +152,15 @@ void osnoise_destroy_tool(struct osnoise_tool *top);
struct osnoise_tool *osnoise_init_tool(char *tool_name);
struct osnoise_tool *osnoise_init_trace_tool(const char *tracer);
bool osnoise_trace_is_off(struct osnoise_tool *tool, struct osnoise_tool *record);
int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us);
int osnoise_set_stop_total_us(struct osnoise_context *context,
long long stop_total_us);
int common_parse_options(int argc, char **argv, struct common_params *common);
int common_apply_config(struct osnoise_tool *tool, struct common_params *params);
int top_main_loop(struct osnoise_tool *tool);
int hist_main_loop(struct osnoise_tool *tool);
int osn_set_stop(struct osnoise_tool *tool);
void common_usage(const char *tool, const char *mode,
const char *desc, const char * const *start_msgs, const char * const *opt_msgs);

View file

@ -1128,18 +1128,6 @@ osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params)
goto out_err;
}
retval = osnoise_set_stop_us(tool->context, params->common.stop_us);
if (retval) {
err_msg("Failed to set stop us\n");
goto out_err;
}
retval = osnoise_set_stop_total_us(tool->context, params->common.stop_total_us);
if (retval) {
err_msg("Failed to set stop total us\n");
goto out_err;
}
retval = osnoise_set_tracing_thresh(tool->context, params->threshold);
if (retval) {
err_msg("Failed to set tracing_thresh\n");
@ -1184,9 +1172,12 @@ int osnoise_enable(struct osnoise_tool *tool)
debug_msg("Error cleaning up the buffer");
return retval;
}
}
retval = osn_set_stop(tool);
if (retval)
return retval;
return 0;
}

View file

@ -34,12 +34,7 @@ int osnoise_set_runtime_period(struct osnoise_context *context,
unsigned long long period);
void osnoise_restore_runtime_period(struct osnoise_context *context);
int osnoise_set_stop_us(struct osnoise_context *context,
long long stop_us);
void osnoise_restore_stop_us(struct osnoise_context *context);
int osnoise_set_stop_total_us(struct osnoise_context *context,
long long stop_total_us);
void osnoise_restore_stop_total_us(struct osnoise_context *context);
int osnoise_set_timerlat_period_us(struct osnoise_context *context,
@ -58,8 +53,6 @@ int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff);
void osnoise_report_missed_events(struct osnoise_tool *tool);
int osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params);
int osnoise_hist_main(int argc, char *argv[]);
int osnoise_top_main(int argc, char **argv);
int osnoise_enable(struct osnoise_tool *tool);
int osnoise_main(int argc, char **argv);
int hwnoise_main(int argc, char **argv);
@ -68,4 +61,3 @@ extern struct tool_ops timerlat_top_ops, timerlat_hist_ops;
extern struct tool_ops osnoise_top_ops, osnoise_hist_ops;
int run_tool(struct tool_ops *ops, int argc, char *argv[]);
int hist_main_loop(struct osnoise_tool *tool);

View file

@ -9,7 +9,6 @@
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
@ -409,16 +408,15 @@ osnoise_print_stats(struct osnoise_tool *tool)
*/
static void osnoise_hist_usage(void)
{
int i;
static const char * const msg[] = {
"",
" usage: rtla osnoise hist [-h] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
static const char * const msg_start[] = {
"[-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
" [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
" [-c cpu-list] [-H cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] \\",
" [--no-index] [--with-zeros] [-C [cgroup_name]] [--warm-up]",
"",
" -h/--help: print this menu",
NULL,
};
static const char * const msg_opts[] = {
" -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
" -p/--period us: osnoise period in us",
" -r/--runtime us: osnoise runtime in us",
@ -453,13 +451,8 @@ static void osnoise_hist_usage(void)
NULL,
};
fprintf(stderr, "rtla osnoise hist: a per-cpu histogram of the OS noise (version %s)\n",
VERSION);
for (i = 0; msg[i]; i++)
fprintf(stderr, "%s\n", msg[i]);
exit(EXIT_SUCCESS);
common_usage("osnoise", "hist", "a per-cpu histogram of the OS noise",
msg_start, msg_opts);
}
/*
@ -469,7 +462,6 @@ static struct common_params
*osnoise_hist_parse_args(int argc, char *argv[])
{
struct osnoise_params *params;
struct trace_events *tevent;
int retval;
int c;
char *trace_output = NULL;
@ -491,19 +483,12 @@ static struct common_params
{"auto", required_argument, 0, 'a'},
{"bucket-size", required_argument, 0, 'b'},
{"entries", required_argument, 0, 'E'},
{"cpus", required_argument, 0, 'c'},
{"cgroup", optional_argument, 0, 'C'},
{"debug", no_argument, 0, 'D'},
{"duration", required_argument, 0, 'd'},
{"house-keeping", required_argument, 0, 'H'},
{"help", no_argument, 0, 'h'},
{"period", required_argument, 0, 'p'},
{"priority", required_argument, 0, 'P'},
{"runtime", required_argument, 0, 'r'},
{"stop", required_argument, 0, 's'},
{"stop-total", required_argument, 0, 'S'},
{"trace", optional_argument, 0, 't'},
{"event", required_argument, 0, 'e'},
{"threshold", required_argument, 0, 'T'},
{"no-header", no_argument, 0, '0'},
{"no-summary", no_argument, 0, '1'},
@ -518,7 +503,10 @@ static struct common_params
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "a:c:C::b:d:e:E:DhH:p:P:r:s:S:t::T:01234:5:6:7:",
if (common_parse_options(argc, argv, &params->common))
continue;
c = getopt_long(argc, argv, "a:b:E:hp:r:s:S:t::T:01234:5:6:7:",
long_options, NULL);
/* detect the end of the options. */
@ -544,34 +532,6 @@ static struct common_params
params->common.hist.bucket_size >= 1000000)
fatal("Bucket size needs to be > 0 and <= 1000000");
break;
case 'c':
retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
if (retval)
fatal("Invalid -c cpu list");
params->common.cpus = optarg;
break;
case 'C':
params->common.cgroup = 1;
params->common.cgroup_name = parse_optional_arg(argc, argv);
break;
case 'D':
config_debug = 1;
break;
case 'd':
params->common.duration = parse_seconds_duration(optarg);
if (!params->common.duration)
fatal("Invalid -D duration");
break;
case 'e':
tevent = trace_event_alloc(optarg);
if (!tevent)
fatal("Error alloc trace event");
if (params->common.events)
tevent->next = params->common.events;
params->common.events = tevent;
break;
case 'E':
params->common.hist.entries = get_llong_from_str(optarg);
if (params->common.hist.entries < 10 ||
@ -582,23 +542,11 @@ static struct common_params
case '?':
osnoise_hist_usage();
break;
case 'H':
params->common.hk_cpus = 1;
retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
if (retval)
fatal("Error parsing house keeping CPUs");
break;
case 'p':
params->period = get_llong_from_str(optarg);
if (params->period > 10000000)
fatal("Period longer than 10 s");
break;
case 'P':
retval = parse_prio(optarg, &params->common.sched_param);
if (retval == -1)
fatal("Invalid -P priority");
params->common.set_sched = 1;
break;
case 'r':
params->runtime = get_llong_from_str(optarg);
if (params->runtime < 100)

View file

@ -257,14 +257,16 @@ osnoise_print_stats(struct osnoise_tool *top)
*/
static void osnoise_top_usage(struct osnoise_params *params)
{
int i;
const char *tool, *mode, *desc;
static const char * const msg[] = {
" [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
static const char * const msg_start[] = {
"[-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
" [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
" [-c cpu-list] [-H cpu-list] [-P priority] [-C [cgroup_name]] [--warm-up s]",
"",
" -h/--help: print this menu",
NULL,
};
static const char * const msg_opts[] = {
" -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
" -p/--period us: osnoise period in us",
" -r/--runtime us: osnoise runtime in us",
@ -295,25 +297,16 @@ static void osnoise_top_usage(struct osnoise_params *params)
};
if (params->mode == MODE_OSNOISE) {
fprintf(stderr,
"rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n",
VERSION);
fprintf(stderr, " usage: rtla osnoise [top]");
tool = "osnoise";
mode = "top";
desc = "a per-cpu summary of the OS noise";
} else {
tool = "hwnoise";
mode = "";
desc = "a summary of hardware-related noise";
}
if (params->mode == MODE_HWNOISE) {
fprintf(stderr,
"rtla hwnoise: a summary of hardware-related noise (version %s)\n",
VERSION);
fprintf(stderr, " usage: rtla hwnoise");
}
for (i = 0; msg[i]; i++)
fprintf(stderr, "%s\n", msg[i]);
exit(EXIT_SUCCESS);
common_usage(tool, mode, desc, msg_start, msg_opts);
}
/*
@ -322,7 +315,6 @@ static void osnoise_top_usage(struct osnoise_params *params)
struct common_params *osnoise_top_parse_args(int argc, char **argv)
{
struct osnoise_params *params;
struct trace_events *tevent;
int retval;
int c;
char *trace_output = NULL;
@ -346,15 +338,8 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv)
while (1) {
static struct option long_options[] = {
{"auto", required_argument, 0, 'a'},
{"cpus", required_argument, 0, 'c'},
{"cgroup", optional_argument, 0, 'C'},
{"debug", no_argument, 0, 'D'},
{"duration", required_argument, 0, 'd'},
{"event", required_argument, 0, 'e'},
{"house-keeping", required_argument, 0, 'H'},
{"help", no_argument, 0, 'h'},
{"period", required_argument, 0, 'p'},
{"priority", required_argument, 0, 'P'},
{"quiet", no_argument, 0, 'q'},
{"runtime", required_argument, 0, 'r'},
{"stop", required_argument, 0, 's'},
@ -370,7 +355,10 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv)
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "a:c:C::d:De:hH:p:P:qr:s:S:t::T:0:1:2:3:",
if (common_parse_options(argc, argv, &params->common))
continue;
c = getopt_long(argc, argv, "a:hp:qr:s:S:t::T:0:1:2:3:",
long_options, NULL);
/* Detect the end of the options. */
@ -389,56 +377,16 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv)
if (!trace_output)
trace_output = "osnoise_trace.txt";
break;
case 'c':
retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
if (retval)
fatal("Invalid -c cpu list");
params->common.cpus = optarg;
break;
case 'C':
params->common.cgroup = 1;
params->common.cgroup_name = parse_optional_arg(argc, argv);
break;
case 'D':
config_debug = 1;
break;
case 'd':
params->common.duration = parse_seconds_duration(optarg);
if (!params->common.duration)
fatal("Invalid -d duration");
break;
case 'e':
tevent = trace_event_alloc(optarg);
if (!tevent)
fatal("Error alloc trace event");
if (params->common.events)
tevent->next = params->common.events;
params->common.events = tevent;
break;
case 'h':
case '?':
osnoise_top_usage(params);
break;
case 'H':
params->common.hk_cpus = 1;
retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
if (retval)
fatal("Error parsing house keeping CPUs");
break;
case 'p':
params->period = get_llong_from_str(optarg);
if (params->period > 10000000)
fatal("Period longer than 10 s");
break;
case 'P':
retval = parse_prio(optarg, &params->common.sched_param);
if (retval == -1)
fatal("Invalid -P priority");
params->common.set_sched = 1;
break;
case 'q':
params->common.quiet = 1;
break;

View file

@ -40,6 +40,17 @@ struct {
__uint(max_entries, 1);
} signal_stop_tracing SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(key_size, sizeof(unsigned int));
__uint(max_entries, 1);
__array(values, unsigned int (void *));
} bpf_action SEC(".maps") = {
.values = {
[0] = 0
},
};
/* Params to be set by rtla */
const volatile int bucket_size = 1;
const volatile int output_divisor = 1000;
@ -109,7 +120,7 @@ nosubprog void update_summary(void *map,
map_set(map, SUMMARY_SUM, map_get(map, SUMMARY_SUM) + latency);
}
nosubprog void set_stop_tracing(void)
nosubprog void set_stop_tracing(struct trace_event_raw_timerlat_sample *tp_args)
{
int value = 0;
@ -118,6 +129,12 @@ nosubprog void set_stop_tracing(void)
/* Signal to userspace */
bpf_ringbuf_output(&signal_stop_tracing, &value, sizeof(value), 0);
/*
* Call into BPF action program, if attached.
* Otherwise, just silently fail.
*/
bpf_tail_call(tp_args, &bpf_action, 0);
}
SEC("tp/osnoise/timerlat_sample")
@ -138,19 +155,19 @@ int handle_timerlat_sample(struct trace_event_raw_timerlat_sample *tp_args)
update_summary(&summary_irq, latency, bucket);
if (irq_threshold != 0 && latency_us >= irq_threshold)
set_stop_tracing();
set_stop_tracing(tp_args);
} else if (tp_args->context == 1) {
update_main_hist(&hist_thread, bucket);
update_summary(&summary_thread, latency, bucket);
if (thread_threshold != 0 && latency_us >= thread_threshold)
set_stop_tracing();
set_stop_tracing(tp_args);
} else {
update_main_hist(&hist_user, bucket);
update_summary(&summary_user, latency, bucket);
if (thread_threshold != 0 && latency_us >= thread_threshold)
set_stop_tracing();
set_stop_tracing(tp_args);
}
return 0;

View file

@ -9,7 +9,6 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sched.h>
@ -48,24 +47,16 @@ timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params)
}
}
if (params->mode != TRACING_MODE_BPF) {
/*
* In tracefs and mixed mode, timerlat tracer handles stopping
* on threshold
*/
retval = osnoise_set_stop_us(tool->context, params->common.stop_us);
if (retval) {
err_msg("Failed to set stop us\n");
/* Check if BPF action program is requested but BPF is not available */
if (params->bpf_action_program) {
if (params->mode == TRACING_MODE_TRACEFS) {
err_msg("BPF actions are not supported in tracefs-only mode\n");
goto out_err;
}
retval = osnoise_set_stop_total_us(tool->context, params->common.stop_total_us);
if (retval) {
err_msg("Failed to set stop total us\n");
if (timerlat_load_bpf_action_program(params->bpf_action_program))
goto out_err;
}
}
retval = osnoise_set_timerlat_period_us(tool->context,
params->timerlat_period_us ?
@ -184,6 +175,16 @@ int timerlat_enable(struct osnoise_tool *tool)
}
}
/*
* In tracefs and mixed mode, timerlat tracer handles stopping
* on threshold
*/
if (params->mode != TRACING_MODE_BPF) {
retval = osn_set_stop(tool);
if (retval)
return retval;
}
return 0;
}

View file

@ -27,6 +27,7 @@ struct timerlat_params {
int dump_tasks;
int deepest_idle_state;
enum timerlat_tracing_mode mode;
const char *bpf_action_program;
};
#define to_timerlat_params(ptr) container_of(ptr, struct timerlat_params, common)
@ -36,4 +37,3 @@ int timerlat_main(int argc, char *argv[]);
int timerlat_enable(struct osnoise_tool *tool);
void timerlat_analyze(struct osnoise_tool *tool, bool stopped);
void timerlat_free(struct osnoise_tool *tool);

View file

@ -7,6 +7,10 @@
static struct timerlat_bpf *bpf;
/* BPF object and program for action program */
static struct bpf_object *obj;
static struct bpf_program *prog;
/*
* timerlat_bpf_init - load and initialize BPF program to collect timerlat data
*/
@ -59,6 +63,19 @@ int timerlat_bpf_init(struct timerlat_params *params)
return 0;
}
/*
* timerlat_bpf_set_action - set action on threshold executed on BPF side
*/
static int timerlat_bpf_set_action(struct bpf_program *prog)
{
unsigned int key = 0, value = bpf_program__fd(prog);
return bpf_map__update_elem(bpf->maps.bpf_action,
&key, sizeof(key),
&value, sizeof(value),
BPF_ANY);
}
/*
* timerlat_bpf_attach - attach BPF program to collect timerlat data
*/
@ -83,6 +100,11 @@ void timerlat_bpf_detach(void)
void timerlat_bpf_destroy(void)
{
timerlat_bpf__destroy(bpf);
bpf = NULL;
if (obj)
bpf_object__close(obj);
obj = NULL;
prog = NULL;
}
static int handle_rb_event(void *ctx, void *data, size_t data_sz)
@ -177,4 +199,48 @@ int timerlat_bpf_get_summary_value(enum summary_field key,
bpf->maps.summary_user,
key, value_irq, value_thread, value_user, cpus);
}
/*
* timerlat_load_bpf_action_program - load and register a BPF action program
*/
int timerlat_load_bpf_action_program(const char *program_path)
{
int err;
obj = bpf_object__open_file(program_path, NULL);
if (!obj) {
err_msg("Failed to open BPF action program: %s\n", program_path);
goto out_err;
}
err = bpf_object__load(obj);
if (err) {
err_msg("Failed to load BPF action program: %s\n", program_path);
goto out_obj_err;
}
prog = bpf_object__find_program_by_name(obj, "action_handler");
if (!prog) {
err_msg("BPF action program must have 'action_handler' function: %s\n",
program_path);
goto out_obj_err;
}
err = timerlat_bpf_set_action(prog);
if (err) {
err_msg("Failed to register BPF action program: %s\n", program_path);
goto out_prog_err;
}
return 0;
out_prog_err:
prog = NULL;
out_obj_err:
bpf_object__close(obj);
obj = NULL;
out_err:
return 1;
}
#endif /* HAVE_BPF_SKEL */

View file

@ -12,6 +12,7 @@ enum summary_field {
};
#ifndef __bpf__
#include <bpf/libbpf.h>
#ifdef HAVE_BPF_SKEL
int timerlat_bpf_init(struct timerlat_params *params);
int timerlat_bpf_attach(void);
@ -29,7 +30,7 @@ int timerlat_bpf_get_summary_value(enum summary_field key,
long long *value_thread,
long long *value_user,
int cpus);
int timerlat_load_bpf_action_program(const char *program_path);
static inline int have_libbpf_support(void) { return 1; }
#else
static inline int timerlat_bpf_init(struct timerlat_params *params)
@ -57,6 +58,10 @@ static inline int timerlat_bpf_get_summary_value(enum summary_field key,
{
return -1;
}
static inline int timerlat_load_bpf_action_program(const char *program_path)
{
return -1;
}
static inline int have_libbpf_support(void) { return 0; }
#endif /* HAVE_BPF_SKEL */
#endif /* __bpf__ */

View file

@ -696,17 +696,16 @@ timerlat_print_stats(struct osnoise_tool *tool)
*/
static void timerlat_hist_usage(void)
{
int i;
char *msg[] = {
"",
" usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\",
static const char * const msg_start[] = {
"[-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\",
" [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
" [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\",
" [--no-index] [--with-zeros] [--dma-latency us] [-C [cgroup_name]] [--no-aa] [--dump-task] [-u|-k]",
" [--warm-up s] [--deepest-idle-state n]",
"",
" -h/--help: print this menu",
NULL,
};
static const char * const msg_opts[] = {
" -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
" -p/--period us: timerlat period in us",
" -i/--irq us: stop trace if the irq latency is higher than the argument in us",
@ -747,16 +746,12 @@ static void timerlat_hist_usage(void)
" --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
" --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
" --on-end <action>: define action to be executed at measurement end, multiple are allowed",
" --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
NULL,
};
fprintf(stderr, "rtla timerlat hist: a per-cpu histogram of the timer latency (version %s)\n",
VERSION);
for (i = 0; msg[i]; i++)
fprintf(stderr, "%s\n", msg[i]);
exit(EXIT_SUCCESS);
common_usage("timerlat", "hist", "a per-cpu histogram of the timer latency",
msg_start, msg_opts);
}
/*
@ -766,7 +761,6 @@ static struct common_params
*timerlat_hist_parse_args(int argc, char *argv[])
{
struct timerlat_params *params;
struct trace_events *tevent;
int auto_thresh;
int retval;
int c;
@ -796,25 +790,18 @@ static struct common_params
while (1) {
static struct option long_options[] = {
{"auto", required_argument, 0, 'a'},
{"cpus", required_argument, 0, 'c'},
{"cgroup", optional_argument, 0, 'C'},
{"bucket-size", required_argument, 0, 'b'},
{"debug", no_argument, 0, 'D'},
{"entries", required_argument, 0, 'E'},
{"duration", required_argument, 0, 'd'},
{"house-keeping", required_argument, 0, 'H'},
{"help", no_argument, 0, 'h'},
{"irq", required_argument, 0, 'i'},
{"nano", no_argument, 0, 'n'},
{"period", required_argument, 0, 'p'},
{"priority", required_argument, 0, 'P'},
{"stack", required_argument, 0, 's'},
{"thread", required_argument, 0, 'T'},
{"trace", optional_argument, 0, 't'},
{"user-threads", no_argument, 0, 'u'},
{"kernel-threads", no_argument, 0, 'k'},
{"user-load", no_argument, 0, 'U'},
{"event", required_argument, 0, 'e'},
{"no-irq", no_argument, 0, '0'},
{"no-thread", no_argument, 0, '1'},
{"no-header", no_argument, 0, '2'},
@ -831,10 +818,14 @@ static struct common_params
{"deepest-idle-state", required_argument, 0, '\4'},
{"on-threshold", required_argument, 0, '\5'},
{"on-end", required_argument, 0, '\6'},
{"bpf-action", required_argument, 0, '\7'},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "a:c:C::b:d:e:E:DhH:i:knp:P:s:t::T:uU0123456:7:8:9\1\2:\3:",
if (common_parse_options(argc, argv, &params->common))
continue;
c = getopt_long(argc, argv, "a:b:E:hi:knp:s:t::T:uU0123456:7:8:9\1\2:\3:",
long_options, NULL);
/* detect the end of the options. */
@ -856,16 +847,6 @@ static struct common_params
if (!trace_output)
trace_output = "timerlat_trace.txt";
break;
case 'c':
retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
if (retval)
fatal("Invalid -c cpu list");
params->common.cpus = optarg;
break;
case 'C':
params->common.cgroup = 1;
params->common.cgroup_name = parse_optional_arg(argc, argv);
break;
case 'b':
params->common.hist.bucket_size = get_llong_from_str(optarg);
@ -873,24 +854,6 @@ static struct common_params
params->common.hist.bucket_size >= 1000000)
fatal("Bucket size needs to be > 0 and <= 1000000");
break;
case 'D':
config_debug = 1;
break;
case 'd':
params->common.duration = parse_seconds_duration(optarg);
if (!params->common.duration)
fatal("Invalid -D duration");
break;
case 'e':
tevent = trace_event_alloc(optarg);
if (!tevent)
fatal("Error alloc trace event");
if (params->common.events)
tevent->next = params->common.events;
params->common.events = tevent;
break;
case 'E':
params->common.hist.entries = get_llong_from_str(optarg);
if (params->common.hist.entries < 10 ||
@ -901,12 +864,6 @@ static struct common_params
case '?':
timerlat_hist_usage();
break;
case 'H':
params->common.hk_cpus = 1;
retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
if (retval)
fatal("Error parsing house keeping CPUs");
break;
case 'i':
params->common.stop_us = get_llong_from_str(optarg);
break;
@ -921,12 +878,6 @@ static struct common_params
if (params->timerlat_period_us > 1000000)
fatal("Period longer than 1 s");
break;
case 'P':
retval = parse_prio(optarg, &params->common.sched_param);
if (retval == -1)
fatal("Invalid -P priority");
params->common.set_sched = 1;
break;
case 's':
params->print_stack = get_llong_from_str(optarg);
break;
@ -1012,6 +963,9 @@ static struct common_params
if (retval)
fatal("Invalid action %s", optarg);
break;
case '\7':
params->bpf_action_program = optarg;
break;
default:
fatal("Invalid option");
}

View file

@ -11,7 +11,6 @@
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <sched.h>
#include <pthread.h>
@ -476,15 +475,14 @@ timerlat_print_stats(struct osnoise_tool *top)
*/
static void timerlat_top_usage(void)
{
int i;
static const char *const msg[] = {
"",
" usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\",
static const char *const msg_start[] = {
"[-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\",
" [[-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
" [-P priority] [--dma-latency us] [--aa-only us] [-C [cgroup_name]] [-u|-k] [--warm-up s] [--deepest-idle-state n]",
"",
" -h/--help: print this menu",
NULL,
};
static const char *const msg_opts[] = {
" -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
" --aa-only us: stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)",
" -p/--period us: timerlat period in us",
@ -519,16 +517,12 @@ static void timerlat_top_usage(void)
" --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
" --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
" --on-end: define action to be executed at measurement end, multiple are allowed",
" --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
NULL,
};
fprintf(stderr, "rtla timerlat top: a per-cpu summary of the timer latency (version %s)\n",
VERSION);
for (i = 0; msg[i]; i++)
fprintf(stderr, "%s\n", msg[i]);
exit(EXIT_SUCCESS);
common_usage("timerlat", "top", "a per-cpu summary of the timer latency",
msg_start, msg_opts);
}
/*
@ -538,7 +532,6 @@ static struct common_params
*timerlat_top_parse_args(int argc, char **argv)
{
struct timerlat_params *params;
struct trace_events *tevent;
long long auto_thresh;
int retval;
int c;
@ -566,17 +559,10 @@ static struct common_params
while (1) {
static struct option long_options[] = {
{"auto", required_argument, 0, 'a'},
{"cpus", required_argument, 0, 'c'},
{"cgroup", optional_argument, 0, 'C'},
{"debug", no_argument, 0, 'D'},
{"duration", required_argument, 0, 'd'},
{"event", required_argument, 0, 'e'},
{"help", no_argument, 0, 'h'},
{"house-keeping", required_argument, 0, 'H'},
{"irq", required_argument, 0, 'i'},
{"nano", no_argument, 0, 'n'},
{"period", required_argument, 0, 'p'},
{"priority", required_argument, 0, 'P'},
{"quiet", no_argument, 0, 'q'},
{"stack", required_argument, 0, 's'},
{"thread", required_argument, 0, 'T'},
@ -595,10 +581,14 @@ static struct common_params
{"deepest-idle-state", required_argument, 0, '8'},
{"on-threshold", required_argument, 0, '9'},
{"on-end", required_argument, 0, '\1'},
{"bpf-action", required_argument, 0, '\2'},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "a:c:C::d:De:hH:i:knp:P:qs:t::T:uU0:1:2:345:6:7:",
if (common_parse_options(argc, argv, &params->common))
continue;
c = getopt_long(argc, argv, "a:hi:knp:qs:t::T:uU0:1:2:345:6:7:",
long_options, NULL);
/* detect the end of the options. */
@ -635,43 +625,10 @@ static struct common_params
/* set aa_only to avoid parsing the trace */
params->common.aa_only = 1;
break;
case 'c':
retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
if (retval)
fatal("Invalid -c cpu list");
params->common.cpus = optarg;
break;
case 'C':
params->common.cgroup = 1;
params->common.cgroup_name = optarg;
break;
case 'D':
config_debug = 1;
break;
case 'd':
params->common.duration = parse_seconds_duration(optarg);
if (!params->common.duration)
fatal("Invalid -d duration");
break;
case 'e':
tevent = trace_event_alloc(optarg);
if (!tevent)
fatal("Error alloc trace event");
if (params->common.events)
tevent->next = params->common.events;
params->common.events = tevent;
break;
case 'h':
case '?':
timerlat_top_usage();
break;
case 'H':
params->common.hk_cpus = 1;
retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
if (retval)
fatal("Error parsing house keeping CPUs");
break;
case 'i':
params->common.stop_us = get_llong_from_str(optarg);
break;
@ -686,12 +643,6 @@ static struct common_params
if (params->timerlat_period_us > 1000000)
fatal("Period longer than 1 s");
break;
case 'P':
retval = parse_prio(optarg, &params->common.sched_param);
if (retval == -1)
fatal("Invalid -P priority");
params->common.set_sched = 1;
break;
case 'q':
params->common.quiet = 1;
break;
@ -762,6 +713,9 @@ static struct common_params
if (retval)
fatal("Invalid action %s", optarg);
break;
case '\2':
params->bpf_action_program = optarg;
break;
default:
fatal("Invalid option");
}

View file

@ -2,7 +2,6 @@
#define _GNU_SOURCE
#include <sys/sendfile.h>
#include <tracefs.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

View file

@ -17,6 +17,7 @@
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <limits.h>
#include "utils.h"
@ -112,7 +113,7 @@ void get_duration(time_t start_time, char *output, int output_size)
* Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set
* filling cpu_set_t argument.
*
* Returns 1 on success, 0 otherwise.
* Returns 0 on success, 1 otherwise.
*/
int parse_cpu_set(char *cpu_list, cpu_set_t *set)
{
@ -314,6 +315,7 @@ static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_e
if (retval <= 0)
return 0;
buffer[MAX_PATH-1] = '\0';
retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
if (retval)
return 0;
@ -337,6 +339,7 @@ int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
struct dirent *proc_entry;
DIR *procfs;
int retval;
int pid;
if (strlen(comm_prefix) >= MAX_PATH) {
err_msg("Command prefix is too long: %d < strlen(%s)\n",
@ -356,8 +359,12 @@ int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
if (!retval)
continue;
if (strtoi(proc_entry->d_name, &pid)) {
err_msg("'%s' is not a valid pid", proc_entry->d_name);
goto out_err;
}
/* procfs_is_workload_pid confirmed it is a pid */
retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
retval = __set_sched_attr(pid, attr);
if (retval) {
err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
goto out_err;
@ -742,6 +749,7 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
if (fd < 0)
return 0;
memset(path, 0, sizeof(path));
retval = read(fd, path, MAX_PATH);
close(fd);
@ -749,6 +757,7 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
if (retval <= 0)
return 0;
path[MAX_PATH-1] = '\0';
start = path;
start = strstr(start, ":");
@ -784,27 +793,27 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
}
/*
* set_comm_cgroup - Set cgroup to pid_t pid
* open_cgroup_procs - Open the cgroup.procs file for the given cgroup
*
* If cgroup argument is not NULL, the threads will move to the given cgroup.
* Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
* If cgroup argument is not NULL, the cgroup.procs file for that cgroup
* will be opened. Otherwise, the cgroup of the calling, i.e., rtla, thread
* will be used.
*
* Supports cgroup v2.
*
* Returns 1 on success, 0 otherwise.
* Returns the file descriptor on success, -1 otherwise.
*/
int set_pid_cgroup(pid_t pid, const char *cgroup)
static int open_cgroup_procs(const char *cgroup)
{
char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
char cgroup_procs[MAX_PATH];
char pid_str[24];
int retval;
int cg_fd;
retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
if (!retval) {
err_msg("Did not find cgroupv2 mount point\n");
return 0;
return -1;
}
if (!cgroup) {
@ -812,7 +821,7 @@ int set_pid_cgroup(pid_t pid, const char *cgroup)
sizeof(cgroup_path) - strlen(cgroup_path));
if (!retval) {
err_msg("Did not find self cgroup\n");
return 0;
return -1;
}
} else {
snprintf(&cgroup_path[strlen(cgroup_path)],
@ -824,6 +833,29 @@ int set_pid_cgroup(pid_t pid, const char *cgroup)
debug_msg("Using cgroup path at: %s\n", cgroup_procs);
cg_fd = open(cgroup_procs, O_RDWR);
if (cg_fd < 0)
return -1;
return cg_fd;
}
/*
* set_pid_cgroup - Set cgroup to pid_t pid
*
* If cgroup argument is not NULL, the threads will move to the given cgroup.
* Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
*
* Supports cgroup v2.
*
* Returns 1 on success, 0 otherwise.
*/
int set_pid_cgroup(pid_t pid, const char *cgroup)
{
char pid_str[24];
int retval;
int cg_fd;
cg_fd = open_cgroup_procs(cgroup);
if (cg_fd < 0)
return 0;
@ -853,8 +885,6 @@ int set_pid_cgroup(pid_t pid, const char *cgroup)
*/
int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
{
char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
char cgroup_procs[MAX_PATH];
struct dirent *proc_entry;
DIR *procfs;
int retval;
@ -866,29 +896,7 @@ int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
return 0;
}
retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
if (!retval) {
err_msg("Did not find cgroupv2 mount point\n");
return 0;
}
if (!cgroup) {
retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
sizeof(cgroup_path) - strlen(cgroup_path));
if (!retval) {
err_msg("Did not find self cgroup\n");
return 0;
}
} else {
snprintf(&cgroup_path[strlen(cgroup_path)],
sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
}
snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
debug_msg("Using cgroup path at: %s\n", cgroup_procs);
cg_fd = open(cgroup_procs, O_RDWR);
cg_fd = open_cgroup_procs(cgroup);
if (cg_fd < 0)
return 0;
@ -1000,3 +1008,25 @@ char *parse_optional_arg(int argc, char **argv)
return NULL;
}
}
/*
* strtoi - convert string to integer with error checking
*
* Returns 0 on success, -1 if conversion fails or result is out of int range.
*/
int strtoi(const char *s, int *res)
{
char *end_ptr;
long lres;
if (!*s)
return -1;
errno = 0;
lres = strtol(s, &end_ptr, 0);
if (errno || *end_ptr || lres > INT_MAX || lres < INT_MIN)
return -1;
*res = (int) lres;
return 0;
}

View file

@ -3,6 +3,8 @@
#include <stdint.h>
#include <time.h>
#include <sched.h>
#include <stdbool.h>
#include <stdlib.h>
/*
* '18446744073709551615\0'
@ -24,7 +26,6 @@ void fatal(const char *fmt, ...);
long parse_seconds_duration(char *val);
void get_duration(time_t start_time, char *output, int output_size);
int parse_cpu_list(char *cpu_list, char **monitored_cpus);
char *parse_optional_arg(int argc, char **argv);
long long get_llong_from_str(char *start);
@ -82,12 +83,13 @@ static inline int set_deepest_cpu_idle_state(unsigned int cpu, unsigned int stat
static inline int have_libcpupower_support(void) { return 0; }
#endif /* HAVE_LIBCPUPOWER_SUPPORT */
int auto_house_keeping(cpu_set_t *monitored_cpus);
__attribute__((__warn_unused_result__)) int strtoi(const char *s, int *res);
#define ns_to_usf(x) (((double)x/1000))
#define ns_to_per(total, part) ((part * 100) / (double)total)
enum result {
PASSED = 0, /* same as EXIT_SUCCESS */
ERROR = 1, /* same as EXIT_FAILURE, an error in arguments */
FAILED = 2, /* test hit the stop tracing condition */
PASSED = EXIT_SUCCESS,
ERROR = EXIT_FAILURE,
FAILED, /* test hit the stop tracing condition */
};

View file

@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_tracing.h>
char LICENSE[] SEC("license") = "GPL";
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, unsigned int);
__type(value, unsigned long long);
} rtla_test_map SEC(".maps");
struct trace_event_raw_timerlat_sample;
SEC("tp/timerlat_action")
int action_handler(struct trace_event_raw_timerlat_sample *tp_args)
{
unsigned int key = 0;
unsigned long long value = 42;
bpf_map_update_elem(&rtla_test_map, &key, &value, BPF_ANY);
return 0;
}

View file

@ -105,7 +105,6 @@ check_with_osnoise_options() {
[ "$1" == "" ] && continue
option=$(echo $1 | cut -d '=' -f 1)
value=$(echo $1 | cut -d '=' -f 2)
echo "option: $option, value: $value"
echo "$value" > "/sys/kernel/tracing/osnoise/$option" || return 1
done
fi

View file

@ -67,6 +67,21 @@ check "hist with trace output at end" \
"timerlat hist -d 1s --on-end trace" 0 "^ Saving trace to timerlat_trace.txt$"
check "top with trace output at end" \
"timerlat top -d 1s --on-end trace" 0 "^ Saving trace to timerlat_trace.txt$"
# BPF action program tests
if [ "$option" -eq 0 ]
then
# Test BPF action program properly in BPF mode
[ -z "$BPFTOOL" ] && BPFTOOL=bpftool
check "hist with BPF action program (BPF mode)" \
"timerlat hist -T 2 --bpf-action tests/bpf/bpf_action_map.o --on-threshold shell,command='$BPFTOOL map dump name rtla_test_map'" \
2 '"value": 42'
else
# Test BPF action program failure in non-BPF mode
check "hist with BPF action program (non-BPF mode)" \
"timerlat hist -T 2 --bpf-action tests/bpf/bpf_action_map.o" \
1 "BPF actions are not supported in tracefs-only mode"
fi
done
test_end