mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Merge tag 'trace-rtla-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace
Pull RTLA updates from Steven Rostedt: - Simplify option parsing Auto-generate getopt_long() optstring for short options from long options array, avoiding the need to specify it manually and reducing the surface for mistakes. - Add unit tests Implement unit tests (make unit-tests) using libcheck, next to existing runtime tests (make check). Currently, three functions from utils.c are tested. - Add --stack-format option In addition to stopping stack pointer decoding (with -s/--stack option) on first unresolvable pointer, allow also skipping unresolvable pointers and displaying everything, configurable with a new option. - Unify number of CPUs into one global variable Use one global variable, nr_cpus, to store the number of CPUs instead of retrieving it and passing it at multiple places. - Fix behavior in various corner cases Make RTLA behave correctly in several corner cases: memory allocation failure, invalid value read from kernel side, thread creation failure, malformed time value input, and read/write failure or interruption by signal. - Improve string handling Simplify several places in the code that handle strings, including parsing of action arguments. A few new helper functions and variables are added for that purpose. - Get rid of magic numbers Few places handling paths use a magic number of 1024. Replace it with MAX_PATH and ARRAY_SIZE() macro. - Unify threshold handling Code that handles response to latency threshold is duplicated between tools, which has led to bugs in the past. Unify it into a new helper as much as possible. - Fix segfault on SIGINT during cleanup The SIGINT handler touches dynamically allocated memory. Detach it before freeing it during cleanup to prevent segmentation fault and discarding of output buffers. Also, properly document SIGINT handling while at it. * tag 'trace-rtla-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (28 commits) Documentation/rtla: Document SIGINT behavior rtla: Fix segfault on multiple SIGINTs rtla/utils: Fix loop condition in PID validation rtla/utils: Fix resource leak in set_comm_sched_attr() rtla/trace: Fix I/O handling in save_trace_to_file() rtla/trace: Fix write loop in trace_event_save_hist() rtla/timerlat: Simplify RTLA_NO_BPF environment variable check rtla: Use str_has_prefix() for option prefix check rtla: Enforce exact match for time unit suffixes rtla: Use str_has_prefix() for prefix checks rtla: Add str_has_prefix() helper function rtla: Handle pthread_create() failure properly rtla/timerlat: Add bounds check for softirq vector rtla: Simplify code by caching string lengths rtla: Replace magic number with MAX_PATH rtla: Introduce common_threshold_handler() helper rtla/actions: Simplify argument parsing rtla: Use strdup() to simplify code rtla: Exit on memory allocation failures during initialization tools/rtla: Remove unneeded nr_cpus from for_each_monitored_cpu ...
This commit is contained in:
@@ -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
|
||||
===========
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ;-).
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
8
tools/build/feature/test-libcheck.c
Normal file
8
tools/build/feature/test-libcheck.c
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <check.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Suite *s = suite_create("test");
|
||||
return s == 0;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
rtla-y += src/
|
||||
unit_tests-y += tests/unit/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
#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 ? */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#pragma once
|
||||
|
||||
#include <getopt.h>
|
||||
#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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 <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",
|
||||
" --stack-format <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();
|
||||
|
||||
@@ -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 <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",
|
||||
" --stack-format <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;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#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);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#pragma once
|
||||
/*
|
||||
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sched.h>
|
||||
#include <stdbool.h>
|
||||
@@ -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);
|
||||
|
||||
2
tools/tracing/rtla/tests/unit/Build
Normal file
2
tools/tracing/rtla/tests/unit/Build
Normal file
@@ -0,0 +1,2 @@
|
||||
unit_tests-y += unit_tests.o
|
||||
unit_tests-y +=../../src/utils.o
|
||||
17
tools/tracing/rtla/tests/unit/Makefile.unit
Normal file
17
tools/tracing/rtla/tests/unit/Makefile.unit
Normal file
@@ -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
|
||||
119
tools/tracing/rtla/tests/unit/unit_tests.c
Normal file
119
tools/tracing/rtla/tests/unit/unit_tests.c
Normal file
@@ -0,0 +1,119 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <check.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sched.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user