diff --git a/Documentation/tools/rtla/common_appendix.txt b/Documentation/tools/rtla/common_appendix.txt index 53cae7537537..8c90a02588e7 100644 --- a/Documentation/tools/rtla/common_appendix.txt +++ b/Documentation/tools/rtla/common_appendix.txt @@ -1,5 +1,26 @@ .. SPDX-License-Identifier: GPL-2.0 +SIGINT BEHAVIOR +=============== + +On the first SIGINT, RTLA exits after collecting all outstanding samples up to +the point of receiving the signal. + +When receiving more than one SIGINT, RTLA discards any outstanding samples, and +exits while displaying only samples that have already been processed. + +If SIGINT is received during RTLA cleanup, RTLA exits immediately via +the default signal handler. + +Note: For the purpose of SIGINT behavior, the expiry of duration specified via +the -d/--duration option is treated as equivalent to receiving a SIGINT. For +example, a SIGINT received after duration expired but samples have not been +processed yet will drop any outstanding samples. + +Also note that when using the timerlat tool in BPF mode, samples are processed +in-kernel; RTLA only copies them out to display them to the user. A second +SIGINT does not affect in-kernel sample aggregation. + EXIT STATUS =========== diff --git a/Documentation/tools/rtla/common_timerlat_options.txt b/Documentation/tools/rtla/common_timerlat_options.txt index 07a285fcf7cf..ab159b2cbfe7 100644 --- a/Documentation/tools/rtla/common_timerlat_options.txt +++ b/Documentation/tools/rtla/common_timerlat_options.txt @@ -83,3 +83,15 @@ **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. + +**--stack-format** *format* + + Adjust the format of the stack trace printed during auto-analysis. + + The supported values for *format* are: + + * **truncate** Print the stack trace up to the first unknown address (default). + * **skip** Skip unknown addresses. + * **full** Print the entire stack trace, including unknown addresses. + + For unknown addresses, the raw pointer is printed. diff --git a/tools/build/Makefile.feature b/tools/build/Makefile.feature index 0b7a7c38cb88..2f192d3bf61b 100644 --- a/tools/build/Makefile.feature +++ b/tools/build/Makefile.feature @@ -115,6 +115,7 @@ FEATURE_TESTS_EXTRA := \ hello \ libbabeltrace \ libcapstone \ + libcheck \ libbfd-liberty \ libbfd-liberty-z \ libopencsd \ @@ -176,6 +177,8 @@ ifneq ($(PKG_CONFIG),) $(foreach package,$(FEATURE_PKG_CONFIG),$(call feature_pkg_config,$(package))) endif +FEATURE_CHECK_LDFLAGS-libcheck = -lcheck + # Set FEATURE_CHECK_(C|LD)FLAGS-all for all FEATURE_TESTS features. # If in the future we need per-feature checks/flags for features not # mentioned in this list we need to refactor this ;-). diff --git a/tools/build/feature/Makefile b/tools/build/feature/Makefile index 1fbcb3ce74d2..99bdc8a6d26e 100644 --- a/tools/build/feature/Makefile +++ b/tools/build/feature/Makefile @@ -50,6 +50,7 @@ FILES= \ test-timerfd.bin \ test-libbabeltrace.bin \ test-libcapstone.bin \ + test-libcheck.bin \ test-compile-32.bin \ test-compile-x32.bin \ test-zlib.bin \ @@ -307,6 +308,9 @@ $(OUTPUT)test-libbabeltrace.bin: $(OUTPUT)test-libcapstone.bin: $(BUILD) # -lcapstone provided by $(FEATURE_CHECK_LDFLAGS-libcapstone) +$(OUTPUT)test-libcheck.bin: + $(BUILD) # -lcheck is provided by $(FEATURE_CHECK_LDFLAGS-libcheck) + $(OUTPUT)test-compile-32.bin: $(CC) -m32 -Wall -Werror -o $@ test-compile.c diff --git a/tools/build/feature/test-libcheck.c b/tools/build/feature/test-libcheck.c new file mode 100644 index 000000000000..cfb8d452e9ef --- /dev/null +++ b/tools/build/feature/test-libcheck.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +int main(void) +{ + Suite *s = suite_create("test"); + return s == 0; +} diff --git a/tools/tracing/rtla/Build b/tools/tracing/rtla/Build index 6c9d5b36a315..3ce2e0f567fd 100644 --- a/tools/tracing/rtla/Build +++ b/tools/tracing/rtla/Build @@ -1 +1,2 @@ rtla-y += src/ +unit_tests-y += tests/unit/ diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile index 2701256abaf3..45690ee14544 100644 --- a/tools/tracing/rtla/Makefile +++ b/tools/tracing/rtla/Makefile @@ -33,12 +33,14 @@ DOCSRC := ../../../Documentation/tools/rtla/ FEATURE_TESTS := libtraceevent FEATURE_TESTS += libtracefs FEATURE_TESTS += libcpupower +FEATURE_TESTS += libcheck FEATURE_TESTS += libbpf FEATURE_TESTS += clang-bpf-co-re FEATURE_TESTS += bpftool-skeletons FEATURE_DISPLAY := libtraceevent FEATURE_DISPLAY += libtracefs FEATURE_DISPLAY += libcpupower +FEATURE_DISPLAY += libcheck FEATURE_DISPLAY += libbpf FEATURE_DISPLAY += clang-bpf-co-re FEATURE_DISPLAY += bpftool-skeletons @@ -47,6 +49,7 @@ all: $(RTLA) include $(srctree)/tools/build/Makefile.include include Makefile.rtla +include tests/unit/Makefile.unit # check for dependencies only on required targets NON_CONFIG_TARGETS := clean install tarball doc doc_clean doc_install @@ -109,6 +112,8 @@ clean: doc_clean fixdep-clean $(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 example/timerlat_bpf_action.o + $(Q)rm -f $(UNIT_TESTS) + check: $(RTLA) tests/bpf/bpf_action_map.o RTLA=$(RTLA) BPFTOOL=$(SYSTEM_BPFTOOL) prove -o -f -v tests/ examples: example/timerlat_bpf_action.o diff --git a/tools/tracing/rtla/Makefile.config b/tools/tracing/rtla/Makefile.config index 07ff5e8f3006..0bdd258b76de 100644 --- a/tools/tracing/rtla/Makefile.config +++ b/tools/tracing/rtla/Makefile.config @@ -61,6 +61,14 @@ else $(info Please install libcpupower-dev/kernel-tools-libs-devel) endif +$(call feature_check,libcheck) +ifeq ($(feature-libcheck), 1) + $(call detected,CONFIG_LIBCHECK) +else + $(info libcheck is missing, building without unit tests support.) + $(info Please install check-devel/check) +endif + ifndef BUILD_BPF_SKEL # BPF skeletons are used to implement improved sample collection, enable # them by default. diff --git a/tools/tracing/rtla/README.txt b/tools/tracing/rtla/README.txt index 43e98311d10f..a9faee4dbb3a 100644 --- a/tools/tracing/rtla/README.txt +++ b/tools/tracing/rtla/README.txt @@ -12,6 +12,7 @@ RTLA depends on the following libraries and tools: - libtracefs - libtraceevent - libcpupower (optional, for --deepest-idle-state) + - libcheck (optional, for unit tests) For BPF sample collection support, the following extra dependencies are required: diff --git a/tools/tracing/rtla/src/actions.c b/tools/tracing/rtla/src/actions.c index a42615011962..b0d68b5de08d 100644 --- a/tools/tracing/rtla/src/actions.c +++ b/tools/tracing/rtla/src/actions.c @@ -15,7 +15,7 @@ void actions_init(struct actions *self) { self->size = action_default_size; - self->list = calloc(self->size, sizeof(struct action)); + self->list = calloc_fatal(self->size, sizeof(struct action)); self->len = 0; self->continue_flag = false; @@ -50,8 +50,10 @@ static struct action * actions_new(struct actions *self) { if (self->len >= self->size) { - self->size *= 2; - self->list = realloc(self->list, self->size * sizeof(struct action)); + const size_t new_size = self->size * 2; + + self->list = reallocarray_fatal(self->list, new_size, sizeof(struct action)); + self->size = new_size; } return &self->list[self->len++]; @@ -60,25 +62,20 @@ actions_new(struct actions *self) /* * actions_add_trace_output - add an action to output trace */ -int +void actions_add_trace_output(struct actions *self, const char *trace_output) { struct action *action = actions_new(self); self->present[ACTION_TRACE_OUTPUT] = true; action->type = ACTION_TRACE_OUTPUT; - action->trace_output = calloc(strlen(trace_output) + 1, sizeof(char)); - if (!action->trace_output) - return -1; - strcpy(action->trace_output, trace_output); - - return 0; + action->trace_output = strdup_fatal(trace_output); } /* * actions_add_trace_output - add an action to send signal to a process */ -int +void actions_add_signal(struct actions *self, int signal, int pid) { struct action *action = actions_new(self); @@ -87,42 +84,56 @@ actions_add_signal(struct actions *self, int signal, int pid) action->type = ACTION_SIGNAL; action->signal = signal; action->pid = pid; - - return 0; } /* * actions_add_shell - add an action to execute a shell command */ -int +void actions_add_shell(struct actions *self, const char *command) { struct action *action = actions_new(self); self->present[ACTION_SHELL] = true; action->type = ACTION_SHELL; - action->command = calloc(strlen(command) + 1, sizeof(char)); - if (!action->command) - return -1; - strcpy(action->command, command); - - return 0; + action->command = strdup_fatal(command); } /* * actions_add_continue - add an action to resume measurement */ -int +void actions_add_continue(struct actions *self) { struct action *action = actions_new(self); self->present[ACTION_CONTINUE] = true; action->type = ACTION_CONTINUE; - - return 0; } +static inline const char *__extract_arg(const char *token, const char *opt, size_t opt_len) +{ + const size_t tok_len = strlen(token); + + if (tok_len <= opt_len) + return NULL; + + if (strncmp(token, opt, opt_len)) + return NULL; + + return token + opt_len; +} + +/* + * extract_arg - extract argument value from option token + * @token: option token (e.g., "file=trace.txt") + * @opt: option name to match (e.g., "file") + * + * Returns pointer to argument value after "=" if token matches "opt=", + * otherwise returns NULL. + */ +#define extract_arg(token, opt) __extract_arg(token, opt "=", STRING_LENGTH(opt "=")) + /* * actions_parse - add an action based on text specification */ @@ -132,6 +143,7 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn) enum action_type type = ACTION_NONE; const char *token; char trigger_c[strlen(trigger) + 1]; + const char *arg_value; /* For ACTION_SIGNAL */ int signal = 0, pid = 0; @@ -164,33 +176,36 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn) if (token == NULL) trace_output = tracefn; else { - if (strlen(token) > 5 && strncmp(token, "file=", 5) == 0) { - trace_output = token + 5; - } else { + trace_output = extract_arg(token, "file"); + if (!trace_output) /* Invalid argument */ return -1; - } token = strtok(NULL, ","); if (token != NULL) /* Only one argument allowed */ return -1; } - return actions_add_trace_output(self, trace_output); + actions_add_trace_output(self, trace_output); + break; case ACTION_SIGNAL: /* Takes two arguments, num (signal) and pid */ while (token != NULL) { - if (strlen(token) > 4 && strncmp(token, "num=", 4) == 0) { - 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 if (strtoi(token + 4, &pid)) + arg_value = extract_arg(token, "num"); + if (arg_value) { + if (strtoi(arg_value, &signal)) return -1; } else { - /* Invalid argument */ - return -1; + arg_value = extract_arg(token, "pid"); + if (arg_value) { + if (strncmp_static(arg_value, "parent") == 0) + pid = -1; + else if (strtoi(arg_value, &pid)) + return -1; + } else { + /* Invalid argument */ + return -1; + } } token = strtok(NULL, ","); @@ -200,21 +215,27 @@ actions_parse(struct actions *self, const char *trigger, const char *tracefn) /* Missing argument */ return -1; - return actions_add_signal(self, signal, pid); + actions_add_signal(self, signal, pid); + break; case ACTION_SHELL: if (token == NULL) return -1; - if (strlen(token) > 8 && strncmp(token, "command=", 8) == 0) - return actions_add_shell(self, token + 8); - return -1; + arg_value = extract_arg(token, "command"); + if (!arg_value) + return -1; + actions_add_shell(self, arg_value); + break; case ACTION_CONTINUE: /* Takes no argument */ if (token != NULL) return -1; - return actions_add_continue(self); + actions_add_continue(self); + break; default: return -1; } + + return 0; } /* diff --git a/tools/tracing/rtla/src/actions.h b/tools/tracing/rtla/src/actions.h index fb77069c972b..034048682fef 100644 --- a/tools/tracing/rtla/src/actions.h +++ b/tools/tracing/rtla/src/actions.h @@ -49,9 +49,9 @@ struct actions { void actions_init(struct actions *self); void actions_destroy(struct actions *self); -int actions_add_trace_output(struct actions *self, const char *trace_output); -int actions_add_signal(struct actions *self, int signal, int pid); -int actions_add_shell(struct actions *self, const char *command); -int actions_add_continue(struct actions *self); +void actions_add_trace_output(struct actions *self, const char *trace_output); +void actions_add_signal(struct actions *self, int signal, int pid); +void actions_add_shell(struct actions *self, const char *command); +void actions_add_continue(struct actions *self); int actions_parse(struct actions *self, const char *trigger, const char *tracefn); int actions_perform(struct actions *self); diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c index ceff76a62a30..35e3d3aa922e 100644 --- a/tools/tracing/rtla/src/common.c +++ b/tools/tracing/rtla/src/common.c @@ -5,12 +5,14 @@ #include #include #include -#include #include +#include + #include "common.h" struct trace_instance *trace_inst; volatile int stop_tracing; +int nr_cpus; static void stop_trace(int sig) { @@ -39,6 +41,48 @@ static void set_signals(struct common_params *params) } } +/* + * unset_signals - unsets the signals to stop the tool + */ +static void unset_signals(struct common_params *params) +{ + signal(SIGINT, SIG_DFL); + if (params->duration) { + alarm(0); + signal(SIGALRM, SIG_DFL); + } +} + +/* + * getopt_auto - auto-generates optstring from long_options + */ +int getopt_auto(int argc, char **argv, const struct option *long_opts) +{ + char opts[256]; + int n = 0; + + for (int i = 0; long_opts[i].name; i++) { + if (long_opts[i].val < 32 || long_opts[i].val > 127) + continue; + + if (n + 4 >= sizeof(opts)) + fatal("optstring buffer overflow"); + + opts[n++] = long_opts[i].val; + + if (long_opts[i].has_arg == required_argument) + opts[n++] = ':'; + else if (long_opts[i].has_arg == optional_argument) { + opts[n++] = ':'; + opts[n++] = ':'; + } + } + + opts[n] = '\0'; + + return getopt_long(argc, argv, opts, long_opts, NULL); +} + /* * common_parse_options - parse common command line options * @@ -69,7 +113,7 @@ int common_parse_options(int argc, char **argv, struct common_params *common) }; opterr = 0; - c = getopt_long(argc, argv, "c:C::Dd:e:H:P:", long_options, NULL); + c = getopt_auto(argc, argv, long_options); opterr = 1; switch (c) { @@ -135,7 +179,7 @@ common_apply_config(struct osnoise_tool *tool, struct common_params *params) } if (!params->cpus) { - for (i = 0; i < sysconf(_SC_NPROCESSORS_CONF); i++) + for (i = 0; i < nr_cpus; i++) CPU_SET(i, ¶ms->monitored_cpus); } @@ -175,6 +219,38 @@ out_err: } +/** + * common_threshold_handler - handle latency threshold overflow + * @tool: pointer to the osnoise_tool instance containing trace contexts + * + * Executes the configured threshold actions (e.g., saving trace, printing, + * sending signals). If the continue flag is set (--on-threshold continue), + * restarts the auxiliary trace instances to continue monitoring. + * + * Return: 0 for success, -1 for error. + */ +int +common_threshold_handler(const struct osnoise_tool *tool) +{ + actions_perform(&tool->params->threshold_actions); + + if (!should_continue_tracing(tool->params)) + /* continue flag not set, break */ + return 0; + + /* continue action reached, re-enable tracing */ + if (tool->record && trace_instance_start(&tool->record->trace)) + goto err; + if (tool->aa && trace_instance_start(&tool->aa->trace)) + goto err; + + return 0; + +err: + err_msg("Error restarting trace\n"); + return -1; +} + int run_tool(struct tool_ops *ops, int argc, char *argv[]) { struct common_params *params; @@ -183,6 +259,7 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[]) bool stopped; int retval; + nr_cpus = get_nprocs_conf(); params = ops->parse_args(argc, argv); if (!params) exit(1); @@ -271,8 +348,10 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[]) params->user.cgroup_name = params->cgroup_name; retval = pthread_create(&user_thread, NULL, timerlat_u_dispatcher, ¶ms->user); - if (retval) + if (retval) { err_msg("Error creating timerlat user-space threads\n"); + goto out_trace; + } } retval = ops->enable(tool); @@ -284,7 +363,7 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[]) retval = ops->main(tool); if (retval) - goto out_trace; + goto out_signals; if (params->user_workload && !params->user.stopped_running) { params->user.should_run = 0; @@ -306,6 +385,8 @@ int run_tool(struct tool_ops *ops, int argc, char *argv[]) if (ops->analyze) ops->analyze(tool, stopped); +out_signals: + unset_signals(params); out_trace: trace_events_destroy(&tool->record->trace, params->events); params->events = NULL; @@ -352,17 +433,14 @@ int top_main_loop(struct osnoise_tool *tool) /* stop tracing requested, do not perform actions */ return 0; - actions_perform(¶ms->threshold_actions); + retval = common_threshold_handler(tool); + if (retval) + return retval; - if (!params->threshold_actions.continue_flag) - /* continue flag not set, break */ + + if (!should_continue_tracing(params)) return 0; - /* continue action reached, re-enable tracing */ - if (record) - trace_instance_start(&record->trace); - if (tool->aa) - trace_instance_start(&tool->aa->trace); trace_instance_start(trace); } @@ -403,18 +481,14 @@ int hist_main_loop(struct osnoise_tool *tool) /* stop tracing requested, do not perform actions */ break; - actions_perform(¶ms->threshold_actions); + retval = common_threshold_handler(tool); + if (retval) + return retval; - if (!params->threshold_actions.continue_flag) - /* continue flag not set, break */ - break; + if (!should_continue_tracing(params)) + return 0; - /* continue action reached, re-enable tracing */ - if (tool->record) - trace_instance_start(&tool->record->trace); - if (tool->aa) - trace_instance_start(&tool->aa->trace); - trace_instance_start(&tool->trace); + trace_instance_start(trace); } /* is there still any user-threads ? */ diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h index 7602c5593ef5..51665db4ffce 100644 --- a/tools/tracing/rtla/src/common.h +++ b/tools/tracing/rtla/src/common.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ #pragma once +#include #include "actions.h" #include "timerlat_u.h" #include "trace.h" @@ -107,7 +108,9 @@ struct common_params { struct timerlat_u_params user; }; -#define for_each_monitored_cpu(cpu, nr_cpus, common) \ +extern int nr_cpus; + +#define for_each_monitored_cpu(cpu, common) \ for (cpu = 0; cpu < nr_cpus; cpu++) \ if (!(common)->cpus || CPU_ISSET(cpu, &(common)->monitored_cpus)) @@ -143,6 +146,24 @@ struct tool_ops { void (*free)(struct osnoise_tool *tool); }; +/** + * should_continue_tracing - check if tracing should continue after threshold + * @params: pointer to the common parameters structure + * + * Returns true if the continue action was configured (--on-threshold continue), + * indicating that tracing should be restarted after handling the threshold event. + * + * Return: 1 if tracing should continue, 0 otherwise. + */ +static inline int +should_continue_tracing(const struct common_params *params) +{ + return params->threshold_actions.continue_flag; +} + +int +common_threshold_handler(const struct osnoise_tool *tool); + int osnoise_set_cpus(struct osnoise_context *context, char *cpus); void osnoise_restore_cpus(struct osnoise_context *context); @@ -156,6 +177,7 @@ 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 getopt_auto(int argc, char **argv, const struct option *long_opts); 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); diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c index 945eb61efc46..2db3db155c44 100644 --- a/tools/tracing/rtla/src/osnoise.c +++ b/tools/tracing/rtla/src/osnoise.c @@ -62,7 +62,7 @@ int osnoise_set_cpus(struct osnoise_context *context, char *cpus) if (!context->curr_cpus) return -1; - snprintf(buffer, 1024, "%s\n", cpus); + snprintf(buffer, ARRAY_SIZE(buffer), "%s\n", cpus); debug_msg("setting cpus to %s from %s", cpus, context->orig_cpus); @@ -938,9 +938,7 @@ struct osnoise_context *osnoise_context_alloc(void) { struct osnoise_context *context; - context = calloc(1, sizeof(*context)); - if (!context) - return NULL; + context = calloc_fatal(1, sizeof(*context)); context->orig_stop_us = OSNOISE_OPTION_INIT_VAL; context->stop_us = OSNOISE_OPTION_INIT_VAL; @@ -1017,24 +1015,16 @@ void osnoise_destroy_tool(struct osnoise_tool *top) struct osnoise_tool *osnoise_init_tool(char *tool_name) { struct osnoise_tool *top; - int retval; - - top = calloc(1, sizeof(*top)); - if (!top) - return NULL; + top = calloc_fatal(1, sizeof(*top)); top->context = osnoise_context_alloc(); - if (!top->context) - goto out_err; - retval = trace_instance_init(&top->trace, tool_name); - if (retval) - goto out_err; + if (trace_instance_init(&top->trace, tool_name)) { + osnoise_destroy_tool(top); + return NULL; + } return top; -out_err: - osnoise_destroy_tool(top); - return NULL; } /* @@ -1220,7 +1210,7 @@ int osnoise_main(int argc, char *argv[]) if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) { osnoise_usage(0); - } else if (strncmp(argv[1], "-", 1) == 0) { + } else if (str_has_prefix(argv[1], "-")) { /* the user skipped the tool, call the default one */ run_tool(&osnoise_top_ops, argc, argv); exit(0); diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index 9d70ea34807f..8ad816b80265 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -29,7 +29,6 @@ struct osnoise_hist_data { struct osnoise_hist_cpu *hist; int entries; int bucket_size; - int nr_cpus; }; /* @@ -41,7 +40,7 @@ osnoise_free_histogram(struct osnoise_hist_data *data) int cpu; /* one histogram for IRQ and one for thread, per CPU */ - for (cpu = 0; cpu < data->nr_cpus; cpu++) { + for (cpu = 0; cpu < nr_cpus; cpu++) { if (data->hist[cpu].samples) free(data->hist[cpu].samples); } @@ -62,7 +61,7 @@ static void osnoise_free_hist_tool(struct osnoise_tool *tool) * osnoise_alloc_histogram - alloc runtime data */ static struct osnoise_hist_data -*osnoise_alloc_histogram(int nr_cpus, int entries, int bucket_size) +*osnoise_alloc_histogram(int entries, int bucket_size) { struct osnoise_hist_data *data; int cpu; @@ -73,7 +72,6 @@ static struct osnoise_hist_data data->entries = entries; data->bucket_size = bucket_size; - data->nr_cpus = nr_cpus; data->hist = calloc(1, sizeof(*data->hist) * nr_cpus); if (!data->hist) @@ -246,7 +244,7 @@ static void osnoise_hist_header(struct osnoise_tool *tool) if (!params->common.hist.no_index) trace_seq_printf(s, "Index"); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].count) continue; @@ -275,8 +273,7 @@ osnoise_print_summary(struct osnoise_params *params, if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "count:"); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { - + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].count) continue; @@ -287,7 +284,7 @@ osnoise_print_summary(struct osnoise_params *params, if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "min: "); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].count) continue; @@ -300,7 +297,7 @@ osnoise_print_summary(struct osnoise_params *params, if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "avg: "); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].count) continue; @@ -316,7 +313,7 @@ osnoise_print_summary(struct osnoise_params *params, if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "max: "); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].count) continue; @@ -351,7 +348,7 @@ osnoise_print_stats(struct osnoise_tool *tool) trace_seq_printf(trace->seq, "%-6d", bucket * data->bucket_size); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].count) continue; @@ -387,7 +384,7 @@ osnoise_print_stats(struct osnoise_tool *tool) if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "over: "); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].count) continue; @@ -466,9 +463,7 @@ static struct common_params int c; char *trace_output = NULL; - params = calloc(1, sizeof(*params)); - if (!params) - exit(1); + params = calloc_fatal(1, sizeof(*params)); actions_init(¶ms->common.threshold_actions); actions_init(¶ms->common.end_actions); @@ -506,8 +501,7 @@ static struct common_params if (common_parse_options(argc, argv, ¶ms->common)) continue; - c = getopt_long(argc, argv, "a:b:E:hp:r:s:S:t::T:01234:5:6:7:", - long_options, NULL); + c = getopt_auto(argc, argv, long_options); /* detect the end of the options. */ if (c == -1) @@ -579,22 +573,16 @@ static struct common_params params->common.hist.with_zeros = 1; break; case '4': /* trigger */ - if (params->common.events) { - retval = trace_event_add_trigger(params->common.events, optarg); - if (retval) - fatal("Error adding trigger %s", optarg); - } else { + if (params->common.events) + trace_event_add_trigger(params->common.events, optarg); + else fatal("--trigger requires a previous -e"); - } break; case '5': /* filter */ - if (params->common.events) { - retval = trace_event_add_filter(params->common.events, optarg); - if (retval) - fatal("Error adding filter %s", optarg); - } else { + if (params->common.events) + trace_event_add_filter(params->common.events, optarg); + else fatal("--filter requires a previous -e"); - } break; case '6': params->common.warmup = get_llong_from_str(optarg); @@ -647,15 +635,12 @@ static struct osnoise_tool *osnoise_init_hist(struct common_params *params) { struct osnoise_tool *tool; - int nr_cpus; - - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); tool = osnoise_init_tool("osnoise_hist"); if (!tool) return NULL; - tool->data = osnoise_alloc_histogram(nr_cpus, params->hist.entries, + tool->data = osnoise_alloc_histogram(params->hist.entries, params->hist.bucket_size); if (!tool->data) goto out_err; diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index d54d47947fb4..244bdce022ad 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -31,7 +31,6 @@ struct osnoise_top_cpu { struct osnoise_top_data { struct osnoise_top_cpu *cpu_data; - int nr_cpus; }; /* @@ -51,7 +50,7 @@ static void osnoise_free_top_tool(struct osnoise_tool *tool) /* * osnoise_alloc_histogram - alloc runtime data */ -static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus) +static struct osnoise_top_data *osnoise_alloc_top(void) { struct osnoise_top_data *data; @@ -59,8 +58,6 @@ static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus) if (!data) return NULL; - data->nr_cpus = nr_cpus; - /* one set of histograms per CPU */ data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus); if (!data->cpu_data) @@ -232,18 +229,14 @@ osnoise_print_stats(struct osnoise_tool *top) { struct osnoise_params *params = to_osnoise_params(top->params); struct trace_instance *trace = &top->trace; - static int nr_cpus = -1; int i; - if (nr_cpus == -1) - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); - if (!params->common.quiet) clear_terminal(trace->seq); osnoise_top_header(top); - for_each_monitored_cpu(i, nr_cpus, ¶ms->common) { + for_each_monitored_cpu(i, ¶ms->common) { osnoise_top_print(top, i); } @@ -319,9 +312,7 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv) int c; char *trace_output = NULL; - params = calloc(1, sizeof(*params)); - if (!params) - exit(1); + params = calloc_fatal(1, sizeof(*params)); actions_init(¶ms->common.threshold_actions); actions_init(¶ms->common.end_actions); @@ -358,8 +349,7 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv) if (common_parse_options(argc, argv, ¶ms->common)) continue; - c = getopt_long(argc, argv, "a:hp:qr:s:S:t::T:0:1:2:3:", - long_options, NULL); + c = getopt_auto(argc, argv, long_options); /* Detect the end of the options. */ if (c == -1) @@ -410,22 +400,16 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv) params->threshold = get_llong_from_str(optarg); break; case '0': /* trigger */ - if (params->common.events) { - retval = trace_event_add_trigger(params->common.events, optarg); - if (retval) - fatal("Error adding trigger %s", optarg); - } else { + if (params->common.events) + trace_event_add_trigger(params->common.events, optarg); + else fatal("--trigger requires a previous -e"); - } break; case '1': /* filter */ - if (params->common.events) { - retval = trace_event_add_filter(params->common.events, optarg); - if (retval) - fatal("Error adding filter %s", optarg); - } else { + if (params->common.events) + trace_event_add_filter(params->common.events, optarg); + else fatal("--filter requires a previous -e"); - } break; case '2': params->common.warmup = get_llong_from_str(optarg); @@ -495,15 +479,12 @@ out_err: struct osnoise_tool *osnoise_init_top(struct common_params *params) { struct osnoise_tool *tool; - int nr_cpus; - - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); tool = osnoise_init_tool("osnoise_top"); if (!tool) return NULL; - tool->data = osnoise_alloc_top(nr_cpus); + tool->data = osnoise_alloc_top(); if (!tool->data) { osnoise_destroy_tool(tool); return NULL; diff --git a/tools/tracing/rtla/src/timerlat.c b/tools/tracing/rtla/src/timerlat.c index 8f8811f7a13b..f8c057518d22 100644 --- a/tools/tracing/rtla/src/timerlat.c +++ b/tools/tracing/rtla/src/timerlat.c @@ -28,12 +28,13 @@ int timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params) { int retval; + const char *const rtla_no_bpf = getenv("RTLA_NO_BPF"); /* * 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) { + if (rtla_no_bpf && strncmp_static(rtla_no_bpf, "1") == 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")) { @@ -99,7 +100,7 @@ out_err: int timerlat_enable(struct osnoise_tool *tool) { struct timerlat_params *params = to_timerlat_params(tool->params); - int retval, nr_cpus, i; + int retval, i; if (params->dma_latency >= 0) { dma_latency_fd = set_cpu_dma_latency(params->dma_latency); @@ -115,9 +116,7 @@ int timerlat_enable(struct osnoise_tool *tool) return -1; } - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); - - for_each_monitored_cpu(i, nr_cpus, ¶ms->common) { + for_each_monitored_cpu(i, ¶ms->common) { if (save_cpu_idle_disable_state(i) < 0) { err_msg("Could not save cpu idle state.\n"); return -1; @@ -134,7 +133,7 @@ int timerlat_enable(struct osnoise_tool *tool) if (!tool->aa) return -1; - retval = timerlat_aa_init(tool->aa, params->dump_tasks); + retval = timerlat_aa_init(tool->aa, params->dump_tasks, params->stack_format); if (retval) { err_msg("Failed to enable the auto analysis instance\n"); return retval; @@ -214,14 +213,13 @@ void timerlat_analyze(struct osnoise_tool *tool, bool stopped) void timerlat_free(struct osnoise_tool *tool) { struct timerlat_params *params = to_timerlat_params(tool->params); - int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); int i; timerlat_aa_destroy(); if (dma_latency_fd >= 0) close(dma_latency_fd); if (params->deepest_idle_state >= -1) { - for_each_monitored_cpu(i, nr_cpus, ¶ms->common) { + for_each_monitored_cpu(i, ¶ms->common) { restore_cpu_idle_disable_state(i); } } @@ -272,7 +270,7 @@ int timerlat_main(int argc, char *argv[]) if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) { timerlat_usage(0); - } else if (strncmp(argv[1], "-", 1) == 0) { + } else if (str_has_prefix(argv[1], "-")) { /* the user skipped the tool, call the default one */ run_tool(&timerlat_top_ops, argc, argv); exit(0); diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h index 8dd5d134ce08..364203a29abd 100644 --- a/tools/tracing/rtla/src/timerlat.h +++ b/tools/tracing/rtla/src/timerlat.h @@ -28,6 +28,7 @@ struct timerlat_params { int deepest_idle_state; enum timerlat_tracing_mode mode; const char *bpf_action_program; + enum stack_format stack_format; }; #define to_timerlat_params(ptr) container_of(ptr, struct timerlat_params, common) diff --git a/tools/tracing/rtla/src/timerlat_aa.c b/tools/tracing/rtla/src/timerlat_aa.c index 31e66ea2b144..a121ff9d2c57 100644 --- a/tools/tracing/rtla/src/timerlat_aa.c +++ b/tools/tracing/rtla/src/timerlat_aa.c @@ -102,8 +102,8 @@ struct timerlat_aa_data { * The analysis context and system wide view */ struct timerlat_aa_context { - int nr_cpus; int dump_tasks; + enum stack_format stack_format; /* per CPU data */ struct timerlat_aa_data *taa_data; @@ -417,8 +417,8 @@ static int timerlat_aa_softirq_handler(struct trace_seq *s, struct tep_record *r taa_data->thread_softirq_sum += duration; trace_seq_printf(taa_data->softirqs_seq, " %24s:%-3llu %.*s %9.2f us\n", - softirq_name[vector], vector, - 24, spaces, + vector < ARRAY_SIZE(softirq_name) ? softirq_name[vector] : "UNKNOWN", + vector, 24, spaces, ns_to_usf(duration)); return 0; } @@ -481,23 +481,43 @@ static int timerlat_aa_stack_handler(struct trace_seq *s, struct tep_record *rec { struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx(); struct timerlat_aa_data *taa_data = timerlat_aa_get_data(taa_ctx, record->cpu); + enum stack_format stack_format = taa_ctx->stack_format; unsigned long *caller; const char *function; - int val, i; + int val; + unsigned long long i; trace_seq_reset(taa_data->stack_seq); trace_seq_printf(taa_data->stack_seq, " Blocking thread stack trace\n"); caller = tep_get_field_raw(s, event, "caller", record, &val, 1); + if (caller) { - for (i = 0; ; i++) { + unsigned long long size; + unsigned long long max_entries; + + if (tep_get_field_val(s, event, "size", record, &size, 1) == 0) + max_entries = size < 64 ? size : 64; + else + max_entries = 64; + + for (i = 0; i < max_entries; i++) { function = tep_find_function(taa_ctx->tool->trace.tep, caller[i]); - if (!function) - break; - trace_seq_printf(taa_data->stack_seq, " %.*s -> %s\n", - 14, spaces, function); + if (!function) { + if (stack_format == STACK_FORMAT_TRUNCATE) + break; + else if (stack_format == STACK_FORMAT_SKIP) + continue; + else if (stack_format == STACK_FORMAT_FULL) + trace_seq_printf(taa_data->stack_seq, " %.*s -> 0x%lx\n", + 14, spaces, caller[i]); + } else { + trace_seq_printf(taa_data->stack_seq, " %.*s -> %s\n", + 14, spaces, function); + } } } + return 0; } @@ -738,7 +758,7 @@ void timerlat_auto_analysis(int irq_thresh, int thread_thresh) irq_thresh = irq_thresh * 1000; thread_thresh = thread_thresh * 1000; - for (cpu = 0; cpu < taa_ctx->nr_cpus; cpu++) { + for (cpu = 0; cpu < nr_cpus; cpu++) { taa_data = timerlat_aa_get_data(taa_ctx, cpu); if (irq_thresh && taa_data->tlat_irq_latency >= irq_thresh) { @@ -766,7 +786,7 @@ void timerlat_auto_analysis(int irq_thresh, int thread_thresh) printf("\n"); printf("Printing CPU tasks:\n"); - for (cpu = 0; cpu < taa_ctx->nr_cpus; cpu++) { + for (cpu = 0; cpu < nr_cpus; cpu++) { taa_data = timerlat_aa_get_data(taa_ctx, cpu); tep = taa_ctx->tool->trace.tep; @@ -792,7 +812,7 @@ static void timerlat_aa_destroy_seqs(struct timerlat_aa_context *taa_ctx) if (!taa_ctx->taa_data) return; - for (i = 0; i < taa_ctx->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { taa_data = timerlat_aa_get_data(taa_ctx, i); if (taa_data->prev_irqs_seq) { @@ -842,7 +862,7 @@ static int timerlat_aa_init_seqs(struct timerlat_aa_context *taa_ctx) struct timerlat_aa_data *taa_data; int i; - for (i = 0; i < taa_ctx->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { taa_data = timerlat_aa_get_data(taa_ctx, i); @@ -1020,9 +1040,8 @@ out_ctx: * * Returns 0 on success, -1 otherwise. */ -int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks) +int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks, enum stack_format stack_format) { - int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); struct timerlat_aa_context *taa_ctx; int retval; @@ -1032,9 +1051,9 @@ int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks) __timerlat_aa_ctx = taa_ctx; - taa_ctx->nr_cpus = nr_cpus; taa_ctx->tool = tool; taa_ctx->dump_tasks = dump_tasks; + taa_ctx->stack_format = stack_format; taa_ctx->taa_data = calloc(nr_cpus, sizeof(*taa_ctx->taa_data)); if (!taa_ctx->taa_data) diff --git a/tools/tracing/rtla/src/timerlat_aa.h b/tools/tracing/rtla/src/timerlat_aa.h index cea4bb1531a8..a11b5f30cdce 100644 --- a/tools/tracing/rtla/src/timerlat_aa.h +++ b/tools/tracing/rtla/src/timerlat_aa.h @@ -3,7 +3,7 @@ * Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira */ -int timerlat_aa_init(struct osnoise_tool *tool, int dump_task); +int timerlat_aa_init(struct osnoise_tool *tool, int dump_task, enum stack_format stack_format); void timerlat_aa_destroy(void); void timerlat_auto_analysis(int irq_thresh, int thread_thresh); diff --git a/tools/tracing/rtla/src/timerlat_bpf.c b/tools/tracing/rtla/src/timerlat_bpf.c index 05adf18303df..dd3cf71d74e5 100644 --- a/tools/tracing/rtla/src/timerlat_bpf.c +++ b/tools/tracing/rtla/src/timerlat_bpf.c @@ -147,24 +147,23 @@ static int get_value(struct bpf_map *map_irq, int key, long long *value_irq, long long *value_thread, - long long *value_user, - int cpus) + long long *value_user) { int err; err = bpf_map__lookup_elem(map_irq, &key, sizeof(unsigned int), value_irq, - sizeof(long long) * cpus, 0); + sizeof(long long) * nr_cpus, 0); if (err) return err; err = bpf_map__lookup_elem(map_thread, &key, sizeof(unsigned int), value_thread, - sizeof(long long) * cpus, 0); + sizeof(long long) * nr_cpus, 0); if (err) return err; err = bpf_map__lookup_elem(map_user, &key, sizeof(unsigned int), value_user, - sizeof(long long) * cpus, 0); + sizeof(long long) * nr_cpus, 0); if (err) return err; return 0; @@ -176,13 +175,12 @@ static int get_value(struct bpf_map *map_irq, int timerlat_bpf_get_hist_value(int key, long long *value_irq, long long *value_thread, - long long *value_user, - int cpus) + long long *value_user) { return get_value(bpf->maps.hist_irq, bpf->maps.hist_thread, bpf->maps.hist_user, - key, value_irq, value_thread, value_user, cpus); + key, value_irq, value_thread, value_user); } /* @@ -191,13 +189,12 @@ int timerlat_bpf_get_hist_value(int key, int timerlat_bpf_get_summary_value(enum summary_field key, long long *value_irq, long long *value_thread, - long long *value_user, - int cpus) + long long *value_user) { return get_value(bpf->maps.summary_irq, bpf->maps.summary_thread, bpf->maps.summary_user, - key, value_irq, value_thread, value_user, cpus); + key, value_irq, value_thread, value_user); } /* diff --git a/tools/tracing/rtla/src/timerlat_bpf.h b/tools/tracing/rtla/src/timerlat_bpf.h index f7c5675737fe..33f0df6a9060 100644 --- a/tools/tracing/rtla/src/timerlat_bpf.h +++ b/tools/tracing/rtla/src/timerlat_bpf.h @@ -22,13 +22,11 @@ int timerlat_bpf_restart_tracing(void); int timerlat_bpf_get_hist_value(int key, long long *value_irq, long long *value_thread, - long long *value_user, - int cpus); + long long *value_user); int timerlat_bpf_get_summary_value(enum summary_field key, long long *value_irq, long long *value_thread, - long long *value_user, - int cpus); + long long *value_user); int timerlat_load_bpf_action_program(const char *program_path); static inline int have_libbpf_support(void) { return 1; } #else @@ -44,16 +42,14 @@ static inline int timerlat_bpf_restart_tracing(void) { return -1; }; static inline int timerlat_bpf_get_hist_value(int key, long long *value_irq, long long *value_thread, - long long *value_user, - int cpus) + long long *value_user) { return -1; } static inline int timerlat_bpf_get_summary_value(enum summary_field key, long long *value_irq, long long *value_thread, - long long *value_user, - int cpus) + long long *value_user) { return -1; } diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 4e8c38a61197..79142af4f566 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -17,6 +17,7 @@ #include "timerlat.h" #include "timerlat_aa.h" #include "timerlat_bpf.h" +#include "common.h" struct timerlat_hist_cpu { int *irq; @@ -44,7 +45,6 @@ struct timerlat_hist_data { struct timerlat_hist_cpu *hist; int entries; int bucket_size; - int nr_cpus; }; /* @@ -56,7 +56,7 @@ timerlat_free_histogram(struct timerlat_hist_data *data) int cpu; /* one histogram for IRQ and one for thread, per CPU */ - for (cpu = 0; cpu < data->nr_cpus; cpu++) { + for (cpu = 0; cpu < nr_cpus; cpu++) { if (data->hist[cpu].irq) free(data->hist[cpu].irq); @@ -83,7 +83,7 @@ static void timerlat_free_histogram_tool(struct osnoise_tool *tool) * timerlat_alloc_histogram - alloc runtime data */ static struct timerlat_hist_data -*timerlat_alloc_histogram(int nr_cpus, int entries, int bucket_size) +*timerlat_alloc_histogram(int entries, int bucket_size) { struct timerlat_hist_data *data; int cpu; @@ -94,7 +94,6 @@ static struct timerlat_hist_data data->entries = entries; data->bucket_size = bucket_size; - data->nr_cpus = nr_cpus; /* one set of histograms per CPU */ data->hist = calloc(1, sizeof(*data->hist) * nr_cpus); @@ -204,17 +203,17 @@ static int timerlat_hist_bpf_pull_data(struct osnoise_tool *tool) { struct timerlat_hist_data *data = tool->data; int i, j, err; - long long value_irq[data->nr_cpus], - value_thread[data->nr_cpus], - value_user[data->nr_cpus]; + long long value_irq[nr_cpus], + value_thread[nr_cpus], + value_user[nr_cpus]; /* Pull histogram */ for (i = 0; i < data->entries; i++) { err = timerlat_bpf_get_hist_value(i, value_irq, value_thread, - value_user, data->nr_cpus); + value_user); if (err) return err; - for (j = 0; j < data->nr_cpus; j++) { + for (j = 0; j < nr_cpus; j++) { data->hist[j].irq[i] = value_irq[j]; data->hist[j].thread[i] = value_thread[j]; data->hist[j].user[i] = value_user[j]; @@ -223,55 +222,50 @@ static int timerlat_hist_bpf_pull_data(struct osnoise_tool *tool) /* Pull summary */ err = timerlat_bpf_get_summary_value(SUMMARY_COUNT, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->hist[i].irq_count = value_irq[i]; data->hist[i].thread_count = value_thread[i]; data->hist[i].user_count = value_user[i]; } err = timerlat_bpf_get_summary_value(SUMMARY_MIN, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->hist[i].min_irq = value_irq[i]; data->hist[i].min_thread = value_thread[i]; data->hist[i].min_user = value_user[i]; } err = timerlat_bpf_get_summary_value(SUMMARY_MAX, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->hist[i].max_irq = value_irq[i]; data->hist[i].max_thread = value_thread[i]; data->hist[i].max_user = value_user[i]; } err = timerlat_bpf_get_summary_value(SUMMARY_SUM, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->hist[i].sum_irq = value_irq[i]; data->hist[i].sum_thread = value_thread[i]; data->hist[i].sum_user = value_user[i]; } err = timerlat_bpf_get_summary_value(SUMMARY_OVERFLOW, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->hist[i].irq[data->entries] = value_irq[i]; data->hist[i].thread[data->entries] = value_thread[i]; data->hist[i].user[data->entries] = value_user[i]; @@ -305,7 +299,7 @@ static void timerlat_hist_header(struct osnoise_tool *tool) if (!params->common.hist.no_index) trace_seq_printf(s, "Index"); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) continue; @@ -357,7 +351,7 @@ timerlat_print_summary(struct timerlat_params *params, if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "count:"); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) continue; @@ -379,7 +373,7 @@ timerlat_print_summary(struct timerlat_params *params, if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "min: "); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) continue; @@ -407,7 +401,7 @@ timerlat_print_summary(struct timerlat_params *params, if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "avg: "); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) continue; @@ -435,7 +429,7 @@ timerlat_print_summary(struct timerlat_params *params, if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "max: "); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) continue; @@ -480,7 +474,7 @@ timerlat_print_stats_all(struct timerlat_params *params, sum.min_thread = ~0; sum.min_user = ~0; - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) continue; @@ -627,7 +621,7 @@ timerlat_print_stats(struct osnoise_tool *tool) trace_seq_printf(trace->seq, "%-6d", bucket * data->bucket_size); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) continue; @@ -665,7 +659,7 @@ timerlat_print_stats(struct osnoise_tool *tool) if (!params->common.hist.no_index) trace_seq_printf(trace->seq, "over: "); - for_each_monitored_cpu(cpu, data->nr_cpus, ¶ms->common) { + for_each_monitored_cpu(cpu, ¶ms->common) { if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count) continue; @@ -747,6 +741,7 @@ static void timerlat_hist_usage(void) " --on-threshold : 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 : load and execute BPF program when latency threshold is exceeded", + " --stack-format : set the stack format (truncate, skip, full)", NULL, }; @@ -766,9 +761,7 @@ static struct common_params int c; char *trace_output = NULL; - params = calloc(1, sizeof(*params)); - if (!params) - exit(1); + params = calloc_fatal(1, sizeof(*params)); actions_init(¶ms->common.threshold_actions); actions_init(¶ms->common.end_actions); @@ -787,6 +780,9 @@ static struct common_params /* default to BPF mode */ params->mode = TRACING_MODE_BPF; + /* default to truncate stack format */ + params->stack_format = STACK_FORMAT_TRUNCATE; + while (1) { static struct option long_options[] = { {"auto", required_argument, 0, 'a'}, @@ -819,14 +815,14 @@ static struct common_params {"on-threshold", required_argument, 0, '\5'}, {"on-end", required_argument, 0, '\6'}, {"bpf-action", required_argument, 0, '\7'}, + {"stack-format", required_argument, 0, '\10'}, {0, 0, 0, 0} }; if (common_parse_options(argc, argv, ¶ms->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); + c = getopt_auto(argc, argv, long_options); /* detect the end of the options. */ if (c == -1) @@ -914,22 +910,16 @@ static struct common_params params->common.hist.with_zeros = 1; break; case '6': /* trigger */ - if (params->common.events) { - retval = trace_event_add_trigger(params->common.events, optarg); - if (retval) - fatal("Error adding trigger %s", optarg); - } else { + if (params->common.events) + trace_event_add_trigger(params->common.events, optarg); + else fatal("--trigger requires a previous -e"); - } break; case '7': /* filter */ - if (params->common.events) { - retval = trace_event_add_filter(params->common.events, optarg); - if (retval) - fatal("Error adding filter %s", optarg); - } else { + if (params->common.events) + trace_event_add_filter(params->common.events, optarg); + else fatal("--filter requires a previous -e"); - } break; case '8': params->dma_latency = get_llong_from_str(optarg); @@ -966,6 +956,11 @@ static struct common_params case '\7': params->bpf_action_program = optarg; break; + case '\10': + params->stack_format = parse_stack_format(optarg); + if (params->stack_format == -1) + fatal("Invalid --stack-format option"); + break; default: fatal("Invalid option"); } @@ -1031,15 +1026,12 @@ static struct osnoise_tool *timerlat_init_hist(struct common_params *params) { struct osnoise_tool *tool; - int nr_cpus; - - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); tool = osnoise_init_tool("timerlat_hist"); if (!tool) return NULL; - tool->data = timerlat_alloc_histogram(nr_cpus, params->hist.entries, + tool->data = timerlat_alloc_histogram(params->hist.entries, params->hist.bucket_size); if (!tool->data) goto out_err; @@ -1056,7 +1048,6 @@ out_err: static int timerlat_hist_bpf_main_loop(struct osnoise_tool *tool) { - struct timerlat_params *params = to_timerlat_params(tool->params); int retval; while (!stop_tracing) { @@ -1064,18 +1055,17 @@ static int timerlat_hist_bpf_main_loop(struct osnoise_tool *tool) if (!stop_tracing) { /* Threshold overflow, perform actions on threshold */ - actions_perform(¶ms->common.threshold_actions); + retval = common_threshold_handler(tool); + if (retval) + return retval; - if (!params->common.threshold_actions.continue_flag) - /* continue flag not set, break */ + if (!should_continue_tracing(tool->params)) break; - /* continue action reached, re-enable tracing */ - if (tool->record) - trace_instance_start(&tool->record->trace); - if (tool->aa) - trace_instance_start(&tool->aa->trace); - timerlat_bpf_restart_tracing(); + if (timerlat_bpf_restart_tracing()) { + err_msg("Error restarting BPF trace\n"); + return -1; + } } } timerlat_bpf_detach(); diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index 284b74773c2b..64cbdcc878b0 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -17,6 +17,7 @@ #include "timerlat.h" #include "timerlat_aa.h" #include "timerlat_bpf.h" +#include "common.h" struct timerlat_top_cpu { unsigned long long irq_count; @@ -41,7 +42,6 @@ struct timerlat_top_cpu { struct timerlat_top_data { struct timerlat_top_cpu *cpu_data; - int nr_cpus; }; /* @@ -62,7 +62,7 @@ static void timerlat_free_top_tool(struct osnoise_tool *tool) /* * timerlat_alloc_histogram - alloc runtime data */ -static struct timerlat_top_data *timerlat_alloc_top(int nr_cpus) +static struct timerlat_top_data *timerlat_alloc_top(void) { struct timerlat_top_data *data; int cpu; @@ -71,8 +71,6 @@ static struct timerlat_top_data *timerlat_alloc_top(int nr_cpus) if (!data) return NULL; - data->nr_cpus = nr_cpus; - /* one set of histograms per CPU */ data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus); if (!data->cpu_data) @@ -190,61 +188,56 @@ static int timerlat_top_bpf_pull_data(struct osnoise_tool *tool) { struct timerlat_top_data *data = tool->data; int i, err; - long long value_irq[data->nr_cpus], - value_thread[data->nr_cpus], - value_user[data->nr_cpus]; + long long value_irq[nr_cpus], + value_thread[nr_cpus], + value_user[nr_cpus]; /* Pull summary */ err = timerlat_bpf_get_summary_value(SUMMARY_CURRENT, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->cpu_data[i].cur_irq = value_irq[i]; data->cpu_data[i].cur_thread = value_thread[i]; data->cpu_data[i].cur_user = value_user[i]; } err = timerlat_bpf_get_summary_value(SUMMARY_COUNT, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->cpu_data[i].irq_count = value_irq[i]; data->cpu_data[i].thread_count = value_thread[i]; data->cpu_data[i].user_count = value_user[i]; } err = timerlat_bpf_get_summary_value(SUMMARY_MIN, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->cpu_data[i].min_irq = value_irq[i]; data->cpu_data[i].min_thread = value_thread[i]; data->cpu_data[i].min_user = value_user[i]; } err = timerlat_bpf_get_summary_value(SUMMARY_MAX, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->cpu_data[i].max_irq = value_irq[i]; data->cpu_data[i].max_thread = value_thread[i]; data->cpu_data[i].max_user = value_user[i]; } err = timerlat_bpf_get_summary_value(SUMMARY_SUM, - value_irq, value_thread, value_user, - data->nr_cpus); + value_irq, value_thread, value_user); if (err) return err; - for (i = 0; i < data->nr_cpus; i++) { + for (i = 0; i < nr_cpus; i++) { data->cpu_data[i].sum_irq = value_irq[i]; data->cpu_data[i].sum_thread = value_thread[i]; data->cpu_data[i].sum_user = value_user[i]; @@ -442,15 +435,11 @@ timerlat_print_stats(struct osnoise_tool *top) struct timerlat_params *params = to_timerlat_params(top->params); struct trace_instance *trace = &top->trace; struct timerlat_top_cpu summary; - static int nr_cpus = -1; int i; if (params->common.aa_only) return; - if (nr_cpus == -1) - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); - if (!params->common.quiet) clear_terminal(trace->seq); @@ -458,7 +447,7 @@ timerlat_print_stats(struct osnoise_tool *top) timerlat_top_header(params, top); - for_each_monitored_cpu(i, nr_cpus, ¶ms->common) { + for_each_monitored_cpu(i, ¶ms->common) { timerlat_top_print(top, i); timerlat_top_update_sum(top, i, &summary); } @@ -518,6 +507,7 @@ static void timerlat_top_usage(void) " --on-threshold : 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 : load and execute BPF program when latency threshold is exceeded", + " --stack-format : set the stack format (truncate, skip, full)", NULL, }; @@ -537,9 +527,7 @@ static struct common_params int c; char *trace_output = NULL; - params = calloc(1, sizeof(*params)); - if (!params) - exit(1); + params = calloc_fatal(1, sizeof(*params)); actions_init(¶ms->common.threshold_actions); actions_init(¶ms->common.end_actions); @@ -556,6 +544,9 @@ static struct common_params /* default to BPF mode */ params->mode = TRACING_MODE_BPF; + /* default to truncate stack format */ + params->stack_format = STACK_FORMAT_TRUNCATE; + while (1) { static struct option long_options[] = { {"auto", required_argument, 0, 'a'}, @@ -582,14 +573,14 @@ static struct common_params {"on-threshold", required_argument, 0, '9'}, {"on-end", required_argument, 0, '\1'}, {"bpf-action", required_argument, 0, '\2'}, + {"stack-format", required_argument, 0, '\3'}, {0, 0, 0, 0} }; if (common_parse_options(argc, argv, ¶ms->common)) continue; - c = getopt_long(argc, argv, "a:hi:knp:qs:t::T:uU0:1:2:345:6:7:", - long_options, NULL); + c = getopt_auto(argc, argv, long_options); /* detect the end of the options. */ if (c == -1) @@ -664,22 +655,16 @@ static struct common_params params->common.user_data = true; break; case '0': /* trigger */ - if (params->common.events) { - retval = trace_event_add_trigger(params->common.events, optarg); - if (retval) - fatal("Error adding trigger %s", optarg); - } else { + if (params->common.events) + trace_event_add_trigger(params->common.events, optarg); + else fatal("--trigger requires a previous -e"); - } break; case '1': /* filter */ - if (params->common.events) { - retval = trace_event_add_filter(params->common.events, optarg); - if (retval) - fatal("Error adding filter %s", optarg); - } else { + if (params->common.events) + trace_event_add_filter(params->common.events, optarg); + else fatal("--filter requires a previous -e"); - } break; case '2': /* dma-latency */ params->dma_latency = get_llong_from_str(optarg); @@ -716,6 +701,11 @@ static struct common_params case '\2': params->bpf_action_program = optarg; break; + case '\3': + params->stack_format = parse_stack_format(optarg); + if (params->stack_format == -1) + fatal("Invalid --stack-format option"); + break; default: fatal("Invalid option"); } @@ -781,15 +771,12 @@ static struct osnoise_tool *timerlat_init_top(struct common_params *params) { struct osnoise_tool *top; - int nr_cpus; - - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); top = osnoise_init_tool("timerlat_top"); if (!top) return NULL; - top->data = timerlat_alloc_top(nr_cpus); + top->data = timerlat_alloc_top(); if (!top->data) goto out_err; @@ -809,10 +796,10 @@ out_err: static int timerlat_top_bpf_main_loop(struct osnoise_tool *tool) { - struct timerlat_params *params = to_timerlat_params(tool->params); + const struct common_params *params = tool->params; int retval, wait_retval; - if (params->common.aa_only) { + if (params->aa_only) { /* Auto-analysis only, just wait for stop tracing */ timerlat_bpf_wait(-1); return 0; @@ -820,8 +807,8 @@ timerlat_top_bpf_main_loop(struct osnoise_tool *tool) /* Pull and display data in a loop */ while (!stop_tracing) { - wait_retval = timerlat_bpf_wait(params->common.quiet ? -1 : - params->common.sleep_time); + wait_retval = timerlat_bpf_wait(params->quiet ? -1 : + params->sleep_time); retval = timerlat_top_bpf_pull_data(tool); if (retval) { @@ -829,28 +816,27 @@ timerlat_top_bpf_main_loop(struct osnoise_tool *tool) return retval; } - if (!params->common.quiet) + if (!params->quiet) timerlat_print_stats(tool); if (wait_retval != 0) { /* Stopping requested by tracer */ - actions_perform(¶ms->common.threshold_actions); + retval = common_threshold_handler(tool); + if (retval) + return retval; - if (!params->common.threshold_actions.continue_flag) - /* continue flag not set, break */ + if (!should_continue_tracing(tool->params)) break; - /* continue action reached, re-enable tracing */ - if (tool->record) - trace_instance_start(&tool->record->trace); - if (tool->aa) - trace_instance_start(&tool->aa->trace); - timerlat_bpf_restart_tracing(); + if (timerlat_bpf_restart_tracing()) { + err_msg("Error restarting BPF trace\n"); + return -1; + } } /* is there still any user-threads ? */ - if (params->common.user_workload) { - if (params->common.user.stopped_running) { + if (params->user_workload) { + if (params->user.stopped_running) { debug_msg("timerlat user space threads stopped!\n"); break; } diff --git a/tools/tracing/rtla/src/timerlat_u.c b/tools/tracing/rtla/src/timerlat_u.c index ce68e39d25fd..c80edaf07b00 100644 --- a/tools/tracing/rtla/src/timerlat_u.c +++ b/tools/tracing/rtla/src/timerlat_u.c @@ -16,7 +16,7 @@ #include #include -#include "utils.h" +#include "common.h" #include "timerlat_u.h" /* @@ -32,7 +32,7 @@ static int timerlat_u_main(int cpu, struct timerlat_u_params *params) { struct sched_param sp = { .sched_priority = 95 }; - char buffer[1024]; + char buffer[MAX_PATH]; int timerlat_fd; cpu_set_t set; int retval; @@ -83,7 +83,7 @@ static int timerlat_u_main(int cpu, struct timerlat_u_params *params) /* add should continue with a signal handler */ while (true) { - retval = read(timerlat_fd, buffer, 1024); + retval = read(timerlat_fd, buffer, ARRAY_SIZE(buffer)); if (retval < 0) break; } @@ -99,7 +99,7 @@ static int timerlat_u_main(int cpu, struct timerlat_u_params *params) * * Return the number of processes that received the kill. */ -static int timerlat_u_send_kill(pid_t *procs, int nr_cpus) +static int timerlat_u_send_kill(pid_t *procs) { int killed = 0; int i, retval; @@ -131,7 +131,6 @@ static int timerlat_u_send_kill(pid_t *procs, int nr_cpus) */ void *timerlat_u_dispatcher(void *data) { - int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); struct timerlat_u_params *params = data; char proc_name[128]; int procs_count = 0; @@ -170,7 +169,7 @@ void *timerlat_u_dispatcher(void *data) /* parent */ if (pid == -1) { - timerlat_u_send_kill(procs, nr_cpus); + timerlat_u_send_kill(procs); debug_msg("Failed to create child processes"); pthread_exit(&retval); } @@ -197,7 +196,7 @@ void *timerlat_u_dispatcher(void *data) sleep(1); } - timerlat_u_send_kill(procs, nr_cpus); + timerlat_u_send_kill(procs); while (procs_count) { pid = waitpid(-1, &wstatus, 0); diff --git a/tools/tracing/rtla/src/timerlat_u.h b/tools/tracing/rtla/src/timerlat_u.h index 661511908957..a692331bd1c7 100644 --- a/tools/tracing/rtla/src/timerlat_u.h +++ b/tools/tracing/rtla/src/timerlat_u.h @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#pragma once /* * Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira */ diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c index b8be3e28680e..e407447773d0 100644 --- a/tools/tracing/rtla/src/trace.c +++ b/tools/tracing/rtla/src/trace.c @@ -73,6 +73,8 @@ int save_trace_to_file(struct tracefs_instance *inst, const char *filename) char buffer[4096]; int out_fd, in_fd; int retval = -1; + ssize_t n_read; + ssize_t n_written; if (!inst || !filename) return 0; @@ -90,15 +92,30 @@ int save_trace_to_file(struct tracefs_instance *inst, const char *filename) goto out_close_in; } - do { - retval = read(in_fd, buffer, sizeof(buffer)); - if (retval <= 0) + for (;;) { + n_read = read(in_fd, buffer, sizeof(buffer)); + if (n_read < 0) { + if (errno == EINTR) + continue; + err_msg("Error reading trace file: %s\n", strerror(errno)); goto out_close; + } + if (n_read == 0) + break; - retval = write(out_fd, buffer, retval); - if (retval < 0) - goto out_close; - } while (retval > 0); + n_written = 0; + while (n_written < n_read) { + const ssize_t w = write(out_fd, buffer + n_written, n_read - n_written); + + if (w < 0) { + if (errno == EINTR) + continue; + err_msg("Error writing trace file: %s\n", strerror(errno)); + goto out_close; + } + n_written += w; + } + } retval = 0; out_close: @@ -191,9 +208,7 @@ void trace_instance_destroy(struct trace_instance *trace) */ int trace_instance_init(struct trace_instance *trace, char *tool_name) { - trace->seq = calloc(1, sizeof(*trace->seq)); - if (!trace->seq) - goto out_err; + trace->seq = calloc_fatal(1, sizeof(*trace->seq)); trace_seq_init(trace->seq); @@ -274,15 +289,9 @@ struct trace_events *trace_event_alloc(const char *event_string) { struct trace_events *tevent; - tevent = calloc(1, sizeof(*tevent)); - if (!tevent) - return NULL; + tevent = calloc_fatal(1, sizeof(*tevent)); - tevent->system = strdup(event_string); - if (!tevent->system) { - free(tevent); - return NULL; - } + tevent->system = strdup_fatal(event_string); tevent->event = strstr(tevent->system, ":"); if (tevent->event) { @@ -296,31 +305,23 @@ struct trace_events *trace_event_alloc(const char *event_string) /* * trace_event_add_filter - record an event filter */ -int trace_event_add_filter(struct trace_events *event, char *filter) +void trace_event_add_filter(struct trace_events *event, char *filter) { if (event->filter) free(event->filter); - event->filter = strdup(filter); - if (!event->filter) - return 1; - - return 0; + event->filter = strdup_fatal(filter); } /* * trace_event_add_trigger - record an event trigger action */ -int trace_event_add_trigger(struct trace_events *event, char *trigger) +void trace_event_add_trigger(struct trace_events *event, char *trigger) { if (event->trigger) free(event->trigger); - event->trigger = strdup(trigger); - if (!event->trigger) - return 1; - - return 0; + event->trigger = strdup_fatal(trigger); } /* @@ -329,7 +330,7 @@ int trace_event_add_trigger(struct trace_events *event, char *trigger) static void trace_event_disable_filter(struct trace_instance *instance, struct trace_events *tevent) { - char filter[1024]; + char filter[MAX_PATH]; int retval; if (!tevent->filter) @@ -341,7 +342,7 @@ static void trace_event_disable_filter(struct trace_instance *instance, debug_msg("Disabling %s:%s filter %s\n", tevent->system, tevent->event ? : "*", tevent->filter); - snprintf(filter, 1024, "!%s\n", tevent->filter); + snprintf(filter, ARRAY_SIZE(filter), "!%s\n", tevent->filter); retval = tracefs_event_file_write(instance->inst, tevent->system, tevent->event, "filter", filter); @@ -358,10 +359,11 @@ static void trace_event_disable_filter(struct trace_instance *instance, static void trace_event_save_hist(struct trace_instance *instance, struct trace_events *tevent) { - int retval, index, out_fd; + size_t index, hist_len; mode_t mode = 0644; - char path[1024]; + char path[MAX_PATH]; char *hist; + int out_fd; if (!tevent) return; @@ -371,11 +373,10 @@ static void trace_event_save_hist(struct trace_instance *instance, return; /* is this a hist: trigger? */ - retval = strncmp(tevent->trigger, "hist:", strlen("hist:")); - if (retval) + if (!str_has_prefix(tevent->trigger, "hist:")) return; - snprintf(path, 1024, "%s_%s_hist.txt", tevent->system, tevent->event); + snprintf(path, ARRAY_SIZE(path), "%s_%s_hist.txt", tevent->system, tevent->event); printf(" Saving event %s:%s hist to %s\n", tevent->system, tevent->event, path); @@ -392,9 +393,18 @@ static void trace_event_save_hist(struct trace_instance *instance, } index = 0; + hist_len = strlen(hist); do { - index += write(out_fd, &hist[index], strlen(hist) - index); - } while (index < strlen(hist)); + const ssize_t written = write(out_fd, &hist[index], hist_len - index); + + if (written < 0) { + if (errno == EINTR) + continue; + err_msg(" Error writing hist file: %s\n", strerror(errno)); + break; + } + index += written; + } while (index < hist_len); free(hist); out_close: @@ -407,7 +417,7 @@ out_close: static void trace_event_disable_trigger(struct trace_instance *instance, struct trace_events *tevent) { - char trigger[1024]; + char trigger[MAX_PATH]; int retval; if (!tevent->trigger) @@ -421,7 +431,7 @@ static void trace_event_disable_trigger(struct trace_instance *instance, trace_event_save_hist(instance, tevent); - snprintf(trigger, 1024, "!%s\n", tevent->trigger); + snprintf(trigger, ARRAY_SIZE(trigger), "!%s\n", tevent->trigger); retval = tracefs_event_file_write(instance->inst, tevent->system, tevent->event, "trigger", trigger); @@ -460,7 +470,7 @@ void trace_events_disable(struct trace_instance *instance, static int trace_event_enable_filter(struct trace_instance *instance, struct trace_events *tevent) { - char filter[1024]; + char filter[MAX_PATH]; int retval; if (!tevent->filter) @@ -472,7 +482,7 @@ static int trace_event_enable_filter(struct trace_instance *instance, return 1; } - snprintf(filter, 1024, "%s\n", tevent->filter); + snprintf(filter, ARRAY_SIZE(filter), "%s\n", tevent->filter); debug_msg("Enabling %s:%s filter %s\n", tevent->system, tevent->event ? : "*", tevent->filter); @@ -495,7 +505,7 @@ static int trace_event_enable_filter(struct trace_instance *instance, static int trace_event_enable_trigger(struct trace_instance *instance, struct trace_events *tevent) { - char trigger[1024]; + char trigger[MAX_PATH]; int retval; if (!tevent->trigger) @@ -507,7 +517,7 @@ static int trace_event_enable_trigger(struct trace_instance *instance, return 1; } - snprintf(trigger, 1024, "%s\n", tevent->trigger); + snprintf(trigger, ARRAY_SIZE(trigger), "%s\n", tevent->trigger); debug_msg("Enabling %s:%s trigger %s\n", tevent->system, tevent->event ? : "*", tevent->trigger); diff --git a/tools/tracing/rtla/src/trace.h b/tools/tracing/rtla/src/trace.h index 1e5aee4b828d..95b911a2228b 100644 --- a/tools/tracing/rtla/src/trace.h +++ b/tools/tracing/rtla/src/trace.h @@ -45,6 +45,6 @@ void trace_events_destroy(struct trace_instance *instance, int trace_events_enable(struct trace_instance *instance, struct trace_events *events); -int trace_event_add_filter(struct trace_events *event, char *filter); -int trace_event_add_trigger(struct trace_events *event, char *trigger); +void trace_event_add_filter(struct trace_events *event, char *filter); +void trace_event_add_trigger(struct trace_events *event, char *trigger); int trace_set_buffer_size(struct trace_instance *trace, int size); diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c index 0da3b2470c31..9cec5b3e02c8 100644 --- a/tools/tracing/rtla/src/utils.c +++ b/tools/tracing/rtla/src/utils.c @@ -19,7 +19,7 @@ #include #include -#include "utils.h" +#include "common.h" #define MAX_MSG_LENGTH 1024 int config_debug; @@ -119,14 +119,11 @@ int parse_cpu_set(char *cpu_list, cpu_set_t *set) { const char *p; int end_cpu; - int nr_cpus; int cpu; int i; CPU_ZERO(set); - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); - for (p = cpu_list; *p; ) { cpu = atoi(p); if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus) @@ -164,6 +161,24 @@ err: return 1; } +/* + * parse_stack_format - parse the stack format + * + * Return: the stack format on success, -1 otherwise. + */ +int parse_stack_format(char *arg) +{ + if (!strcmp(arg, "truncate")) + return STACK_FORMAT_TRUNCATE; + if (!strcmp(arg, "skip")) + return STACK_FORMAT_SKIP; + if (!strcmp(arg, "full")) + return STACK_FORMAT_FULL; + + debug_msg("Error parsing the stack format %s\n", arg); + return -1; +} + /* * parse_duration - parse duration with s/m/h/d suffix converting it to seconds */ @@ -198,6 +213,21 @@ long parse_seconds_duration(char *val) return t; } +/* + * match_time_unit - check if str starts with unit followed by end-of-string or ':' + * + * This allows the time unit parser to work both in standalone duration strings + * like "100ms" and in colon-delimited SCHED_DEADLINE specifications like + * "d:10ms:100ms", while still rejecting malformed input like "100msx". + */ +static bool match_time_unit(const char *str, const char *unit) +{ + size_t len = strlen(unit); + + return strncmp(str, unit, len) == 0 && + (str[len] == '\0' || str[len] == ':'); +} + /* * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds */ @@ -209,15 +239,15 @@ long parse_ns_duration(char *val) t = strtol(val, &end, 10); if (end) { - if (!strncmp(end, "ns", 2)) { + if (match_time_unit(end, "ns")) { return t; - } else if (!strncmp(end, "us", 2)) { + } else if (match_time_unit(end, "us")) { t *= 1000; return t; - } else if (!strncmp(end, "ms", 2)) { + } else if (match_time_unit(end, "ms")) { t *= 1000 * 1000; return t; - } else if (!strncmp(end, "s", 1)) { + } else if (match_time_unit(end, "s")) { t *= 1000 * 1000 * 1000; return t; } @@ -294,7 +324,7 @@ static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_e return 0; /* check if the string is a pid */ - for (t_name = proc_entry->d_name; t_name; t_name++) { + for (t_name = proc_entry->d_name; *t_name; t_name++) { if (!isdigit(*t_name)) break; } @@ -316,8 +346,7 @@ static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_e return 0; buffer[MAX_PATH-1] = '\0'; - retval = strncmp(comm_prefix, buffer, strlen(comm_prefix)); - if (retval) + if (!str_has_prefix(buffer, comm_prefix)) return 0; /* comm already have \n */ @@ -361,22 +390,23 @@ int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr) if (strtoi(proc_entry->d_name, &pid)) { err_msg("'%s' is not a valid pid", proc_entry->d_name); - goto out_err; + retval = 1; + goto out; } /* procfs_is_workload_pid confirmed it is a pid */ 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; + goto out; } debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name); } - return 0; -out_err: + retval = 0; +out: closedir(procfs); - return 1; + return retval; } #define INVALID_VAL (~0L) @@ -559,7 +589,6 @@ int save_cpu_idle_disable_state(unsigned int cpu) unsigned int nr_states; unsigned int state; int disabled; - int nr_cpus; nr_states = cpuidle_state_count(cpu); @@ -567,7 +596,6 @@ int save_cpu_idle_disable_state(unsigned int cpu) return 0; if (saved_cpu_idle_disable_state == NULL) { - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); saved_cpu_idle_disable_state = calloc(nr_cpus, sizeof(unsigned int *)); if (!saved_cpu_idle_disable_state) return -1; @@ -644,13 +672,10 @@ int restore_cpu_idle_disable_state(unsigned int cpu) void free_cpu_idle_disable_states(void) { int cpu; - int nr_cpus; if (!saved_cpu_idle_disable_state) return; - nr_cpus = sysconf(_SC_NPROCESSORS_CONF); - for (cpu = 0; cpu < nr_cpus; cpu++) { free(saved_cpu_idle_disable_state[cpu]); saved_cpu_idle_disable_state[cpu] = NULL; @@ -809,6 +834,7 @@ static int open_cgroup_procs(const char *cgroup) char cgroup_procs[MAX_PATH]; int retval; int cg_fd; + size_t cg_path_len; retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path)); if (!retval) { @@ -816,16 +842,18 @@ static int open_cgroup_procs(const char *cgroup) return -1; } + cg_path_len = strlen(cgroup_path); + if (!cgroup) { - retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)], - sizeof(cgroup_path) - strlen(cgroup_path)); + retval = get_self_cgroup(&cgroup_path[cg_path_len], + sizeof(cgroup_path) - cg_path_len); if (!retval) { err_msg("Did not find self cgroup\n"); return -1; } } else { - snprintf(&cgroup_path[strlen(cgroup_path)], - sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup); + snprintf(&cgroup_path[cg_path_len], + sizeof(cgroup_path) - cg_path_len, "%s/", cgroup); } snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path); @@ -1030,3 +1058,38 @@ int strtoi(const char *s, int *res) *res = (int) lres; return 0; } + +static inline void fatal_alloc(void) +{ + fatal("Error allocating memory\n"); +} + +void *calloc_fatal(size_t n, size_t size) +{ + void *p = calloc(n, size); + + if (!p) + fatal_alloc(); + + return p; +} + +void *reallocarray_fatal(void *p, size_t n, size_t size) +{ + p = reallocarray(p, n, size); + + if (!p) + fatal_alloc(); + + return p; +} + +char *strdup_fatal(const char *s) +{ + char *p = strdup(s); + + if (!p) + fatal_alloc(); + + return p; +} diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h index f7c2a52a0ab5..e794ede64b2c 100644 --- a/tools/tracing/rtla/src/utils.h +++ b/tools/tracing/rtla/src/utils.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include @@ -14,6 +15,28 @@ #define MAX_NICE 20 #define MIN_NICE -19 +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) +#endif + +/* Calculate string length at compile time (excluding null terminator) */ +#define STRING_LENGTH(s) (ARRAY_SIZE(s) - sizeof(*(s))) + +/* Compare string with static string, length determined at compile time */ +#define strncmp_static(s1, s2) strncmp(s1, s2, ARRAY_SIZE(s2)) + +/** + * str_has_prefix - Test if a string has a given prefix + * @str: The string to test + * @prefix: The string to see if @str starts with + * + * Returns: true if @str starts with @prefix, false otherwise + */ +static inline bool str_has_prefix(const char *str, const char *prefix) +{ + return strncmp(str, prefix, strlen(prefix)) == 0; +} + #define container_of(ptr, type, member)({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)) ; }) @@ -62,13 +85,23 @@ struct sched_attr { }; #endif /* SCHED_ATTR_SIZE_VER0 */ +enum stack_format { + STACK_FORMAT_TRUNCATE, + STACK_FORMAT_SKIP, + STACK_FORMAT_FULL +}; + int parse_prio(char *arg, struct sched_attr *sched_param); int parse_cpu_set(char *cpu_list, cpu_set_t *set); +int parse_stack_format(char *arg); int __set_sched_attr(int pid, struct sched_attr *attr); int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr); int set_comm_cgroup(const char *comm_prefix, const char *cgroup); int set_pid_cgroup(pid_t pid, const char *cgroup); int set_cpu_dma_latency(int32_t latency); +void *calloc_fatal(size_t n, size_t size); +void *reallocarray_fatal(void *p, size_t n, size_t size); +char *strdup_fatal(const char *s); #ifdef HAVE_LIBCPUPOWER_SUPPORT int save_cpu_idle_disable_state(unsigned int cpu); int restore_cpu_idle_disable_state(unsigned int cpu); diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build new file mode 100644 index 000000000000..5f1e531ea8c9 --- /dev/null +++ b/tools/tracing/rtla/tests/unit/Build @@ -0,0 +1,2 @@ +unit_tests-y += unit_tests.o +unit_tests-y +=../../src/utils.o diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit new file mode 100644 index 000000000000..2088c9cc3571 --- /dev/null +++ b/tools/tracing/rtla/tests/unit/Makefile.unit @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only + +UNIT_TESTS := $(OUTPUT)unit_tests +UNIT_TESTS_IN := $(UNIT_TESTS)-in.o + +$(UNIT_TESTS): $(UNIT_TESTS_IN) + $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ -lcheck + +$(UNIT_TESTS_IN): + make $(build)=unit_tests + +unit-tests: FORCE + $(Q)if [ "$(feature-libcheck)" = "1" ]; then \ + $(MAKE) $(UNIT_TESTS) && $(UNIT_TESTS); \ + else \ + echo "libcheck is missing, skipping unit tests. Please install check-devel/check"; \ + fi diff --git a/tools/tracing/rtla/tests/unit/unit_tests.c b/tools/tracing/rtla/tests/unit/unit_tests.c new file mode 100644 index 000000000000..f3c6d89e3300 --- /dev/null +++ b/tools/tracing/rtla/tests/unit/unit_tests.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "../../src/utils.h" +int nr_cpus; + +START_TEST(test_strtoi) +{ + int result; + char buf[64]; + + ck_assert_int_eq(strtoi("123", &result), 0); + ck_assert_int_eq(result, 123); + ck_assert_int_eq(strtoi(" -456", &result), 0); + ck_assert_int_eq(result, -456); + + snprintf(buf, sizeof(buf), "%d", INT_MAX); + ck_assert_int_eq(strtoi(buf, &result), 0); + snprintf(buf, sizeof(buf), "%ld", (long)INT_MAX + 1); + ck_assert_int_eq(strtoi(buf, &result), -1); + + ck_assert_int_eq(strtoi("", &result), -1); + ck_assert_int_eq(strtoi("123abc", &result), -1); + ck_assert_int_eq(strtoi("123 ", &result), -1); +} +END_TEST + +START_TEST(test_parse_cpu_set) +{ + cpu_set_t set; + + nr_cpus = 8; + ck_assert_int_eq(parse_cpu_set("0", &set), 0); + ck_assert(CPU_ISSET(0, &set)); + ck_assert(!CPU_ISSET(1, &set)); + + ck_assert_int_eq(parse_cpu_set("0,2", &set), 0); + ck_assert(CPU_ISSET(0, &set)); + ck_assert(CPU_ISSET(2, &set)); + + ck_assert_int_eq(parse_cpu_set("0-3", &set), 0); + ck_assert(CPU_ISSET(0, &set)); + ck_assert(CPU_ISSET(1, &set)); + ck_assert(CPU_ISSET(2, &set)); + ck_assert(CPU_ISSET(3, &set)); + + ck_assert_int_eq(parse_cpu_set("1-3,5", &set), 0); + ck_assert(!CPU_ISSET(0, &set)); + ck_assert(CPU_ISSET(1, &set)); + ck_assert(CPU_ISSET(2, &set)); + ck_assert(CPU_ISSET(3, &set)); + ck_assert(!CPU_ISSET(4, &set)); + ck_assert(CPU_ISSET(5, &set)); + + ck_assert_int_eq(parse_cpu_set("-1", &set), 1); + ck_assert_int_eq(parse_cpu_set("abc", &set), 1); + ck_assert_int_eq(parse_cpu_set("9999", &set), 1); +} +END_TEST + +START_TEST(test_parse_prio) +{ + struct sched_attr attr; + + ck_assert_int_eq(parse_prio("f:50", &attr), 0); + ck_assert_uint_eq(attr.sched_policy, SCHED_FIFO); + ck_assert_uint_eq(attr.sched_priority, 50U); + + ck_assert_int_eq(parse_prio("r:30", &attr), 0); + ck_assert_uint_eq(attr.sched_policy, SCHED_RR); + + ck_assert_int_eq(parse_prio("o:0", &attr), 0); + ck_assert_uint_eq(attr.sched_policy, SCHED_OTHER); + ck_assert_int_eq(attr.sched_nice, 0); + + ck_assert_int_eq(parse_prio("d:10ms:100ms", &attr), 0); + ck_assert_uint_eq(attr.sched_policy, 6U); + + ck_assert_int_eq(parse_prio("f:999", &attr), -1); + ck_assert_int_eq(parse_prio("o:-20", &attr), -1); + ck_assert_int_eq(parse_prio("d:100ms:10ms", &attr), -1); + ck_assert_int_eq(parse_prio("x:50", &attr), -1); +} +END_TEST + +Suite *utils_suite(void) +{ + Suite *s = suite_create("utils"); + TCase *tc = tcase_create("core"); + + tcase_add_test(tc, test_strtoi); + tcase_add_test(tc, test_parse_cpu_set); + tcase_add_test(tc, test_parse_prio); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int num_failed; + SRunner *sr; + + sr = srunner_create(utils_suite()); + srunner_run_all(sr, CK_NORMAL); + num_failed = srunner_ntests_failed(sr); + + srunner_free(sr); + + return (num_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +}