Files
linux/tools/tracing/rtla/src/timerlat.c
Crystal Wood 2f3172f9dd tools/rtla: Consolidate code between osnoise/timerlat and hist/top
Currently a lot of code is duplicated between the different rtla tools,
making maintenance more difficult, and encouraging divergence such as
features that are only implemented for certain tools even though they
could be more broadly applicable.

Merge the various main() functions into a common run_tool() with an ops
struct for tool-specific details.

Implement enough support for actions on osnoise to not need to keep the
old params->trace_output path.

Cc: John Kacur <jkacur@redhat.com>
Cc: Costa Shulyupin <costa.shul@redhat.com>
Link: https://lore.kernel.org/20250907022325.243930-5-crwood@redhat.com
Reviewed-by: Tomas Glozar  <tglozar@redhat.com>
Signed-off-by: Crystal Wood <crwood@redhat.com>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
2025-09-27 04:52:57 -04:00

294 lines
7.1 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sched.h>
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
#define DEFAULT_TIMERLAT_PERIOD 1000 /* 1ms */
static int dma_latency_fd = -1;
/*
* timerlat_apply_config - apply common configs to the initialized tool
*/
int
timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params)
{
int retval;
/*
* Try to enable BPF, unless disabled explicitly.
* If BPF enablement fails, fall back to tracefs mode.
*/
if (getenv("RTLA_NO_BPF") && strncmp(getenv("RTLA_NO_BPF"), "1", 2) == 0) {
debug_msg("RTLA_NO_BPF set, disabling BPF\n");
params->mode = TRACING_MODE_TRACEFS;
} else if (!tep_find_event_by_name(tool->trace.tep, "osnoise", "timerlat_sample")) {
debug_msg("osnoise:timerlat_sample missing, disabling BPF\n");
params->mode = TRACING_MODE_TRACEFS;
} else {
retval = timerlat_bpf_init(params);
if (retval) {
debug_msg("Could not enable BPF\n");
params->mode = TRACING_MODE_TRACEFS;
}
}
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");
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_timerlat_period_us(tool->context,
params->timerlat_period_us ?
params->timerlat_period_us :
DEFAULT_TIMERLAT_PERIOD);
if (retval) {
err_msg("Failed to set timerlat period\n");
goto out_err;
}
retval = osnoise_set_print_stack(tool->context, params->print_stack);
if (retval) {
err_msg("Failed to set print stack\n");
goto out_err;
}
/*
* If the user did not specify a type of thread, try user-threads first.
* Fall back to kernel threads otherwise.
*/
if (!params->common.kernel_workload && !params->common.user_data) {
retval = tracefs_file_exists(NULL, "osnoise/per_cpu/cpu0/timerlat_fd");
if (retval) {
debug_msg("User-space interface detected, setting user-threads\n");
params->common.user_workload = 1;
params->common.user_data = 1;
} else {
debug_msg("User-space interface not detected, setting kernel-threads\n");
params->common.kernel_workload = 1;
}
}
return common_apply_config(tool, &params->common);
out_err:
return -1;
}
int timerlat_enable(struct osnoise_tool *tool)
{
struct timerlat_params *params = to_timerlat_params(tool->params);
int retval, nr_cpus, i;
if (params->dma_latency >= 0) {
dma_latency_fd = set_cpu_dma_latency(params->dma_latency);
if (dma_latency_fd < 0) {
err_msg("Could not set /dev/cpu_dma_latency.\n");
return -1;
}
}
if (params->deepest_idle_state >= -1) {
if (!have_libcpupower_support()) {
err_msg("rtla built without libcpupower, --deepest-idle-state is not supported\n");
return -1;
}
nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
for (i = 0; i < nr_cpus; i++) {
if (params->common.cpus && !CPU_ISSET(i, &params->common.monitored_cpus))
continue;
if (save_cpu_idle_disable_state(i) < 0) {
err_msg("Could not save cpu idle state.\n");
return -1;
}
if (set_deepest_cpu_idle_state(i, params->deepest_idle_state) < 0) {
err_msg("Could not set deepest cpu idle state.\n");
return -1;
}
}
}
if (!params->no_aa) {
tool->aa = osnoise_init_tool("timerlat_aa");
if (!tool->aa)
return -1;
retval = timerlat_aa_init(tool->aa, params->dump_tasks);
if (retval) {
err_msg("Failed to enable the auto analysis instance\n");
return retval;
}
retval = enable_tracer_by_name(tool->aa->trace.inst, "timerlat");
if (retval) {
err_msg("Failed to enable aa tracer\n");
return retval;
}
}
if (params->common.warmup > 0) {
debug_msg("Warming up for %d seconds\n", params->common.warmup);
sleep(params->common.warmup);
if (stop_tracing)
return -1;
}
/*
* Start the tracers here, after having set all instances.
*
* Let the trace instance start first for the case of hitting a stop
* tracing while enabling other instances. The trace instance is the
* one with most valuable information.
*/
if (tool->record)
trace_instance_start(&tool->record->trace);
if (!params->no_aa)
trace_instance_start(&tool->aa->trace);
if (params->mode == TRACING_MODE_TRACEFS) {
trace_instance_start(&tool->trace);
} else {
retval = timerlat_bpf_attach();
if (retval) {
err_msg("Error attaching BPF program\n");
return retval;
}
}
return 0;
}
void timerlat_analyze(struct osnoise_tool *tool, bool stopped)
{
struct timerlat_params *params = to_timerlat_params(tool->params);
if (stopped) {
if (!params->no_aa)
timerlat_auto_analysis(params->common.stop_us,
params->common.stop_total_us);
} else if (params->common.aa_only) {
char *max_lat;
/*
* If the trace did not stop with --aa-only, at least print
* the max known latency.
*/
max_lat = tracefs_instance_file_read(trace_inst->inst, "tracing_max_latency", NULL);
if (max_lat) {
printf(" Max latency was %s\n", max_lat);
free(max_lat);
}
}
}
void timerlat_free(struct osnoise_tool *tool)
{
struct timerlat_params *params = to_timerlat_params(tool->params);
int nr_cpus, i;
timerlat_aa_destroy();
if (dma_latency_fd >= 0)
close(dma_latency_fd);
if (params->deepest_idle_state >= -1) {
for (i = 0; i < nr_cpus; i++) {
if (params->common.cpus &&
!CPU_ISSET(i, &params->common.monitored_cpus))
continue;
restore_cpu_idle_disable_state(i);
}
}
osnoise_destroy_tool(tool->aa);
if (params->mode != TRACING_MODE_TRACEFS)
timerlat_bpf_destroy();
free_cpu_idle_disable_states();
}
static void timerlat_usage(int err)
{
int i;
static const char * const msg[] = {
"",
"timerlat version " VERSION,
"",
" usage: [rtla] timerlat [MODE] ...",
"",
" modes:",
" top - prints the summary from timerlat tracer",
" hist - prints a histogram of timer latencies",
"",
"if no MODE is given, the top mode is called, passing the arguments",
NULL,
};
for (i = 0; msg[i]; i++)
fprintf(stderr, "%s\n", msg[i]);
exit(err);
}
int timerlat_main(int argc, char *argv[])
{
if (argc == 0)
goto usage;
/*
* if timerlat was called without any argument, run the
* default cmdline.
*/
if (argc == 1) {
run_tool(&timerlat_top_ops, argc, argv);
exit(0);
}
if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
timerlat_usage(0);
} else if (strncmp(argv[1], "-", 1) == 0) {
/* the user skipped the tool, call the default one */
run_tool(&timerlat_top_ops, argc, argv);
exit(0);
} else if (strcmp(argv[1], "top") == 0) {
run_tool(&timerlat_top_ops, argc-1, &argv[1]);
exit(0);
} else if (strcmp(argv[1], "hist") == 0) {
run_tool(&timerlat_hist_ops, argc-1, &argv[1]);
exit(0);
}
usage:
timerlat_usage(1);
exit(1);
}