summaryrefslogtreecommitdiff
path: root/tools/tracing
diff options
context:
space:
mode:
Diffstat (limited to 'tools/tracing')
-rw-r--r--tools/tracing/latency/Makefile.config8
-rw-r--r--tools/tracing/latency/latency-collector.c2
-rw-r--r--tools/tracing/rtla/.gitignore5
-rw-r--r--tools/tracing/rtla/Build1
-rw-r--r--tools/tracing/rtla/Makefile42
-rw-r--r--tools/tracing/rtla/Makefile.config58
-rw-r--r--tools/tracing/rtla/Makefile.rtla19
-rw-r--r--tools/tracing/rtla/README.txt8
-rw-r--r--tools/tracing/rtla/example/timerlat_bpf_action.c16
-rw-r--r--tools/tracing/rtla/example/timerlat_load.py (renamed from tools/tracing/rtla/sample/timerlat_load.py)0
-rw-r--r--tools/tracing/rtla/src/Build3
-rw-r--r--tools/tracing/rtla/src/actions.c284
-rw-r--r--tools/tracing/rtla/src/actions.h57
-rw-r--r--tools/tracing/rtla/src/common.c562
-rw-r--r--tools/tracing/rtla/src/common.h188
-rw-r--r--tools/tracing/rtla/src/osnoise.c138
-rw-r--r--tools/tracing/rtla/src/osnoise.h92
-rw-r--r--tools/tracing/rtla/src/osnoise_hist.c607
-rw-r--r--tools/tracing/rtla/src/osnoise_top.c549
-rw-r--r--tools/tracing/rtla/src/timerlat.bpf.c174
-rw-r--r--tools/tracing/rtla/src/timerlat.c229
-rw-r--r--tools/tracing/rtla/src/timerlat.h40
-rw-r--r--tools/tracing/rtla/src/timerlat_aa.c53
-rw-r--r--tools/tracing/rtla/src/timerlat_aa.h2
-rw-r--r--tools/tracing/rtla/src/timerlat_bpf.c243
-rw-r--r--tools/tracing/rtla/src/timerlat_bpf.h62
-rw-r--r--tools/tracing/rtla/src/timerlat_hist.c977
-rw-r--r--tools/tracing/rtla/src/timerlat_top.c916
-rw-r--r--tools/tracing/rtla/src/timerlat_u.c25
-rw-r--r--tools/tracing/rtla/src/timerlat_u.h1
-rw-r--r--tools/tracing/rtla/src/trace.c107
-rw-r--r--tools/tracing/rtla/src/trace.h7
-rw-r--r--tools/tracing/rtla/src/utils.c254
-rw-r--r--tools/tracing/rtla/src/utils.h45
-rw-r--r--tools/tracing/rtla/tests/bpf/bpf_action_map.c25
-rw-r--r--tools/tracing/rtla/tests/engine.sh97
-rw-r--r--tools/tracing/rtla/tests/hwnoise.t13
-rw-r--r--tools/tracing/rtla/tests/osnoise.t41
-rwxr-xr-xtools/tracing/rtla/tests/scripts/check-priority.sh8
-rw-r--r--tools/tracing/rtla/tests/timerlat.t78
-rw-r--r--tools/tracing/rtla/tests/unit/Build2
-rw-r--r--tools/tracing/rtla/tests/unit/Makefile.unit17
-rw-r--r--tools/tracing/rtla/tests/unit/unit_tests.c119
43 files changed, 3712 insertions, 2462 deletions
diff --git a/tools/tracing/latency/Makefile.config b/tools/tracing/latency/Makefile.config
index 0fe6b50f029b..6efa13e3ca93 100644
--- a/tools/tracing/latency/Makefile.config
+++ b/tools/tracing/latency/Makefile.config
@@ -1,7 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
+include $(srctree)/tools/scripts/utilities.mak
+
STOP_ERROR :=
+ifndef ($(NO_LIBTRACEEVENT),1)
+ ifeq ($(call get-executable,$(PKG_CONFIG)),)
+ $(error Error: $(PKG_CONFIG) needed by libtraceevent/libtracefs is missing on this system, please install it)
+ endif
+endif
+
define lib_setup
$(eval LIB_INCLUDES += $(shell sh -c "$(PKG_CONFIG) --cflags lib$(1)"))
$(eval LDFLAGS += $(shell sh -c "$(PKG_CONFIG) --libs-only-L lib$(1)"))
diff --git a/tools/tracing/latency/latency-collector.c b/tools/tracing/latency/latency-collector.c
index cf263fe9deaf..ef97916e3873 100644
--- a/tools/tracing/latency/latency-collector.c
+++ b/tools/tracing/latency/latency-collector.c
@@ -1725,7 +1725,7 @@ static void show_usage(void)
"-n, --notrace\t\tIf latency is detected, do not print out the content of\n"
"\t\t\tthe trace file to standard output\n\n"
-"-t, --threads NRTHR\tRun NRTHR threads for printing. Default is %d.\n\n"
+"-e, --threads NRTHR\tRun NRTHR threads for printing. Default is %d.\n\n"
"-r, --random\t\tArbitrarily sleep a certain amount of time, default\n"
"\t\t\t%ld ms, before reading the trace file. The\n"
diff --git a/tools/tracing/rtla/.gitignore b/tools/tracing/rtla/.gitignore
index 293f0dbb0ca2..4d39d64ac08c 100644
--- a/tools/tracing/rtla/.gitignore
+++ b/tools/tracing/rtla/.gitignore
@@ -4,3 +4,8 @@ rtla-static
fixdep
feature
FEATURE-DUMP
+*.skel.h
+custom_filename.txt
+osnoise_irq_noise_hist.txt
+osnoise_trace.txt
+timerlat_trace.txt
diff --git a/tools/tracing/rtla/Build b/tools/tracing/rtla/Build
index 6c9d5b36a315..3ce2e0f567fd 100644
--- a/tools/tracing/rtla/Build
+++ b/tools/tracing/rtla/Build
@@ -1 +1,2 @@
rtla-y += src/
+unit_tests-y += tests/unit/
diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile
index 0b61208db604..45690ee14544 100644
--- a/tools/tracing/rtla/Makefile
+++ b/tools/tracing/rtla/Makefile
@@ -33,14 +33,23 @@ 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
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
@@ -61,6 +70,29 @@ CFLAGS += $(INCLUDES) $(LIB_INCLUDES)
export CFLAGS OUTPUT srctree
+ifeq ($(BUILD_BPF_SKEL),1)
+src/timerlat.bpf.o: src/timerlat.bpf.c
+ $(QUIET_CLANG)$(CLANG) -g -O2 -target bpf -c $(filter %.c,$^) -o $@
+
+src/timerlat.skel.h: src/timerlat.bpf.o
+ $(QUIET_GENSKEL)$(SYSTEM_BPFTOOL) gen skeleton $< > $@
+
+example/timerlat_bpf_action.o: example/timerlat_bpf_action.c
+ $(QUIET_CLANG)$(CLANG) -g -O2 -target bpf -c $(filter %.c,$^) -o $@
+
+tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
+ $(QUIET_CLANG)$(CLANG) -g -O2 -target bpf -c $(filter %.c,$^) -o $@
+else
+src/timerlat.skel.h:
+ $(Q)echo '/* BPF skeleton is disabled */' > src/timerlat.skel.h
+
+example/timerlat_bpf_action.o: example/timerlat_bpf_action.c
+ $(Q)echo "BPF skeleton support is disabled, skipping example/timerlat_bpf_action.o"
+
+tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
+ $(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o"
+endif
+
$(RTLA): $(RTLA_IN)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(EXTLIBS)
@@ -71,7 +103,7 @@ static: $(RTLA_IN)
rtla.%: fixdep FORCE
make -f $(srctree)/tools/build/Makefile.build dir=. $@
-$(RTLA_IN): fixdep FORCE
+$(RTLA_IN): fixdep FORCE src/timerlat.skel.h
make $(build)=rtla
clean: doc_clean fixdep-clean
@@ -79,6 +111,10 @@ clean: doc_clean fixdep-clean
$(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
$(Q)rm -f rtla rtla-static fixdep FEATURE-DUMP rtla-*
$(Q)rm -rf feature
-check: $(RTLA)
- RTLA=$(RTLA) prove -o -f tests/
+ $(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
.PHONY: FORCE clean check
diff --git a/tools/tracing/rtla/Makefile.config b/tools/tracing/rtla/Makefile.config
index 92a6e12e42d3..0bdd258b76de 100644
--- a/tools/tracing/rtla/Makefile.config
+++ b/tools/tracing/rtla/Makefile.config
@@ -1,10 +1,18 @@
# SPDX-License-Identifier: GPL-2.0-only
+include $(srctree)/tools/scripts/utilities.mak
+
STOP_ERROR :=
LIBTRACEEVENT_MIN_VERSION = 1.5
LIBTRACEFS_MIN_VERSION = 1.6
+ifndef ($(NO_LIBTRACEEVENT),1)
+ ifeq ($(call get-executable,$(PKG_CONFIG)),)
+ $(error Error: $(PKG_CONFIG) needed by libtraceevent/libtracefs is missing on this system, please install it)
+ endif
+endif
+
define lib_setup
$(eval LIB_INCLUDES += $(shell sh -c "$(PKG_CONFIG) --cflags lib$(1)"))
$(eval LDFLAGS += $(shell sh -c "$(PKG_CONFIG) --libs-only-L lib$(1)"))
@@ -53,6 +61,56 @@ 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.
+ BUILD_BPF_SKEL := 1
+endif
+
+ifeq ($(BUILD_BPF_SKEL),0)
+ $(info BPF skeleton support disabled, building without BPF skeleton support.)
+endif
+
+$(call feature_check,libbpf)
+ifeq ($(feature-libbpf), 1)
+ $(call detected,CONFIG_LIBBPF)
+else
+ $(info libbpf is missing, building without BPF skeleton support.)
+ $(info Please install libbpf-dev/libbpf-devel)
+ BUILD_BPF_SKEL := 0
+endif
+
+$(call feature_check,clang-bpf-co-re)
+ifeq ($(feature-clang-bpf-co-re), 1)
+ $(call detected,CONFIG_CLANG_BPF_CO_RE)
+else
+ $(info clang is missing or does not support BPF CO-RE, building without BPF skeleton support.)
+ $(info Please install clang)
+ BUILD_BPF_SKEL := 0
+endif
+
+$(call feature_check,bpftool-skeletons)
+ifeq ($(feature-bpftool-skeletons), 1)
+ $(call detected,CONFIG_BPFTOOL_SKELETONS)
+else
+ $(info bpftool is missing or not supporting skeletons, building without BPF skeleton support.)
+ $(info Please install bpftool)
+ BUILD_BPF_SKEL := 0
+endif
+
+ifeq ($(BUILD_BPF_SKEL),1)
+ CFLAGS += -DHAVE_BPF_SKEL
+ EXTLIBS += -lbpf
+endif
+
ifeq ($(STOP_ERROR),1)
$(error Please, check the errors above.)
endif
diff --git a/tools/tracing/rtla/Makefile.rtla b/tools/tracing/rtla/Makefile.rtla
index cc1d6b615475..1743d91829d4 100644
--- a/tools/tracing/rtla/Makefile.rtla
+++ b/tools/tracing/rtla/Makefile.rtla
@@ -18,7 +18,7 @@ export CC AR STRIP PKG_CONFIG LD_SO_CONF_PATH LDCONFIG
FOPTS := -flto=auto -ffat-lto-objects -fexceptions -fstack-protector-strong \
-fasynchronous-unwind-tables -fstack-clash-protection
WOPTS := -O -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 \
- -Wp,-D_GLIBCXX_ASSERTIONS -Wno-maybe-uninitialized
+ -Wp,-D_GLIBCXX_ASSERTIONS
ifeq ($(CC),clang)
FOPTS := $(filter-out -flto=auto -ffat-lto-objects, $(FOPTS))
@@ -34,6 +34,8 @@ INSTALL := install
MKDIR := mkdir
STRIP := strip
BINDIR := /usr/bin
+CTAGS := ctags
+ETAGS := ctags -e
.PHONY: install
install: doc_install
@@ -47,6 +49,18 @@ install: doc_install
@test ! -f $(DESTDIR)$(BINDIR)/timerlat || $(RM) $(DESTDIR)$(BINDIR)/timerlat
@$(LN) -s rtla $(DESTDIR)$(BINDIR)/timerlat
+.PHONY: tags
+tags:
+ $(CTAGS) -R --extras=+f --c-kinds=+p src
+
+.PHONY: TAGS
+TAGS:
+ $(ETAGS) -R --extras=+f --c-kinds=+p src
+
+.PHONY: tags_clean
+tags_clean:
+ $(RM) tags TAGS
+
.PHONY: doc doc_clean doc_install
doc:
$(MAKE) -C $(DOCSRC)
@@ -57,8 +71,7 @@ doc_clean:
doc_install:
$(MAKE) -C $(DOCSRC) install
-# This section is neesary for the tarball, when the tarball
-# support is removed, we can delete these entries.
+# This section is necessary to make the rtla tarball
NAME := rtla
DIRS := src
FILES := Makefile README.txt
diff --git a/tools/tracing/rtla/README.txt b/tools/tracing/rtla/README.txt
index dd5621038c55..a9faee4dbb3a 100644
--- a/tools/tracing/rtla/README.txt
+++ b/tools/tracing/rtla/README.txt
@@ -12,6 +12,14 @@ 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:
+
+ - libbpf 1.0.0 or later
+ - bpftool with skeleton support
+ - clang with BPF CO-RE support
It also depends on python3-docutils to compile man pages.
diff --git a/tools/tracing/rtla/example/timerlat_bpf_action.c b/tools/tracing/rtla/example/timerlat_bpf_action.c
new file mode 100644
index 000000000000..ac1be049a848
--- /dev/null
+++ b/tools/tracing/rtla/example/timerlat_bpf_action.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_tracing.h>
+
+char LICENSE[] SEC("license") = "GPL";
+
+struct trace_event_raw_timerlat_sample {
+ unsigned long long timer_latency;
+} __attribute__((preserve_access_index));
+
+SEC("tp/timerlat_action")
+int action_handler(struct trace_event_raw_timerlat_sample *tp_args)
+{
+ bpf_printk("Latency: %lld\n", tp_args->timer_latency);
+ return 0;
+}
diff --git a/tools/tracing/rtla/sample/timerlat_load.py b/tools/tracing/rtla/example/timerlat_load.py
index a819c3588073..a819c3588073 100644
--- a/tools/tracing/rtla/sample/timerlat_load.py
+++ b/tools/tracing/rtla/example/timerlat_load.py
diff --git a/tools/tracing/rtla/src/Build b/tools/tracing/rtla/src/Build
index dbed9e31829b..329e24a40cf7 100644
--- a/tools/tracing/rtla/src/Build
+++ b/tools/tracing/rtla/src/Build
@@ -1,5 +1,7 @@
rtla-y += trace.o
rtla-y += utils.o
+rtla-y += actions.o
+rtla-y += common.o
rtla-y += osnoise.o
rtla-y += osnoise_top.o
rtla-y += osnoise_hist.o
@@ -8,4 +10,5 @@ rtla-y += timerlat_top.o
rtla-y += timerlat_hist.o
rtla-y += timerlat_u.o
rtla-y += timerlat_aa.o
+rtla-y += timerlat_bpf.o
rtla-y += rtla.o
diff --git a/tools/tracing/rtla/src/actions.c b/tools/tracing/rtla/src/actions.c
new file mode 100644
index 000000000000..b0d68b5de08d
--- /dev/null
+++ b/tools/tracing/rtla/src/actions.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "actions.h"
+#include "trace.h"
+#include "utils.h"
+
+/*
+ * actions_init - initialize struct actions
+ */
+void
+actions_init(struct actions *self)
+{
+ self->size = action_default_size;
+ self->list = calloc_fatal(self->size, sizeof(struct action));
+ self->len = 0;
+ self->continue_flag = false;
+
+ /* This has to be set by the user */
+ self->trace_output_inst = NULL;
+}
+
+/*
+ * actions_destroy - destroy struct actions
+ */
+void
+actions_destroy(struct actions *self)
+{
+ /* Free any action-specific data */
+ struct action *action;
+
+ for_each_action(self, action) {
+ if (action->type == ACTION_SHELL)
+ free(action->command);
+ if (action->type == ACTION_TRACE_OUTPUT)
+ free(action->trace_output);
+ }
+
+ /* Free action list */
+ free(self->list);
+}
+
+/*
+ * actions_new - Get pointer to new action
+ */
+static struct action *
+actions_new(struct actions *self)
+{
+ if (self->len >= self->size) {
+ 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++];
+}
+
+/*
+ * actions_add_trace_output - add an action to output trace
+ */
+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 = strdup_fatal(trace_output);
+}
+
+/*
+ * actions_add_trace_output - add an action to send signal to a process
+ */
+void
+actions_add_signal(struct actions *self, int signal, int pid)
+{
+ struct action *action = actions_new(self);
+
+ self->present[ACTION_SIGNAL] = true;
+ action->type = ACTION_SIGNAL;
+ action->signal = signal;
+ action->pid = pid;
+}
+
+/*
+ * actions_add_shell - add an action to execute a shell command
+ */
+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 = strdup_fatal(command);
+}
+
+/*
+ * actions_add_continue - add an action to resume measurement
+ */
+void
+actions_add_continue(struct actions *self)
+{
+ struct action *action = actions_new(self);
+
+ self->present[ACTION_CONTINUE] = true;
+ action->type = ACTION_CONTINUE;
+}
+
+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
+ */
+int
+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;
+
+ /* For ACTION_TRACE_OUTPUT */
+ const char *trace_output;
+
+ strcpy(trigger_c, trigger);
+ token = strtok(trigger_c, ",");
+ if (!token)
+ return -1;
+
+ if (strcmp(token, "trace") == 0)
+ type = ACTION_TRACE_OUTPUT;
+ else if (strcmp(token, "signal") == 0)
+ type = ACTION_SIGNAL;
+ else if (strcmp(token, "shell") == 0)
+ type = ACTION_SHELL;
+ else if (strcmp(token, "continue") == 0)
+ type = ACTION_CONTINUE;
+ else
+ /* Invalid trigger type */
+ return -1;
+
+ token = strtok(NULL, ",");
+
+ switch (type) {
+ case ACTION_TRACE_OUTPUT:
+ /* Takes no argument */
+ if (token == NULL)
+ trace_output = tracefn;
+ 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;
+ }
+ actions_add_trace_output(self, trace_output);
+ break;
+ case ACTION_SIGNAL:
+ /* Takes two arguments, num (signal) and pid */
+ while (token != NULL) {
+ arg_value = extract_arg(token, "num");
+ if (arg_value) {
+ if (strtoi(arg_value, &signal))
+ return -1;
+ } else {
+ 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, ",");
+ }
+
+ if (!signal || !pid)
+ /* Missing argument */
+ return -1;
+
+ actions_add_signal(self, signal, pid);
+ break;
+ case ACTION_SHELL:
+ if (token == NULL)
+ 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;
+ actions_add_continue(self);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * actions_perform - perform all actions
+ */
+int
+actions_perform(struct actions *self)
+{
+ int pid, retval;
+ const struct action *action;
+
+ for_each_action(self, action) {
+ switch (action->type) {
+ case ACTION_TRACE_OUTPUT:
+ retval = save_trace_to_file(self->trace_output_inst, action->trace_output);
+ if (retval) {
+ err_msg("Error saving trace\n");
+ return retval;
+ }
+ break;
+ case ACTION_SIGNAL:
+ if (action->pid == -1)
+ pid = getppid();
+ else
+ pid = action->pid;
+ retval = kill(pid, action->signal);
+ if (retval) {
+ err_msg("Error sending signal\n");
+ return retval;
+ }
+ break;
+ case ACTION_SHELL:
+ retval = system(action->command);
+ if (retval)
+ return retval;
+ break;
+ case ACTION_CONTINUE:
+ self->continue_flag = true;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
diff --git a/tools/tracing/rtla/src/actions.h b/tools/tracing/rtla/src/actions.h
new file mode 100644
index 000000000000..034048682fef
--- /dev/null
+++ b/tools/tracing/rtla/src/actions.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <tracefs.h>
+#include <stdbool.h>
+
+enum action_type {
+ ACTION_NONE = 0,
+ ACTION_TRACE_OUTPUT,
+ ACTION_SIGNAL,
+ ACTION_SHELL,
+ ACTION_CONTINUE,
+ ACTION_FIELD_N
+};
+
+struct action {
+ enum action_type type;
+ union {
+ struct {
+ /* For ACTION_TRACE_OUTPUT */
+ char *trace_output;
+ };
+ struct {
+ /* For ACTION_SIGNAL */
+ int signal;
+ int pid;
+ };
+ struct {
+ /* For ACTION_SHELL */
+ char *command;
+ };
+ };
+};
+
+static const int action_default_size = 8;
+
+struct actions {
+ struct action *list;
+ int len, size;
+ bool present[ACTION_FIELD_N];
+ bool continue_flag;
+
+ /* External dependencies */
+ struct tracefs_instance *trace_output_inst;
+};
+
+#define for_each_action(actions, action) \
+ for ((action) = (actions)->list; \
+ (action) < (actions)->list + (actions)->len; \
+ (action)++)
+
+void actions_init(struct actions *self);
+void actions_destroy(struct actions *self);
+void actions_add_trace_output(struct actions *self, const char *trace_output);
+void actions_add_signal(struct actions *self, int signal, int pid);
+void actions_add_shell(struct actions *self, const char *command);
+void actions_add_continue(struct actions *self);
+int actions_parse(struct actions *self, const char *trigger, const char *tracefn);
+int actions_perform(struct actions *self);
diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c
new file mode 100644
index 000000000000..35e3d3aa922e
--- /dev/null
+++ b/tools/tracing/rtla/src/common.c
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.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)
+{
+ if (stop_tracing) {
+ /*
+ * Stop requested twice in a row; abort event processing and
+ * exit immediately
+ */
+ tracefs_iterate_stop(trace_inst->inst);
+ return;
+ }
+ stop_tracing = 1;
+ if (trace_inst)
+ trace_instance_stop(trace_inst);
+}
+
+/*
+ * set_signals - handles the signal to stop the tool
+ */
+static void set_signals(struct common_params *params)
+{
+ signal(SIGINT, stop_trace);
+ if (params->duration) {
+ signal(SIGALRM, stop_trace);
+ alarm(params->duration);
+ }
+}
+
+/*
+ * 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
+ *
+ * @argc: argument count
+ * @argv: argument vector
+ * @common: common parameters structure
+ *
+ * Parse command line options that are common to all rtla tools.
+ *
+ * Returns: non zero if a common option was parsed, or 0
+ * if the option should be handled by tool-specific parsing.
+ */
+int common_parse_options(int argc, char **argv, struct common_params *common)
+{
+ struct trace_events *tevent;
+ int saved_state = optind;
+ int c;
+
+ static struct option long_options[] = {
+ {"cpus", required_argument, 0, 'c'},
+ {"cgroup", optional_argument, 0, 'C'},
+ {"debug", no_argument, 0, 'D'},
+ {"duration", required_argument, 0, 'd'},
+ {"event", required_argument, 0, 'e'},
+ {"house-keeping", required_argument, 0, 'H'},
+ {"priority", required_argument, 0, 'P'},
+ {0, 0, 0, 0}
+ };
+
+ opterr = 0;
+ c = getopt_auto(argc, argv, long_options);
+ opterr = 1;
+
+ switch (c) {
+ case 'c':
+ if (parse_cpu_set(optarg, &common->monitored_cpus))
+ fatal("Invalid -c cpu list");
+ common->cpus = optarg;
+ break;
+ case 'C':
+ common->cgroup = 1;
+ common->cgroup_name = parse_optional_arg(argc, argv);
+ break;
+ case 'D':
+ config_debug = 1;
+ break;
+ case 'd':
+ common->duration = parse_seconds_duration(optarg);
+ if (!common->duration)
+ fatal("Invalid -d duration");
+ break;
+ case 'e':
+ tevent = trace_event_alloc(optarg);
+ if (!tevent)
+ fatal("Error alloc trace event");
+
+ if (common->events)
+ tevent->next = common->events;
+ common->events = tevent;
+ break;
+ case 'H':
+ common->hk_cpus = 1;
+ if (parse_cpu_set(optarg, &common->hk_cpu_set))
+ fatal("Error parsing house keeping CPUs");
+ break;
+ case 'P':
+ if (parse_prio(optarg, &common->sched_param) == -1)
+ fatal("Invalid -P priority");
+ common->set_sched = 1;
+ break;
+ default:
+ optind = saved_state;
+ return 0;
+ }
+
+ return c;
+}
+
+/*
+ * common_apply_config - apply common configs to the initialized tool
+ */
+int
+common_apply_config(struct osnoise_tool *tool, struct common_params *params)
+{
+ int retval, i;
+
+ if (!params->sleep_time)
+ params->sleep_time = 1;
+
+ retval = osnoise_set_cpus(tool->context, params->cpus ? params->cpus : "all");
+ if (retval) {
+ err_msg("Failed to apply CPUs config\n");
+ goto out_err;
+ }
+
+ if (!params->cpus) {
+ for (i = 0; i < nr_cpus; i++)
+ CPU_SET(i, &params->monitored_cpus);
+ }
+
+ if (params->hk_cpus) {
+ retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set),
+ &params->hk_cpu_set);
+ if (retval == -1) {
+ err_msg("Failed to set rtla to the house keeping CPUs\n");
+ goto out_err;
+ }
+ } else if (params->cpus) {
+ /*
+ * Even if the user do not set a house-keeping CPU, try to
+ * move rtla to a CPU set different to the one where the user
+ * set the workload to run.
+ *
+ * No need to check results as this is an automatic attempt.
+ */
+ auto_house_keeping(&params->monitored_cpus);
+ }
+
+ /*
+ * Set workload according to type of thread if the kernel supports it.
+ * On kernels without support, user threads will have already failed
+ * on missing fd, and kernel threads do not need it.
+ */
+ retval = osnoise_set_workload(tool->context, params->kernel_workload);
+ if (retval < -1) {
+ err_msg("Failed to set OSNOISE_WORKLOAD option\n");
+ goto out_err;
+ }
+
+ return 0;
+
+out_err:
+ return -1;
+}
+
+
+/**
+ * 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;
+ enum result return_value = ERROR;
+ struct osnoise_tool *tool;
+ bool stopped;
+ int retval;
+
+ nr_cpus = get_nprocs_conf();
+ params = ops->parse_args(argc, argv);
+ if (!params)
+ exit(1);
+
+ tool = ops->init_tool(params);
+ if (!tool) {
+ err_msg("Could not init osnoise tool\n");
+ goto out_exit;
+ }
+ tool->ops = ops;
+ tool->params = params;
+
+ /*
+ * Save trace instance into global variable so that SIGINT can stop
+ * the timerlat tracer.
+ * Otherwise, rtla could loop indefinitely when overloaded.
+ */
+ trace_inst = &tool->trace;
+
+ retval = ops->apply_config(tool);
+ if (retval) {
+ err_msg("Could not apply config\n");
+ goto out_free;
+ }
+
+ retval = enable_tracer_by_name(trace_inst->inst, ops->tracer);
+ if (retval) {
+ err_msg("Failed to enable %s tracer\n", ops->tracer);
+ goto out_free;
+ }
+
+ if (params->set_sched) {
+ retval = set_comm_sched_attr(ops->comm_prefix, &params->sched_param);
+ if (retval) {
+ err_msg("Failed to set sched parameters\n");
+ goto out_free;
+ }
+ }
+
+ if (params->cgroup && !params->user_data) {
+ retval = set_comm_cgroup(ops->comm_prefix, params->cgroup_name);
+ if (!retval) {
+ err_msg("Failed to move threads to cgroup\n");
+ goto out_free;
+ }
+ }
+
+
+ if (params->threshold_actions.present[ACTION_TRACE_OUTPUT] ||
+ params->end_actions.present[ACTION_TRACE_OUTPUT]) {
+ tool->record = osnoise_init_trace_tool(ops->tracer);
+ if (!tool->record) {
+ err_msg("Failed to enable the trace instance\n");
+ goto out_free;
+ }
+ params->threshold_actions.trace_output_inst = tool->record->trace.inst;
+ params->end_actions.trace_output_inst = tool->record->trace.inst;
+
+ if (params->events) {
+ retval = trace_events_enable(&tool->record->trace, params->events);
+ if (retval)
+ goto out_trace;
+ }
+
+ if (params->buffer_size > 0) {
+ retval = trace_set_buffer_size(&tool->record->trace, params->buffer_size);
+ if (retval)
+ goto out_trace;
+ }
+ }
+
+ if (params->user_workload) {
+ pthread_t user_thread;
+
+ /* rtla asked to stop */
+ params->user.should_run = 1;
+ /* all threads left */
+ params->user.stopped_running = 0;
+
+ params->user.set = &params->monitored_cpus;
+ if (params->set_sched)
+ params->user.sched_param = &params->sched_param;
+ else
+ params->user.sched_param = NULL;
+
+ params->user.cgroup_name = params->cgroup_name;
+
+ retval = pthread_create(&user_thread, NULL, timerlat_u_dispatcher, &params->user);
+ if (retval) {
+ err_msg("Error creating timerlat user-space threads\n");
+ goto out_trace;
+ }
+ }
+
+ retval = ops->enable(tool);
+ if (retval)
+ goto out_trace;
+
+ tool->start_time = time(NULL);
+ set_signals(params);
+
+ retval = ops->main(tool);
+ if (retval)
+ goto out_signals;
+
+ if (params->user_workload && !params->user.stopped_running) {
+ params->user.should_run = 0;
+ sleep(1);
+ }
+
+ ops->print_stats(tool);
+
+ actions_perform(&params->end_actions);
+
+ return_value = PASSED;
+
+ stopped = osnoise_trace_is_off(tool, tool->record) && !stop_tracing;
+ if (stopped) {
+ printf("%s hit stop tracing\n", ops->tracer);
+ return_value = FAILED;
+ }
+
+ 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;
+out_free:
+ ops->free(tool);
+ osnoise_destroy_tool(tool->record);
+ osnoise_destroy_tool(tool);
+ actions_destroy(&params->threshold_actions);
+ actions_destroy(&params->end_actions);
+ free(params);
+out_exit:
+ exit(return_value);
+}
+
+int top_main_loop(struct osnoise_tool *tool)
+{
+ struct common_params *params = tool->params;
+ struct trace_instance *trace = &tool->trace;
+ struct osnoise_tool *record = tool->record;
+ int retval;
+
+ while (!stop_tracing) {
+ sleep(params->sleep_time);
+
+ if (params->aa_only && !osnoise_trace_is_off(tool, record))
+ continue;
+
+ retval = tracefs_iterate_raw_events(trace->tep,
+ trace->inst,
+ NULL,
+ 0,
+ collect_registered_events,
+ trace);
+ if (retval < 0) {
+ err_msg("Error iterating on events\n");
+ return retval;
+ }
+
+ if (!params->quiet)
+ tool->ops->print_stats(tool);
+
+ if (osnoise_trace_is_off(tool, record)) {
+ if (stop_tracing)
+ /* stop tracing requested, do not perform actions */
+ return 0;
+
+ retval = common_threshold_handler(tool);
+ if (retval)
+ return retval;
+
+
+ if (!should_continue_tracing(params))
+ return 0;
+
+ trace_instance_start(trace);
+ }
+
+ /* is there still any user-threads ? */
+ if (params->user_workload) {
+ if (params->user.stopped_running) {
+ debug_msg("timerlat user space threads stopped!\n");
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int hist_main_loop(struct osnoise_tool *tool)
+{
+ struct common_params *params = tool->params;
+ struct trace_instance *trace = &tool->trace;
+ int retval = 0;
+
+ while (!stop_tracing) {
+ sleep(params->sleep_time);
+
+ retval = tracefs_iterate_raw_events(trace->tep,
+ trace->inst,
+ NULL,
+ 0,
+ collect_registered_events,
+ trace);
+ if (retval < 0) {
+ err_msg("Error iterating on events\n");
+ break;
+ }
+
+ if (osnoise_trace_is_off(tool, tool->record)) {
+ if (stop_tracing)
+ /* stop tracing requested, do not perform actions */
+ break;
+
+ retval = common_threshold_handler(tool);
+ if (retval)
+ return retval;
+
+ if (!should_continue_tracing(params))
+ return 0;
+
+ trace_instance_start(trace);
+ }
+
+ /* is there still any user-threads ? */
+ if (params->user_workload) {
+ if (params->user.stopped_running) {
+ debug_msg("user-space threads stopped!\n");
+ break;
+ }
+ }
+ }
+
+ return retval;
+}
+
+int osn_set_stop(struct osnoise_tool *tool)
+{
+ struct common_params *params = tool->params;
+ int retval;
+
+ retval = osnoise_set_stop_us(tool->context, params->stop_us);
+ if (retval) {
+ err_msg("Failed to set stop us\n");
+ return retval;
+ }
+
+ retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
+ if (retval) {
+ err_msg("Failed to set stop total us\n");
+ return retval;
+ }
+
+ return 0;
+}
+
+static void print_msg_array(const char * const *msgs)
+{
+ if (!msgs)
+ return;
+
+ for (int i = 0; msgs[i]; i++)
+ fprintf(stderr, "%s\n", msgs[i]);
+}
+
+/*
+ * common_usage - print complete usage information
+ */
+void common_usage(const char *tool, const char *mode,
+ const char *desc, const char * const *start_msgs, const char * const *opt_msgs)
+{
+ static const char * const common_options[] = {
+ " -h/--help: print this menu",
+ NULL
+ };
+ fprintf(stderr, "rtla %s", tool);
+ if (strcmp(mode, ""))
+ fprintf(stderr, " %s", mode);
+ fprintf(stderr, ": %s (version %s)\n\n", desc, VERSION);
+ fprintf(stderr, " usage: [rtla] %s ", tool);
+
+ if (strcmp(mode, "top") == 0)
+ fprintf(stderr, "[top] [-h] ");
+ else
+ fprintf(stderr, "%s [-h] ", mode);
+
+ print_msg_array(start_msgs);
+ fprintf(stderr, "\n");
+ print_msg_array(common_options);
+ print_msg_array(opt_msgs);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
new file mode 100644
index 000000000000..51665db4ffce
--- /dev/null
+++ b/tools/tracing/rtla/src/common.h
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include <getopt.h>
+#include "actions.h"
+#include "timerlat_u.h"
+#include "trace.h"
+#include "utils.h"
+
+/*
+ * osnoise_context - read, store, write, restore osnoise configs.
+ */
+struct osnoise_context {
+ int flags;
+ int ref;
+
+ char *curr_cpus;
+ char *orig_cpus;
+
+ /* 0 as init value */
+ unsigned long long orig_runtime_us;
+ unsigned long long runtime_us;
+
+ /* 0 as init value */
+ unsigned long long orig_period_us;
+ unsigned long long period_us;
+
+ /* 0 as init value */
+ long long orig_timerlat_period_us;
+ long long timerlat_period_us;
+
+ /* 0 as init value */
+ long long orig_tracing_thresh;
+ long long tracing_thresh;
+
+ /* -1 as init value because 0 is disabled */
+ long long orig_stop_us;
+ long long stop_us;
+
+ /* -1 as init value because 0 is disabled */
+ long long orig_stop_total_us;
+ long long stop_total_us;
+
+ /* -1 as init value because 0 is disabled */
+ long long orig_print_stack;
+ long long print_stack;
+
+ /* -1 as init value because 0 is off */
+ int orig_opt_irq_disable;
+ int opt_irq_disable;
+
+ /* -1 as init value because 0 is off */
+ int orig_opt_workload;
+ int opt_workload;
+};
+
+extern struct trace_instance *trace_inst;
+extern volatile int stop_tracing;
+
+struct hist_params {
+ char no_irq;
+ char no_thread;
+ char no_header;
+ char no_summary;
+ char no_index;
+ char with_zeros;
+ int bucket_size;
+ int entries;
+};
+
+/*
+ * common_params - Parameters shared between timerlat_params and osnoise_params
+ */
+struct common_params {
+ /* trace configuration */
+ char *cpus;
+ cpu_set_t monitored_cpus;
+ struct trace_events *events;
+ int buffer_size;
+
+ /* Timing parameters */
+ int warmup;
+ long long stop_us;
+ long long stop_total_us;
+ int sleep_time;
+ int duration;
+
+ /* Scheduling parameters */
+ int set_sched;
+ struct sched_attr sched_param;
+ int cgroup;
+ char *cgroup_name;
+ int hk_cpus;
+ cpu_set_t hk_cpu_set;
+
+ /* Other parameters */
+ struct hist_params hist;
+ int output_divisor;
+ int pretty_output;
+ int quiet;
+ int user_workload;
+ int kernel_workload;
+ int user_data;
+ int aa_only;
+
+ struct actions threshold_actions;
+ struct actions end_actions;
+ struct timerlat_u_params user;
+};
+
+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))
+
+struct tool_ops;
+
+/*
+ * osnoise_tool - osnoise based tool definition.
+ *
+ * Only the "trace" and "context" fields are used for
+ * the additional trace instances (record and aa).
+ */
+struct osnoise_tool {
+ struct tool_ops *ops;
+ struct trace_instance trace;
+ struct osnoise_context *context;
+ void *data;
+ struct common_params *params;
+ time_t start_time;
+ struct osnoise_tool *record;
+ struct osnoise_tool *aa;
+};
+
+struct tool_ops {
+ const char *tracer;
+ const char *comm_prefix;
+ struct common_params *(*parse_args)(int argc, char *argv[]);
+ struct osnoise_tool *(*init_tool)(struct common_params *params);
+ int (*apply_config)(struct osnoise_tool *tool);
+ int (*enable)(struct osnoise_tool *tool);
+ int (*main)(struct osnoise_tool *tool);
+ void (*print_stats)(struct osnoise_tool *tool);
+ void (*analyze)(struct osnoise_tool *tool, bool stopped);
+ 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);
+
+int osnoise_set_workload(struct osnoise_context *context, bool onoff);
+
+void osnoise_destroy_tool(struct osnoise_tool *top);
+struct osnoise_tool *osnoise_init_tool(char *tool_name);
+struct osnoise_tool *osnoise_init_trace_tool(const char *tracer);
+bool osnoise_trace_is_off(struct osnoise_tool *tool, struct osnoise_tool *record);
+int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us);
+int osnoise_set_stop_total_us(struct osnoise_context *context,
+ long long stop_total_us);
+
+int 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);
+int hist_main_loop(struct osnoise_tool *tool);
+int osn_set_stop(struct osnoise_tool *tool);
+
+void common_usage(const char *tool, const char *mode,
+ const char *desc, const char * const *start_msgs, const char * const *opt_msgs);
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index 85f398b89597..2db3db155c44 100644
--- a/tools/tracing/rtla/src/osnoise.c
+++ b/tools/tracing/rtla/src/osnoise.c
@@ -3,6 +3,7 @@
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
+#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
@@ -12,9 +13,12 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+#include <sched.h>
#include "osnoise.h"
-#include "utils.h"
+
+#define DEFAULT_SAMPLE_PERIOD 1000000 /* 1s */
+#define DEFAULT_SAMPLE_RUNTIME 1000000 /* 1s */
/*
* osnoise_get_cpus - return the original "osnoise/cpus" content
@@ -58,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);
@@ -902,22 +906,6 @@ static void osnoise_put_workload(struct osnoise_context *context)
context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
}
-/*
- * enable_osnoise - enable osnoise tracer in the trace_instance
- */
-int enable_osnoise(struct trace_instance *trace)
-{
- return enable_tracer_by_name(trace->inst, "osnoise");
-}
-
-/*
- * enable_timerlat - enable timerlat tracer in the trace_instance
- */
-int enable_timerlat(struct trace_instance *trace)
-{
- return enable_tracer_by_name(trace->inst, "timerlat");
-}
-
enum {
FLAG_CONTEXT_NEWLY_CREATED = (1 << 0),
FLAG_CONTEXT_DELETED = (1 << 1),
@@ -950,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;
@@ -1029,30 +1015,22 @@ 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;
}
/*
* osnoise_init_trace_tool - init a tracer instance to trace osnoise events
*/
-struct osnoise_tool *osnoise_init_trace_tool(char *tracer)
+struct osnoise_tool *osnoise_init_trace_tool(const char *tracer)
{
struct osnoise_tool *trace;
int retval;
@@ -1115,6 +1093,84 @@ osnoise_report_missed_events(struct osnoise_tool *tool)
}
}
+/*
+ * osnoise_apply_config - apply osnoise configs to the initialized tool
+ */
+int
+osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params)
+{
+ int retval;
+
+ params->common.kernel_workload = true;
+
+ if (params->runtime || params->period) {
+ retval = osnoise_set_runtime_period(tool->context,
+ params->runtime,
+ params->period);
+ } else {
+ retval = osnoise_set_runtime_period(tool->context,
+ DEFAULT_SAMPLE_PERIOD,
+ DEFAULT_SAMPLE_RUNTIME);
+ }
+
+ if (retval) {
+ err_msg("Failed to set runtime and/or period\n");
+ goto out_err;
+ }
+
+ retval = osnoise_set_tracing_thresh(tool->context, params->threshold);
+ if (retval) {
+ err_msg("Failed to set tracing_thresh\n");
+ goto out_err;
+ }
+
+ return common_apply_config(tool, &params->common);
+
+out_err:
+ return -1;
+}
+
+int osnoise_enable(struct osnoise_tool *tool)
+{
+ struct osnoise_params *params = to_osnoise_params(tool->params);
+ int retval;
+
+ /*
+ * Start the tracer here, after having set all instances.
+ *
+ * Let the trace instance start first for the case of hitting a stop
+ * tracing while enabling other instances. The trace instance is the
+ * one with most valuable information.
+ */
+ if (tool->record)
+ trace_instance_start(&tool->record->trace);
+ trace_instance_start(&tool->trace);
+
+ if (params->common.warmup > 0) {
+ debug_msg("Warming up for %d seconds\n", params->common.warmup);
+ sleep(params->common.warmup);
+ if (stop_tracing)
+ return -1;
+
+ /*
+ * Clean up the buffer. The osnoise workload do not run
+ * with tracing off to avoid creating a performance penalty
+ * when not needed.
+ */
+ retval = tracefs_instance_file_write(tool->trace.inst, "trace", "");
+ if (retval < 0) {
+ debug_msg("Error cleaning up the buffer");
+ return retval;
+ }
+ }
+
+ retval = osn_set_stop(tool);
+ if (retval)
+ return retval;
+
+ return 0;
+}
+
static void osnoise_usage(int err)
{
int i;
@@ -1148,21 +1204,21 @@ int osnoise_main(int argc, char *argv[])
* default cmdline.
*/
if (argc == 1) {
- osnoise_top_main(argc, argv);
+ run_tool(&osnoise_top_ops, argc, argv);
exit(0);
}
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 */
- osnoise_top_main(argc, argv);
+ run_tool(&osnoise_top_ops, argc, argv);
exit(0);
} else if (strcmp(argv[1], "top") == 0) {
- osnoise_top_main(argc-1, &argv[1]);
+ run_tool(&osnoise_top_ops, argc-1, &argv[1]);
exit(0);
} else if (strcmp(argv[1], "hist") == 0) {
- osnoise_hist_main(argc-1, &argv[1]);
+ run_tool(&osnoise_hist_ops, argc-1, &argv[1]);
exit(0);
}
@@ -1173,6 +1229,6 @@ usage:
int hwnoise_main(int argc, char *argv[])
{
- osnoise_top_main(argc, argv);
+ run_tool(&osnoise_top_ops, argc, argv);
exit(0);
}
diff --git a/tools/tracing/rtla/src/osnoise.h b/tools/tracing/rtla/src/osnoise.h
index 91835a7d8c2b..168669aa7e0d 100644
--- a/tools/tracing/rtla/src/osnoise.h
+++ b/tools/tracing/rtla/src/osnoise.h
@@ -1,53 +1,23 @@
// SPDX-License-Identifier: GPL-2.0
-#include "trace.h"
+#pragma once
-/*
- * osnoise_context - read, store, write, restore osnoise configs.
- */
-struct osnoise_context {
- int flags;
- int ref;
-
- char *curr_cpus;
- char *orig_cpus;
-
- /* 0 as init value */
- unsigned long long orig_runtime_us;
- unsigned long long runtime_us;
-
- /* 0 as init value */
- unsigned long long orig_period_us;
- unsigned long long period_us;
-
- /* 0 as init value */
- long long orig_timerlat_period_us;
- long long timerlat_period_us;
+#include "common.h"
- /* 0 as init value */
- long long orig_tracing_thresh;
- long long tracing_thresh;
-
- /* -1 as init value because 0 is disabled */
- long long orig_stop_us;
- long long stop_us;
-
- /* -1 as init value because 0 is disabled */
- long long orig_stop_total_us;
- long long stop_total_us;
-
- /* -1 as init value because 0 is disabled */
- long long orig_print_stack;
- long long print_stack;
-
- /* -1 as init value because 0 is off */
- int orig_opt_irq_disable;
- int opt_irq_disable;
+enum osnoise_mode {
+ MODE_OSNOISE = 0,
+ MODE_HWNOISE
+};
- /* -1 as init value because 0 is off */
- int orig_opt_workload;
- int opt_workload;
+struct osnoise_params {
+ struct common_params common;
+ unsigned long long runtime;
+ unsigned long long period;
+ long long threshold;
+ enum osnoise_mode mode;
};
+#define to_osnoise_params(ptr) container_of(ptr, struct osnoise_params, common)
+
/*
* *_INIT_VALs are also invalid values, they are used to
* communicate errors.
@@ -59,20 +29,12 @@ struct osnoise_context *osnoise_context_alloc(void);
int osnoise_get_context(struct osnoise_context *context);
void osnoise_put_context(struct osnoise_context *context);
-int osnoise_set_cpus(struct osnoise_context *context, char *cpus);
-void osnoise_restore_cpus(struct osnoise_context *context);
-
int osnoise_set_runtime_period(struct osnoise_context *context,
unsigned long long runtime,
unsigned long long period);
void osnoise_restore_runtime_period(struct osnoise_context *context);
-int osnoise_set_stop_us(struct osnoise_context *context,
- long long stop_us);
void osnoise_restore_stop_us(struct osnoise_context *context);
-
-int osnoise_set_stop_total_us(struct osnoise_context *context,
- long long stop_total_us);
void osnoise_restore_stop_total_us(struct osnoise_context *context);
int osnoise_set_timerlat_period_us(struct osnoise_context *context,
@@ -88,26 +50,14 @@ int osnoise_set_print_stack(struct osnoise_context *context,
long long print_stack);
int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff);
-int osnoise_set_workload(struct osnoise_context *context, bool onoff);
-
-/*
- * osnoise_tool - osnoise based tool definition.
- */
-struct osnoise_tool {
- struct trace_instance trace;
- struct osnoise_context *context;
- void *data;
- void *params;
- time_t start_time;
-};
-
-void osnoise_destroy_tool(struct osnoise_tool *top);
-struct osnoise_tool *osnoise_init_tool(char *tool_name);
-struct osnoise_tool *osnoise_init_trace_tool(char *tracer);
void osnoise_report_missed_events(struct osnoise_tool *tool);
-bool osnoise_trace_is_off(struct osnoise_tool *tool, struct osnoise_tool *record);
+int osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params);
-int osnoise_hist_main(int argc, char *argv[]);
-int osnoise_top_main(int argc, char **argv);
+int osnoise_enable(struct osnoise_tool *tool);
int osnoise_main(int argc, char **argv);
int hwnoise_main(int argc, char **argv);
+
+extern struct tool_ops timerlat_top_ops, timerlat_hist_ops;
+extern struct tool_ops osnoise_top_ops, osnoise_hist_ops;
+
+int run_tool(struct tool_ops *ops, int argc, char *argv[]);
diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c
index b4930b835b0a..8ad816b80265 100644
--- a/tools/tracing/rtla/src/osnoise_hist.c
+++ b/tools/tracing/rtla/src/osnoise_hist.c
@@ -9,43 +9,11 @@
#include <string.h>
#include <signal.h>
#include <unistd.h>
-#include <errno.h>
#include <stdio.h>
#include <time.h>
-#include <sched.h>
-#include "utils.h"
#include "osnoise.h"
-struct osnoise_hist_params {
- char *cpus;
- cpu_set_t monitored_cpus;
- char *trace_output;
- char *cgroup_name;
- unsigned long long runtime;
- unsigned long long period;
- long long threshold;
- long long stop_us;
- long long stop_total_us;
- int sleep_time;
- int duration;
- int set_sched;
- int output_divisor;
- int cgroup;
- int hk_cpus;
- cpu_set_t hk_cpu_set;
- struct sched_attr sched_param;
- struct trace_events *events;
- char no_header;
- char no_summary;
- char no_index;
- char with_zeros;
- int bucket_size;
- int entries;
- int warmup;
- int buffer_size;
-};
-
struct osnoise_hist_cpu {
int *samples;
int count;
@@ -61,7 +29,6 @@ struct osnoise_hist_data {
struct osnoise_hist_cpu *hist;
int entries;
int bucket_size;
- int nr_cpus;
};
/*
@@ -73,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);
}
@@ -85,11 +52,16 @@ osnoise_free_histogram(struct osnoise_hist_data *data)
free(data);
}
+static void osnoise_free_hist_tool(struct osnoise_tool *tool)
+{
+ osnoise_free_histogram(tool->data);
+}
+
/*
* 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;
@@ -100,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)
@@ -126,15 +97,15 @@ cleanup:
static void osnoise_hist_update_multiple(struct osnoise_tool *tool, int cpu,
unsigned long long duration, int count)
{
- struct osnoise_hist_params *params = tool->params;
+ struct osnoise_params *params = to_osnoise_params(tool->params);
struct osnoise_hist_data *data = tool->data;
unsigned long long total_duration;
int entries = data->entries;
int bucket;
int *hist;
- if (params->output_divisor)
- duration = duration / params->output_divisor;
+ if (params->common.output_divisor)
+ duration = duration / params->common.output_divisor;
bucket = duration / data->bucket_size;
@@ -168,7 +139,7 @@ static void osnoise_destroy_trace_hist(struct osnoise_tool *tool)
*/
static int osnoise_init_trace_hist(struct osnoise_tool *tool)
{
- struct osnoise_hist_params *params = tool->params;
+ struct osnoise_params *params = to_osnoise_params(tool->params);
struct osnoise_hist_data *data = tool->data;
int bucket_size;
char buff[128];
@@ -177,7 +148,7 @@ static int osnoise_init_trace_hist(struct osnoise_tool *tool)
/*
* Set the size of the bucket.
*/
- bucket_size = params->output_divisor * params->bucket_size;
+ bucket_size = params->common.output_divisor * params->common.hist.bucket_size;
snprintf(buff, sizeof(buff), "duration.buckets=%d", bucket_size);
data->trace_hist = tracefs_hist_alloc(tool->trace.tep, "osnoise", "sample_threshold",
@@ -253,29 +224,27 @@ static void osnoise_read_trace_hist(struct osnoise_tool *tool)
*/
static void osnoise_hist_header(struct osnoise_tool *tool)
{
- struct osnoise_hist_params *params = tool->params;
+ struct osnoise_params *params = to_osnoise_params(tool->params);
struct osnoise_hist_data *data = tool->data;
struct trace_seq *s = tool->trace.seq;
char duration[26];
int cpu;
- if (params->no_header)
+ if (params->common.hist.no_header)
return;
get_duration(tool->start_time, duration, sizeof(duration));
trace_seq_printf(s, "# RTLA osnoise histogram\n");
trace_seq_printf(s, "# Time unit is %s (%s)\n",
- params->output_divisor == 1 ? "nanoseconds" : "microseconds",
- params->output_divisor == 1 ? "ns" : "us");
+ params->common.output_divisor == 1 ? "nanoseconds" : "microseconds",
+ params->common.output_divisor == 1 ? "ns" : "us");
trace_seq_printf(s, "# Duration: %s\n", duration);
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(s, "Index");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -292,22 +261,19 @@ static void osnoise_hist_header(struct osnoise_tool *tool)
* osnoise_print_summary - print the summary of the hist data to the output
*/
static void
-osnoise_print_summary(struct osnoise_hist_params *params,
+osnoise_print_summary(struct osnoise_params *params,
struct trace_instance *trace,
struct osnoise_hist_data *data)
{
int cpu;
- if (params->no_summary)
+ if (params->common.hist.no_summary)
return;
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "count:");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
-
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -315,12 +281,10 @@ osnoise_print_summary(struct osnoise_hist_params *params,
}
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "min: ");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -330,12 +294,10 @@ osnoise_print_summary(struct osnoise_hist_params *params,
}
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "avg: ");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -348,12 +310,10 @@ osnoise_print_summary(struct osnoise_hist_params *params,
}
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "max: ");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -370,8 +330,9 @@ osnoise_print_summary(struct osnoise_hist_params *params,
* osnoise_print_stats - print data for all CPUs
*/
static void
-osnoise_print_stats(struct osnoise_hist_params *params, struct osnoise_tool *tool)
+osnoise_print_stats(struct osnoise_tool *tool)
{
+ struct osnoise_params *params = to_osnoise_params(tool->params);
struct osnoise_hist_data *data = tool->data;
struct trace_instance *trace = &tool->trace;
int has_samples = 0;
@@ -383,13 +344,11 @@ osnoise_print_stats(struct osnoise_hist_params *params, struct osnoise_tool *too
for (bucket = 0; bucket < data->entries; bucket++) {
total = 0;
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "%-6d",
bucket * data->bucket_size);
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -398,7 +357,7 @@ osnoise_print_stats(struct osnoise_hist_params *params, struct osnoise_tool *too
trace_seq_printf(trace->seq, "%9d ", data->hist[cpu].samples[bucket]);
}
- if (total == 0 && !params->with_zeros) {
+ if (total == 0 && !params->common.hist.with_zeros) {
trace_seq_reset(trace->seq);
continue;
}
@@ -422,12 +381,10 @@ osnoise_print_stats(struct osnoise_hist_params *params, struct osnoise_tool *too
return;
}
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "over: ");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
@@ -446,18 +403,17 @@ osnoise_print_stats(struct osnoise_hist_params *params, struct osnoise_tool *too
/*
* osnoise_hist_usage - prints osnoise hist usage message
*/
-static void osnoise_hist_usage(char *usage)
+static void osnoise_hist_usage(void)
{
- int i;
-
- static const char * const msg[] = {
- "",
- " usage: rtla osnoise hist [-h] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
- " [-T us] [-t[file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
+ static const char * const msg_start[] = {
+ "[-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
+ " [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
" [-c cpu-list] [-H cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] \\",
- " [--no-index] [--with-zeros] [-C[=cgroup_name]] [--warm-up]",
- "",
- " -h/--help: print this menu",
+ " [--no-index] [--with-zeros] [-C [cgroup_name]] [--warm-up]",
+ NULL,
+ };
+
+ static const char * const msg_opts[] = {
" -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
" -p/--period us: osnoise period in us",
" -r/--runtime us: osnoise runtime in us",
@@ -466,10 +422,10 @@ static void osnoise_hist_usage(char *usage)
" -T/--threshold us: the minimum delta to be considered a noise",
" -c/--cpus cpu-list: list of cpus to run osnoise threads",
" -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
+ " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
" -d/--duration time[s|m|h|d]: duration of the session",
" -D/--debug: print debug info",
- " -t/--trace[file]: save the stopped trace to [file|osnoise_trace.txt]",
+ " -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
" -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
" --filter <filter>: enable a trace event filter to the previous -e event",
" --trigger <trigger>: enable a trace event trigger to the previous -e event",
@@ -487,62 +443,47 @@ static void osnoise_hist_usage(char *usage)
" in nanoseconds",
" --warm-up: let the workload run for s seconds before collecting data",
" --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
+ " --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
+ " --on-end <action>: define action to be executed at measurement end, multiple are allowed",
NULL,
};
- if (usage)
- fprintf(stderr, "%s\n", usage);
-
- fprintf(stderr, "rtla osnoise hist: a per-cpu histogram of the OS noise (version %s)\n",
- VERSION);
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
-
- if (usage)
- exit(EXIT_FAILURE);
-
- exit(EXIT_SUCCESS);
+ common_usage("osnoise", "hist", "a per-cpu histogram of the OS noise",
+ msg_start, msg_opts);
}
/*
* osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters
*/
-static struct osnoise_hist_params
+static struct common_params
*osnoise_hist_parse_args(int argc, char *argv[])
{
- struct osnoise_hist_params *params;
- struct trace_events *tevent;
+ struct osnoise_params *params;
int retval;
int c;
+ char *trace_output = NULL;
+
+ params = calloc_fatal(1, sizeof(*params));
- params = calloc(1, sizeof(*params));
- if (!params)
- exit(1);
+ actions_init(&params->common.threshold_actions);
+ actions_init(&params->common.end_actions);
/* display data in microseconds */
- params->output_divisor = 1000;
- params->bucket_size = 1;
- params->entries = 256;
+ params->common.output_divisor = 1000;
+ params->common.hist.bucket_size = 1;
+ params->common.hist.entries = 256;
while (1) {
static struct option long_options[] = {
{"auto", required_argument, 0, 'a'},
{"bucket-size", required_argument, 0, 'b'},
{"entries", required_argument, 0, 'E'},
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
- {"debug", no_argument, 0, 'D'},
- {"duration", required_argument, 0, 'd'},
- {"house-keeping", required_argument, 0, 'H'},
{"help", no_argument, 0, 'h'},
{"period", required_argument, 0, 'p'},
- {"priority", required_argument, 0, 'P'},
{"runtime", required_argument, 0, 'r'},
{"stop", required_argument, 0, 's'},
{"stop-total", required_argument, 0, 'S'},
{"trace", optional_argument, 0, 't'},
- {"event", required_argument, 0, 'e'},
{"threshold", required_argument, 0, 'T'},
{"no-header", no_argument, 0, '0'},
{"no-summary", no_argument, 0, '1'},
@@ -552,14 +493,15 @@ static struct osnoise_hist_params
{"filter", required_argument, 0, '5'},
{"warm-up", required_argument, 0, '6'},
{"trace-buffer-size", required_argument, 0, '7'},
+ {"on-threshold", required_argument, 0, '8'},
+ {"on-end", required_argument, 0, '9'},
{0, 0, 0, 0}
};
- /* getopt_long stores the option index here. */
- int option_index = 0;
+ if (common_parse_options(argc, argv, &params->common))
+ continue;
- c = getopt_long(argc, argv, "a:c:C::b:d:e:E:DhH:p:P:r:s:S:t::T:01234:5:6:7:",
- long_options, &option_index);
+ c = getopt_auto(argc, argv, long_options);
/* detect the end of the options. */
if (c == -1)
@@ -568,263 +510,141 @@ static struct osnoise_hist_params
switch (c) {
case 'a':
/* set sample stop to auto_thresh */
- params->stop_us = get_llong_from_str(optarg);
+ params->common.stop_us = get_llong_from_str(optarg);
/* set sample threshold to 1 */
params->threshold = 1;
/* set trace */
- params->trace_output = "osnoise_trace.txt";
+ if (!trace_output)
+ trace_output = "osnoise_trace.txt";
break;
case 'b':
- params->bucket_size = get_llong_from_str(optarg);
- if ((params->bucket_size == 0) || (params->bucket_size >= 1000000))
- osnoise_hist_usage("Bucket size needs to be > 0 and <= 1000000\n");
- break;
- case 'c':
- retval = parse_cpu_set(optarg, &params->monitored_cpus);
- if (retval)
- osnoise_hist_usage("\nInvalid -c cpu list\n");
- params->cpus = optarg;
- break;
- case 'C':
- params->cgroup = 1;
- if (!optarg) {
- /* will inherit this cgroup */
- params->cgroup_name = NULL;
- } else if (*optarg == '=') {
- /* skip the = */
- params->cgroup_name = ++optarg;
- }
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- params->duration = parse_seconds_duration(optarg);
- if (!params->duration)
- osnoise_hist_usage("Invalid -D duration\n");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent) {
- err_msg("Error alloc trace event");
- exit(EXIT_FAILURE);
- }
-
- if (params->events)
- tevent->next = params->events;
-
- params->events = tevent;
+ params->common.hist.bucket_size = get_llong_from_str(optarg);
+ if (params->common.hist.bucket_size == 0 ||
+ params->common.hist.bucket_size >= 1000000)
+ fatal("Bucket size needs to be > 0 and <= 1000000");
break;
case 'E':
- params->entries = get_llong_from_str(optarg);
- if ((params->entries < 10) || (params->entries > 9999999))
- osnoise_hist_usage("Entries must be > 10 and < 9999999\n");
+ params->common.hist.entries = get_llong_from_str(optarg);
+ if (params->common.hist.entries < 10 ||
+ params->common.hist.entries > 9999999)
+ fatal("Entries must be > 10 and < 9999999");
break;
case 'h':
case '?':
- osnoise_hist_usage(NULL);
- break;
- case 'H':
- params->hk_cpus = 1;
- retval = parse_cpu_set(optarg, &params->hk_cpu_set);
- if (retval) {
- err_msg("Error parsing house keeping CPUs\n");
- exit(EXIT_FAILURE);
- }
+ osnoise_hist_usage();
break;
case 'p':
params->period = get_llong_from_str(optarg);
if (params->period > 10000000)
- osnoise_hist_usage("Period longer than 10 s\n");
- break;
- case 'P':
- retval = parse_prio(optarg, &params->sched_param);
- if (retval == -1)
- osnoise_hist_usage("Invalid -P priority");
- params->set_sched = 1;
+ fatal("Period longer than 10 s");
break;
case 'r':
params->runtime = get_llong_from_str(optarg);
if (params->runtime < 100)
- osnoise_hist_usage("Runtime shorter than 100 us\n");
+ fatal("Runtime shorter than 100 us");
break;
case 's':
- params->stop_us = get_llong_from_str(optarg);
+ params->common.stop_us = get_llong_from_str(optarg);
break;
case 'S':
- params->stop_total_us = get_llong_from_str(optarg);
+ params->common.stop_total_us = get_llong_from_str(optarg);
break;
case 'T':
params->threshold = get_llong_from_str(optarg);
break;
case 't':
- if (optarg) {
- if (optarg[0] == '=')
- params->trace_output = &optarg[1];
- else
- params->trace_output = &optarg[0];
- } else if (optind < argc && argv[optind][0] != '0')
- params->trace_output = argv[optind];
- else
- params->trace_output = "osnoise_trace.txt";
+ trace_output = parse_optional_arg(argc, argv);
+ if (!trace_output)
+ trace_output = "osnoise_trace.txt";
break;
case '0': /* no header */
- params->no_header = 1;
+ params->common.hist.no_header = 1;
break;
case '1': /* no summary */
- params->no_summary = 1;
+ params->common.hist.no_summary = 1;
break;
case '2': /* no index */
- params->no_index = 1;
+ params->common.hist.no_index = 1;
break;
case '3': /* with zeros */
- params->with_zeros = 1;
+ params->common.hist.with_zeros = 1;
break;
case '4': /* trigger */
- if (params->events) {
- retval = trace_event_add_trigger(params->events, optarg);
- if (retval) {
- err_msg("Error adding trigger %s\n", optarg);
- exit(EXIT_FAILURE);
- }
- } else {
- osnoise_hist_usage("--trigger requires a previous -e\n");
- }
+ 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->events) {
- retval = trace_event_add_filter(params->events, optarg);
- if (retval) {
- err_msg("Error adding filter %s\n", optarg);
- exit(EXIT_FAILURE);
- }
- } else {
- osnoise_hist_usage("--filter requires a previous -e\n");
- }
+ if (params->common.events)
+ trace_event_add_filter(params->common.events, optarg);
+ else
+ fatal("--filter requires a previous -e");
break;
case '6':
- params->warmup = get_llong_from_str(optarg);
+ params->common.warmup = get_llong_from_str(optarg);
break;
case '7':
- params->buffer_size = get_llong_from_str(optarg);
+ params->common.buffer_size = get_llong_from_str(optarg);
+ break;
+ case '8':
+ retval = actions_parse(&params->common.threshold_actions, optarg,
+ "osnoise_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", optarg);
+ break;
+ case '9':
+ retval = actions_parse(&params->common.end_actions, optarg,
+ "osnoise_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", optarg);
break;
default:
- osnoise_hist_usage("Invalid option");
+ fatal("Invalid option");
}
}
- if (geteuid()) {
- err_msg("rtla needs root permission\n");
- exit(EXIT_FAILURE);
- }
+ if (trace_output)
+ actions_add_trace_output(&params->common.threshold_actions, trace_output);
+
+ if (geteuid())
+ fatal("rtla needs root permission");
- if (params->no_index && !params->with_zeros)
- osnoise_hist_usage("no-index set and with-zeros not set - it does not make sense");
+ if (params->common.hist.no_index && !params->common.hist.with_zeros)
+ fatal("no-index set and with-zeros not set - it does not make sense");
- return params;
+ return &params->common;
}
/*
* osnoise_hist_apply_config - apply the hist configs to the initialized tool
*/
static int
-osnoise_hist_apply_config(struct osnoise_tool *tool, struct osnoise_hist_params *params)
+osnoise_hist_apply_config(struct osnoise_tool *tool)
{
- int retval;
-
- if (!params->sleep_time)
- params->sleep_time = 1;
-
- if (params->cpus) {
- retval = osnoise_set_cpus(tool->context, params->cpus);
- if (retval) {
- err_msg("Failed to apply CPUs config\n");
- goto out_err;
- }
- }
-
- if (params->runtime || params->period) {
- retval = osnoise_set_runtime_period(tool->context,
- params->runtime,
- params->period);
- if (retval) {
- err_msg("Failed to set runtime and/or period\n");
- goto out_err;
- }
- }
-
- if (params->stop_us) {
- retval = osnoise_set_stop_us(tool->context, params->stop_us);
- if (retval) {
- err_msg("Failed to set stop us\n");
- goto out_err;
- }
- }
-
- if (params->stop_total_us) {
- retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
- if (retval) {
- err_msg("Failed to set stop total us\n");
- goto out_err;
- }
- }
-
- if (params->threshold) {
- retval = osnoise_set_tracing_thresh(tool->context, params->threshold);
- if (retval) {
- err_msg("Failed to set tracing_thresh\n");
- goto out_err;
- }
- }
-
- if (params->hk_cpus) {
- retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set),
- &params->hk_cpu_set);
- if (retval == -1) {
- err_msg("Failed to set rtla to the house keeping CPUs\n");
- goto out_err;
- }
- } else if (params->cpus) {
- /*
- * Even if the user do not set a house-keeping CPU, try to
- * move rtla to a CPU set different to the one where the user
- * set the workload to run.
- *
- * No need to check results as this is an automatic attempt.
- */
- auto_house_keeping(&params->monitored_cpus);
- }
-
- return 0;
-
-out_err:
- return -1;
+ return osnoise_apply_config(tool, to_osnoise_params(tool->params));
}
/*
* osnoise_init_hist - initialize a osnoise hist tool with parameters
*/
static struct osnoise_tool
-*osnoise_init_hist(struct osnoise_hist_params *params)
+*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->entries, params->bucket_size);
+ tool->data = osnoise_alloc_histogram(params->hist.entries,
+ params->hist.bucket_size);
if (!tool->data)
goto out_err;
- tool->params = params;
-
return tool;
out_err:
@@ -832,172 +652,35 @@ out_err:
return NULL;
}
-static int stop_tracing;
-static void stop_hist(int sig)
-{
- stop_tracing = 1;
-}
-
-/*
- * osnoise_hist_set_signals - handles the signal to stop the tool
- */
-static void
-osnoise_hist_set_signals(struct osnoise_hist_params *params)
-{
- signal(SIGINT, stop_hist);
- if (params->duration) {
- signal(SIGALRM, stop_hist);
- alarm(params->duration);
- }
-}
-
-int osnoise_hist_main(int argc, char *argv[])
+static int osnoise_hist_enable(struct osnoise_tool *tool)
{
- struct osnoise_hist_params *params;
- struct osnoise_tool *record = NULL;
- struct osnoise_tool *tool = NULL;
- struct trace_instance *trace;
- int return_value = 1;
int retval;
- params = osnoise_hist_parse_args(argc, argv);
- if (!params)
- exit(1);
-
- tool = osnoise_init_hist(params);
- if (!tool) {
- err_msg("Could not init osnoise hist\n");
- goto out_exit;
- }
-
- retval = osnoise_hist_apply_config(tool, params);
- if (retval) {
- err_msg("Could not apply config\n");
- goto out_destroy;
- }
-
- trace = &tool->trace;
-
- retval = enable_osnoise(trace);
- if (retval) {
- err_msg("Failed to enable osnoise tracer\n");
- goto out_destroy;
- }
-
retval = osnoise_init_trace_hist(tool);
if (retval)
- goto out_destroy;
-
- if (params->set_sched) {
- retval = set_comm_sched_attr("osnoise/", &params->sched_param);
- if (retval) {
- err_msg("Failed to set sched parameters\n");
- goto out_free;
- }
- }
+ return retval;
- if (params->cgroup) {
- retval = set_comm_cgroup("timerlat/", params->cgroup_name);
- if (!retval) {
- err_msg("Failed to move threads to cgroup\n");
- goto out_free;
- }
- }
-
- if (params->trace_output) {
- record = osnoise_init_trace_tool("osnoise");
- if (!record) {
- err_msg("Failed to enable the trace instance\n");
- goto out_free;
- }
-
- if (params->events) {
- retval = trace_events_enable(&record->trace, params->events);
- if (retval)
- goto out_hist;
- }
-
- if (params->buffer_size > 0) {
- retval = trace_set_buffer_size(&record->trace, params->buffer_size);
- if (retval)
- goto out_hist;
- }
- }
-
- /*
- * Start the tracer here, after having set all instances.
- *
- * Let the trace instance start first for the case of hitting a stop
- * tracing while enabling other instances. The trace instance is the
- * one with most valuable information.
- */
- if (params->trace_output)
- trace_instance_start(&record->trace);
- trace_instance_start(trace);
-
- if (params->warmup > 0) {
- debug_msg("Warming up for %d seconds\n", params->warmup);
- sleep(params->warmup);
- if (stop_tracing)
- goto out_hist;
-
- /*
- * Clean up the buffer. The osnoise workload do not run
- * with tracing off to avoid creating a performance penalty
- * when not needed.
- */
- retval = tracefs_instance_file_write(trace->inst, "trace", "");
- if (retval < 0) {
- debug_msg("Error cleaning up the buffer");
- goto out_hist;
- }
-
- }
-
- tool->start_time = time(NULL);
- osnoise_hist_set_signals(params);
-
- while (!stop_tracing) {
- sleep(params->sleep_time);
-
- retval = tracefs_iterate_raw_events(trace->tep,
- trace->inst,
- NULL,
- 0,
- collect_registered_events,
- trace);
- if (retval < 0) {
- err_msg("Error iterating on events\n");
- goto out_hist;
- }
+ return osnoise_enable(tool);
+}
- if (osnoise_trace_is_off(tool, record))
- break;
- }
+static int osnoise_hist_main_loop(struct osnoise_tool *tool)
+{
+ int retval;
+ retval = hist_main_loop(tool);
osnoise_read_trace_hist(tool);
- osnoise_print_stats(params, tool);
-
- return_value = 0;
-
- if (osnoise_trace_is_off(tool, record)) {
- printf("rtla osnoise hit stop tracing\n");
- if (params->trace_output) {
- printf(" Saving trace to %s\n", params->trace_output);
- save_trace_to_file(record->trace.inst, params->trace_output);
- }
- }
-
-out_hist:
- trace_events_destroy(&record->trace, params->events);
- params->events = NULL;
-out_free:
- osnoise_free_histogram(tool->data);
-out_destroy:
- osnoise_destroy_tool(record);
- osnoise_destroy_tool(tool);
- free(params);
-out_exit:
- exit(return_value);
+ return retval;
}
+
+struct tool_ops osnoise_hist_ops = {
+ .tracer = "osnoise",
+ .comm_prefix = "osnoise/",
+ .parse_args = osnoise_hist_parse_args,
+ .init_tool = osnoise_init_hist,
+ .apply_config = osnoise_hist_apply_config,
+ .enable = osnoise_hist_enable,
+ .main = osnoise_hist_main_loop,
+ .print_stats = osnoise_print_stats,
+ .free = osnoise_free_hist_tool,
+};
diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c
index 4772677ac762..244bdce022ad 100644
--- a/tools/tracing/rtla/src/osnoise_top.c
+++ b/tools/tracing/rtla/src/osnoise_top.c
@@ -11,43 +11,8 @@
#include <unistd.h>
#include <stdio.h>
#include <time.h>
-#include <sched.h>
#include "osnoise.h"
-#include "utils.h"
-
-enum osnoise_mode {
- MODE_OSNOISE = 0,
- MODE_HWNOISE
-};
-
-/*
- * osnoise top parameters
- */
-struct osnoise_top_params {
- char *cpus;
- cpu_set_t monitored_cpus;
- char *trace_output;
- char *cgroup_name;
- unsigned long long runtime;
- unsigned long long period;
- long long threshold;
- long long stop_us;
- long long stop_total_us;
- int sleep_time;
- int duration;
- int quiet;
- int set_sched;
- int cgroup;
- int hk_cpus;
- int warmup;
- int buffer_size;
- int pretty_output;
- cpu_set_t hk_cpu_set;
- struct sched_attr sched_param;
- struct trace_events *events;
- enum osnoise_mode mode;
-};
struct osnoise_top_cpu {
unsigned long long sum_runtime;
@@ -66,23 +31,26 @@ struct osnoise_top_cpu {
struct osnoise_top_data {
struct osnoise_top_cpu *cpu_data;
- int nr_cpus;
};
/*
* osnoise_free_top - free runtime data
*/
-static void
-osnoise_free_top(struct osnoise_top_data *data)
+static void osnoise_free_top(struct osnoise_top_data *data)
{
free(data->cpu_data);
free(data);
}
+static void osnoise_free_top_tool(struct osnoise_tool *tool)
+{
+ osnoise_free_top(tool->data);
+}
+
/*
* 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;
@@ -90,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)
@@ -158,13 +124,14 @@ osnoise_top_handler(struct trace_seq *s, struct tep_record *record,
*/
static void osnoise_top_header(struct osnoise_tool *top)
{
- struct osnoise_top_params *params = top->params;
+ struct osnoise_params *params = to_osnoise_params(top->params);
struct trace_seq *s = top->trace.seq;
+ bool pretty = params->common.pretty_output;
char duration[26];
get_duration(top->start_time, duration, sizeof(duration));
- if (params->pretty_output)
+ if (pretty)
trace_seq_printf(s, "\033[2;37;40m");
trace_seq_printf(s, " ");
@@ -178,13 +145,13 @@ static void osnoise_top_header(struct osnoise_tool *top)
trace_seq_printf(s, " ");
- if (params->pretty_output)
+ if (pretty)
trace_seq_printf(s, "\033[0;0;0m");
trace_seq_printf(s, "\n");
trace_seq_printf(s, "duration: %9s | time is in us\n", duration);
- if (params->pretty_output)
+ if (pretty)
trace_seq_printf(s, "\033[2;30;47m");
trace_seq_printf(s, "CPU Period Runtime ");
@@ -199,7 +166,7 @@ static void osnoise_top_header(struct osnoise_tool *top)
trace_seq_printf(s, " IRQ Softirq Thread");
eol:
- if (params->pretty_output)
+ if (pretty)
trace_seq_printf(s, "\033[0;0;0m");
trace_seq_printf(s, "\n");
}
@@ -218,7 +185,7 @@ static void clear_terminal(struct trace_seq *seq)
*/
static void osnoise_top_print(struct osnoise_tool *tool, int cpu)
{
- struct osnoise_top_params *params = tool->params;
+ struct osnoise_params *params = to_osnoise_params(tool->params);
struct trace_seq *s = tool->trace.seq;
struct osnoise_top_cpu *cpu_data;
struct osnoise_top_data *data;
@@ -258,23 +225,18 @@ static void osnoise_top_print(struct osnoise_tool *tool, int cpu)
* osnoise_print_stats - print data for all cpus
*/
static void
-osnoise_print_stats(struct osnoise_top_params *params, struct osnoise_tool *top)
+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->quiet)
+ if (!params->common.quiet)
clear_terminal(trace->seq);
osnoise_top_header(top);
- for (i = 0; i < nr_cpus; i++) {
- if (params->cpus && !CPU_ISSET(i, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(i, &params->common) {
osnoise_top_print(top, i);
}
@@ -286,16 +248,18 @@ osnoise_print_stats(struct osnoise_top_params *params, struct osnoise_tool *top)
/*
* osnoise_top_usage - prints osnoise top usage message
*/
-static void osnoise_top_usage(struct osnoise_top_params *params, char *usage)
+static void osnoise_top_usage(struct osnoise_params *params)
{
- int i;
+ const char *tool, *mode, *desc;
+
+ static const char * const msg_start[] = {
+ "[-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
+ " [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
+ " [-c cpu-list] [-H cpu-list] [-P priority] [-C [cgroup_name]] [--warm-up s]",
+ NULL,
+ };
- static const char * const msg[] = {
- " [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
- " [-T us] [-t[file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
- " [-c cpu-list] [-H cpu-list] [-P priority] [-C[=cgroup_name]] [--warm-up s]",
- "",
- " -h/--help: print this menu",
+ static const char * const msg_opts[] = {
" -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
" -p/--period us: osnoise period in us",
" -r/--runtime us: osnoise runtime in us",
@@ -304,10 +268,10 @@ static void osnoise_top_usage(struct osnoise_top_params *params, char *usage)
" -T/--threshold us: the minimum delta to be considered a noise",
" -c/--cpus cpu-list: list of cpus to run osnoise threads",
" -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
+ " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
" -d/--duration time[s|m|h|d]: duration of the session",
" -D/--debug: print debug info",
- " -t/--trace[file]: save the stopped trace to [file|osnoise_trace.txt]",
+ " -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
" -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
" --filter <filter>: enable a trace event filter to the previous -e event",
" --trigger <trigger>: enable a trace event trigger to the previous -e event",
@@ -320,50 +284,38 @@ static void osnoise_top_usage(struct osnoise_top_params *params, char *usage)
" in nanoseconds",
" --warm-up s: let the workload run for s seconds before collecting data",
" --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
+ " --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
+ " --on-end: define action to be executed at measurement end, multiple are allowed",
NULL,
};
- if (usage)
- fprintf(stderr, "%s\n", usage);
-
if (params->mode == MODE_OSNOISE) {
- fprintf(stderr,
- "rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n",
- VERSION);
-
- fprintf(stderr, " usage: rtla osnoise [top]");
+ tool = "osnoise";
+ mode = "top";
+ desc = "a per-cpu summary of the OS noise";
+ } else {
+ tool = "hwnoise";
+ mode = "";
+ desc = "a summary of hardware-related noise";
}
- if (params->mode == MODE_HWNOISE) {
- fprintf(stderr,
- "rtla hwnoise: a summary of hardware-related noise (version %s)\n",
- VERSION);
-
- fprintf(stderr, " usage: rtla hwnoise");
- }
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
-
- if (usage)
- exit(EXIT_FAILURE);
-
- exit(EXIT_SUCCESS);
+ common_usage(tool, mode, desc, msg_start, msg_opts);
}
/*
* osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
*/
-struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv)
+struct common_params *osnoise_top_parse_args(int argc, char **argv)
{
- struct osnoise_top_params *params;
- struct trace_events *tevent;
+ struct osnoise_params *params;
int retval;
int c;
+ char *trace_output = NULL;
+
+ params = calloc_fatal(1, sizeof(*params));
- params = calloc(1, sizeof(*params));
- if (!params)
- exit(1);
+ actions_init(&params->common.threshold_actions);
+ actions_init(&params->common.end_actions);
if (strcmp(argv[0], "hwnoise") == 0) {
params->mode = MODE_HWNOISE;
@@ -377,15 +329,8 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv)
while (1) {
static struct option long_options[] = {
{"auto", required_argument, 0, 'a'},
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
- {"debug", no_argument, 0, 'D'},
- {"duration", required_argument, 0, 'd'},
- {"event", required_argument, 0, 'e'},
- {"house-keeping", required_argument, 0, 'H'},
{"help", no_argument, 0, 'h'},
{"period", required_argument, 0, 'p'},
- {"priority", required_argument, 0, 'P'},
{"quiet", no_argument, 0, 'q'},
{"runtime", required_argument, 0, 'r'},
{"stop", required_argument, 0, 's'},
@@ -396,14 +341,15 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv)
{"filter", required_argument, 0, '1'},
{"warm-up", required_argument, 0, '2'},
{"trace-buffer-size", required_argument, 0, '3'},
+ {"on-threshold", required_argument, 0, '4'},
+ {"on-end", required_argument, 0, '5'},
{0, 0, 0, 0}
};
- /* getopt_long stores the option index here. */
- int option_index = 0;
+ if (common_parse_options(argc, argv, &params->common))
+ continue;
- c = getopt_long(argc, argv, "a:c:C::d:De:hH:p:P:qr:s:S:t::T:0:1:2:3:",
- long_options, &option_index);
+ c = getopt_auto(argc, argv, long_options);
/* Detect the end of the options. */
if (c == -1)
@@ -412,195 +358,103 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv)
switch (c) {
case 'a':
/* set sample stop to auto_thresh */
- params->stop_us = get_llong_from_str(optarg);
+ params->common.stop_us = get_llong_from_str(optarg);
/* set sample threshold to 1 */
params->threshold = 1;
/* set trace */
- params->trace_output = "osnoise_trace.txt";
-
- break;
- case 'c':
- retval = parse_cpu_set(optarg, &params->monitored_cpus);
- if (retval)
- osnoise_top_usage(params, "\nInvalid -c cpu list\n");
- params->cpus = optarg;
- break;
- case 'C':
- params->cgroup = 1;
- if (!optarg) {
- /* will inherit this cgroup */
- params->cgroup_name = NULL;
- } else if (*optarg == '=') {
- /* skip the = */
- params->cgroup_name = ++optarg;
- }
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- params->duration = parse_seconds_duration(optarg);
- if (!params->duration)
- osnoise_top_usage(params, "Invalid -d duration\n");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent) {
- err_msg("Error alloc trace event");
- exit(EXIT_FAILURE);
- }
-
- if (params->events)
- tevent->next = params->events;
- params->events = tevent;
+ if (!trace_output)
+ trace_output = "osnoise_trace.txt";
break;
case 'h':
case '?':
- osnoise_top_usage(params, NULL);
- break;
- case 'H':
- params->hk_cpus = 1;
- retval = parse_cpu_set(optarg, &params->hk_cpu_set);
- if (retval) {
- err_msg("Error parsing house keeping CPUs\n");
- exit(EXIT_FAILURE);
- }
+ osnoise_top_usage(params);
break;
case 'p':
params->period = get_llong_from_str(optarg);
if (params->period > 10000000)
- osnoise_top_usage(params, "Period longer than 10 s\n");
- break;
- case 'P':
- retval = parse_prio(optarg, &params->sched_param);
- if (retval == -1)
- osnoise_top_usage(params, "Invalid -P priority");
- params->set_sched = 1;
+ fatal("Period longer than 10 s");
break;
case 'q':
- params->quiet = 1;
+ params->common.quiet = 1;
break;
case 'r':
params->runtime = get_llong_from_str(optarg);
if (params->runtime < 100)
- osnoise_top_usage(params, "Runtime shorter than 100 us\n");
+ fatal("Runtime shorter than 100 us");
break;
case 's':
- params->stop_us = get_llong_from_str(optarg);
+ params->common.stop_us = get_llong_from_str(optarg);
break;
case 'S':
- params->stop_total_us = get_llong_from_str(optarg);
+ params->common.stop_total_us = get_llong_from_str(optarg);
break;
case 't':
- if (optarg) {
- if (optarg[0] == '=')
- params->trace_output = &optarg[1];
- else
- params->trace_output = &optarg[0];
- } else if (optind < argc && argv[optind][0] != '-')
- params->trace_output = argv[optind];
- else
- params->trace_output = "osnoise_trace.txt";
+ trace_output = parse_optional_arg(argc, argv);
+ if (!trace_output)
+ trace_output = "osnoise_trace.txt";
break;
case 'T':
params->threshold = get_llong_from_str(optarg);
break;
case '0': /* trigger */
- if (params->events) {
- retval = trace_event_add_trigger(params->events, optarg);
- if (retval) {
- err_msg("Error adding trigger %s\n", optarg);
- exit(EXIT_FAILURE);
- }
- } else {
- osnoise_top_usage(params, "--trigger requires a previous -e\n");
- }
+ 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->events) {
- retval = trace_event_add_filter(params->events, optarg);
- if (retval) {
- err_msg("Error adding filter %s\n", optarg);
- exit(EXIT_FAILURE);
- }
- } else {
- osnoise_top_usage(params, "--filter requires a previous -e\n");
- }
+ if (params->common.events)
+ trace_event_add_filter(params->common.events, optarg);
+ else
+ fatal("--filter requires a previous -e");
break;
case '2':
- params->warmup = get_llong_from_str(optarg);
+ params->common.warmup = get_llong_from_str(optarg);
break;
case '3':
- params->buffer_size = get_llong_from_str(optarg);
+ params->common.buffer_size = get_llong_from_str(optarg);
+ break;
+ case '4':
+ retval = actions_parse(&params->common.threshold_actions, optarg,
+ "osnoise_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", optarg);
+ break;
+ case '5':
+ retval = actions_parse(&params->common.end_actions, optarg,
+ "osnoise_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", optarg);
break;
default:
- osnoise_top_usage(params, "Invalid option");
+ fatal("Invalid option");
}
}
- if (geteuid()) {
- err_msg("osnoise needs root permission\n");
- exit(EXIT_FAILURE);
- }
+ if (trace_output)
+ actions_add_trace_output(&params->common.threshold_actions, trace_output);
- return params;
+ if (geteuid())
+ fatal("osnoise needs root permission");
+
+ return &params->common;
}
/*
* osnoise_top_apply_config - apply the top configs to the initialized tool
*/
static int
-osnoise_top_apply_config(struct osnoise_tool *tool, struct osnoise_top_params *params)
+osnoise_top_apply_config(struct osnoise_tool *tool)
{
+ struct osnoise_params *params = to_osnoise_params(tool->params);
int retval;
- if (!params->sleep_time)
- params->sleep_time = 1;
-
- if (params->cpus) {
- retval = osnoise_set_cpus(tool->context, params->cpus);
- if (retval) {
- err_msg("Failed to apply CPUs config\n");
- goto out_err;
- }
- }
-
- if (params->runtime || params->period) {
- retval = osnoise_set_runtime_period(tool->context,
- params->runtime,
- params->period);
- if (retval) {
- err_msg("Failed to set runtime and/or period\n");
- goto out_err;
- }
- }
-
- if (params->stop_us) {
- retval = osnoise_set_stop_us(tool->context, params->stop_us);
- if (retval) {
- err_msg("Failed to set stop us\n");
- goto out_err;
- }
- }
-
- if (params->stop_total_us) {
- retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
- if (retval) {
- err_msg("Failed to set stop total us\n");
- goto out_err;
- }
- }
-
- if (params->threshold) {
- retval = osnoise_set_tracing_thresh(tool->context, params->threshold);
- if (retval) {
- err_msg("Failed to set tracing_thresh\n");
- goto out_err;
- }
- }
+ retval = osnoise_apply_config(tool, params);
+ if (retval)
+ goto out_err;
if (params->mode == MODE_HWNOISE) {
retval = osnoise_set_irq_disable(tool->context, 1);
@@ -610,26 +464,8 @@ osnoise_top_apply_config(struct osnoise_tool *tool, struct osnoise_top_params *p
}
}
- if (params->hk_cpus) {
- retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set),
- &params->hk_cpu_set);
- if (retval == -1) {
- err_msg("Failed to set rtla to the house keeping CPUs\n");
- goto out_err;
- }
- } else if (params->cpus) {
- /*
- * Even if the user do not set a house-keeping CPU, try to
- * move rtla to a CPU set different to the one where the user
- * set the workload to run.
- *
- * No need to check results as this is an automatic attempt.
- */
- auto_house_keeping(&params->monitored_cpus);
- }
-
- if (isatty(STDOUT_FILENO) && !params->quiet)
- params->pretty_output = 1;
+ if (isatty(STDOUT_FILENO) && !params->common.quiet)
+ params->common.pretty_output = 1;
return 0;
@@ -640,193 +476,34 @@ out_err:
/*
* osnoise_init_top - initialize a osnoise top tool with parameters
*/
-struct osnoise_tool *osnoise_init_top(struct osnoise_top_params *params)
+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;
}
- tool->params = params;
-
tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise",
osnoise_top_handler, NULL);
return tool;
}
-static int stop_tracing;
-static void stop_top(int sig)
-{
- stop_tracing = 1;
-}
-
-/*
- * osnoise_top_set_signals - handles the signal to stop the tool
- */
-static void osnoise_top_set_signals(struct osnoise_top_params *params)
-{
- signal(SIGINT, stop_top);
- if (params->duration) {
- signal(SIGALRM, stop_top);
- alarm(params->duration);
- }
-}
-
-int osnoise_top_main(int argc, char **argv)
-{
- struct osnoise_top_params *params;
- struct osnoise_tool *record = NULL;
- struct osnoise_tool *tool = NULL;
- struct trace_instance *trace;
- int return_value = 1;
- int retval;
-
- params = osnoise_top_parse_args(argc, argv);
- if (!params)
- exit(1);
-
- tool = osnoise_init_top(params);
- if (!tool) {
- err_msg("Could not init osnoise top\n");
- goto out_exit;
- }
-
- retval = osnoise_top_apply_config(tool, params);
- if (retval) {
- err_msg("Could not apply config\n");
- goto out_free;
- }
-
- trace = &tool->trace;
-
- retval = enable_osnoise(trace);
- if (retval) {
- err_msg("Failed to enable osnoise tracer\n");
- goto out_free;
- }
-
- if (params->set_sched) {
- retval = set_comm_sched_attr("osnoise/", &params->sched_param);
- if (retval) {
- err_msg("Failed to set sched parameters\n");
- goto out_free;
- }
- }
-
- if (params->cgroup) {
- retval = set_comm_cgroup("osnoise/", params->cgroup_name);
- if (!retval) {
- err_msg("Failed to move threads to cgroup\n");
- goto out_free;
- }
- }
-
- if (params->trace_output) {
- record = osnoise_init_trace_tool("osnoise");
- if (!record) {
- err_msg("Failed to enable the trace instance\n");
- goto out_free;
- }
-
- if (params->events) {
- retval = trace_events_enable(&record->trace, params->events);
- if (retval)
- goto out_top;
- }
-
- if (params->buffer_size > 0) {
- retval = trace_set_buffer_size(&record->trace, params->buffer_size);
- if (retval)
- goto out_top;
- }
- }
-
- /*
- * Start the tracer here, after having set all instances.
- *
- * Let the trace instance start first for the case of hitting a stop
- * tracing while enabling other instances. The trace instance is the
- * one with most valuable information.
- */
- if (params->trace_output)
- trace_instance_start(&record->trace);
- trace_instance_start(trace);
-
- if (params->warmup > 0) {
- debug_msg("Warming up for %d seconds\n", params->warmup);
- sleep(params->warmup);
- if (stop_tracing)
- goto out_top;
-
- /*
- * Clean up the buffer. The osnoise workload do not run
- * with tracing off to avoid creating a performance penalty
- * when not needed.
- */
- retval = tracefs_instance_file_write(trace->inst, "trace", "");
- if (retval < 0) {
- debug_msg("Error cleaning up the buffer");
- goto out_top;
- }
-
- }
-
- tool->start_time = time(NULL);
- osnoise_top_set_signals(params);
-
- while (!stop_tracing) {
- sleep(params->sleep_time);
-
- retval = tracefs_iterate_raw_events(trace->tep,
- trace->inst,
- NULL,
- 0,
- collect_registered_events,
- trace);
- if (retval < 0) {
- err_msg("Error iterating on events\n");
- goto out_top;
- }
-
- if (!params->quiet)
- osnoise_print_stats(params, tool);
-
- if (osnoise_trace_is_off(tool, record))
- break;
-
- }
-
- osnoise_print_stats(params, tool);
-
- return_value = 0;
-
- if (osnoise_trace_is_off(tool, record)) {
- printf("osnoise hit stop tracing\n");
- if (params->trace_output) {
- printf(" Saving trace to %s\n", params->trace_output);
- save_trace_to_file(record->trace.inst, params->trace_output);
- }
- }
-
-out_top:
- trace_events_destroy(&record->trace, params->events);
- params->events = NULL;
-out_free:
- osnoise_free_top(tool->data);
- osnoise_destroy_tool(record);
- osnoise_destroy_tool(tool);
- free(params);
-out_exit:
- exit(return_value);
-}
+struct tool_ops osnoise_top_ops = {
+ .tracer = "osnoise",
+ .comm_prefix = "osnoise/",
+ .parse_args = osnoise_top_parse_args,
+ .init_tool = osnoise_init_top,
+ .apply_config = osnoise_top_apply_config,
+ .enable = osnoise_enable,
+ .main = top_main_loop,
+ .print_stats = osnoise_print_stats,
+ .free = osnoise_free_top_tool,
+};
diff --git a/tools/tracing/rtla/src/timerlat.bpf.c b/tools/tracing/rtla/src/timerlat.bpf.c
new file mode 100644
index 000000000000..549d2d2191d2
--- /dev/null
+++ b/tools/tracing/rtla/src/timerlat.bpf.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_tracing.h>
+#include <stdbool.h>
+#include "timerlat_bpf.h"
+
+#define nosubprog __always_inline
+#define MAX_ENTRIES_DEFAULT 4096
+
+char LICENSE[] SEC("license") = "GPL";
+
+struct trace_event_raw_timerlat_sample {
+ unsigned long long timer_latency;
+ int context;
+} __attribute__((preserve_access_index));
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, MAX_ENTRIES_DEFAULT);
+ __type(key, unsigned int);
+ __type(value, unsigned long long);
+} hist_irq SEC(".maps"), hist_thread SEC(".maps"), hist_user SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, SUMMARY_FIELD_N);
+ __type(key, unsigned int);
+ __type(value, unsigned long long);
+} summary_irq SEC(".maps"), summary_thread SEC(".maps"), summary_user SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, unsigned int);
+ __type(value, unsigned long long);
+} stop_tracing SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(max_entries, 1);
+} signal_stop_tracing SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(key_size, sizeof(unsigned int));
+ __uint(max_entries, 1);
+ __array(values, unsigned int (void *));
+} bpf_action SEC(".maps") = {
+ .values = {
+ [0] = 0
+ },
+};
+
+/* Params to be set by rtla */
+const volatile int bucket_size = 1;
+const volatile int output_divisor = 1000;
+const volatile int entries = 256;
+const volatile int irq_threshold;
+const volatile int thread_threshold;
+const volatile bool aa_only;
+
+nosubprog unsigned long long map_get(void *map,
+ unsigned int key)
+{
+ unsigned long long *value_ptr;
+
+ value_ptr = bpf_map_lookup_elem(map, &key);
+
+ return !value_ptr ? 0 : *value_ptr;
+}
+
+nosubprog void map_set(void *map,
+ unsigned int key,
+ unsigned long long value)
+{
+ bpf_map_update_elem(map, &key, &value, BPF_ANY);
+}
+
+nosubprog void map_increment(void *map,
+ unsigned int key)
+{
+ map_set(map, key, map_get(map, key) + 1);
+}
+
+nosubprog void update_main_hist(void *map,
+ int bucket)
+{
+ if (entries == 0)
+ /* No histogram */
+ return;
+
+ if (bucket >= entries)
+ /* Overflow */
+ return;
+
+ map_increment(map, bucket);
+}
+
+nosubprog void update_summary(void *map,
+ unsigned long long latency,
+ int bucket)
+{
+ if (aa_only)
+ /* Auto-analysis only, nothing to be done here */
+ return;
+
+ map_set(map, SUMMARY_CURRENT, latency);
+
+ if (bucket >= entries)
+ /* Overflow */
+ map_increment(map, SUMMARY_OVERFLOW);
+
+ if (latency > map_get(map, SUMMARY_MAX))
+ map_set(map, SUMMARY_MAX, latency);
+
+ if (latency < map_get(map, SUMMARY_MIN) || map_get(map, SUMMARY_COUNT) == 0)
+ map_set(map, SUMMARY_MIN, latency);
+
+ map_increment(map, SUMMARY_COUNT);
+ map_set(map, SUMMARY_SUM, map_get(map, SUMMARY_SUM) + latency);
+}
+
+nosubprog void set_stop_tracing(struct trace_event_raw_timerlat_sample *tp_args)
+{
+ int value = 0;
+
+ /* Suppress further sample processing */
+ map_set(&stop_tracing, 0, 1);
+
+ /* Signal to userspace */
+ bpf_ringbuf_output(&signal_stop_tracing, &value, sizeof(value), 0);
+
+ /*
+ * Call into BPF action program, if attached.
+ * Otherwise, just silently fail.
+ */
+ bpf_tail_call(tp_args, &bpf_action, 0);
+}
+
+SEC("tp/osnoise/timerlat_sample")
+int handle_timerlat_sample(struct trace_event_raw_timerlat_sample *tp_args)
+{
+ unsigned long long latency, latency_us;
+ int bucket;
+
+ if (map_get(&stop_tracing, 0))
+ return 0;
+
+ latency = tp_args->timer_latency / output_divisor;
+ latency_us = tp_args->timer_latency / 1000;
+ bucket = latency / bucket_size;
+
+ if (tp_args->context == 0) {
+ update_main_hist(&hist_irq, bucket);
+ update_summary(&summary_irq, latency, bucket);
+
+ if (irq_threshold != 0 && latency_us >= irq_threshold)
+ set_stop_tracing(tp_args);
+ } else if (tp_args->context == 1) {
+ update_main_hist(&hist_thread, bucket);
+ update_summary(&summary_thread, latency, bucket);
+
+ if (thread_threshold != 0 && latency_us >= thread_threshold)
+ set_stop_tracing(tp_args);
+ } else {
+ update_main_hist(&hist_user, bucket);
+ update_summary(&summary_user, latency, bucket);
+
+ if (thread_threshold != 0 && latency_us >= thread_threshold)
+ set_stop_tracing(tp_args);
+ }
+
+ return 0;
+}
diff --git a/tools/tracing/rtla/src/timerlat.c b/tools/tracing/rtla/src/timerlat.c
index 21cdcc5c4a29..f8c057518d22 100644
--- a/tools/tracing/rtla/src/timerlat.c
+++ b/tools/tracing/rtla/src/timerlat.c
@@ -2,17 +2,234 @@
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
+#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+#include <sched.h>
#include "timerlat.h"
+#include "timerlat_aa.h"
+#include "timerlat_bpf.h"
+
+#define DEFAULT_TIMERLAT_PERIOD 1000 /* 1ms */
+
+static int dma_latency_fd = -1;
+
+/*
+ * timerlat_apply_config - apply common configs to the initialized tool
+ */
+int
+timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params)
+{
+ int retval;
+ 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 (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")) {
+ debug_msg("osnoise:timerlat_sample missing, disabling BPF\n");
+ params->mode = TRACING_MODE_TRACEFS;
+ } else {
+ retval = timerlat_bpf_init(params);
+ if (retval) {
+ debug_msg("Could not enable BPF\n");
+ params->mode = TRACING_MODE_TRACEFS;
+ }
+ }
+
+ /* Check if BPF action program is requested but BPF is not available */
+ if (params->bpf_action_program) {
+ if (params->mode == TRACING_MODE_TRACEFS) {
+ err_msg("BPF actions are not supported in tracefs-only mode\n");
+ goto out_err;
+ }
+
+ if (timerlat_load_bpf_action_program(params->bpf_action_program))
+ goto out_err;
+ }
+
+ retval = osnoise_set_timerlat_period_us(tool->context,
+ params->timerlat_period_us ?
+ params->timerlat_period_us :
+ DEFAULT_TIMERLAT_PERIOD);
+ if (retval) {
+ err_msg("Failed to set timerlat period\n");
+ goto out_err;
+ }
+
+
+ retval = osnoise_set_print_stack(tool->context, params->print_stack);
+ if (retval) {
+ err_msg("Failed to set print stack\n");
+ goto out_err;
+ }
+
+ /*
+ * If the user did not specify a type of thread, try user-threads first.
+ * Fall back to kernel threads otherwise.
+ */
+ if (!params->common.kernel_workload && !params->common.user_data) {
+ retval = tracefs_file_exists(NULL, "osnoise/per_cpu/cpu0/timerlat_fd");
+ if (retval) {
+ debug_msg("User-space interface detected, setting user-threads\n");
+ params->common.user_workload = 1;
+ params->common.user_data = 1;
+ } else {
+ debug_msg("User-space interface not detected, setting kernel-threads\n");
+ params->common.kernel_workload = 1;
+ }
+ }
+
+ return common_apply_config(tool, &params->common);
+
+out_err:
+ return -1;
+}
+
+int timerlat_enable(struct osnoise_tool *tool)
+{
+ struct timerlat_params *params = to_timerlat_params(tool->params);
+ int retval, i;
+
+ if (params->dma_latency >= 0) {
+ dma_latency_fd = set_cpu_dma_latency(params->dma_latency);
+ if (dma_latency_fd < 0) {
+ err_msg("Could not set /dev/cpu_dma_latency.\n");
+ return -1;
+ }
+ }
+
+ if (params->deepest_idle_state >= -1) {
+ if (!have_libcpupower_support()) {
+ err_msg("rtla built without libcpupower, --deepest-idle-state is not supported\n");
+ return -1;
+ }
+
+ for_each_monitored_cpu(i, &params->common) {
+ if (save_cpu_idle_disable_state(i) < 0) {
+ err_msg("Could not save cpu idle state.\n");
+ return -1;
+ }
+ if (set_deepest_cpu_idle_state(i, params->deepest_idle_state) < 0) {
+ err_msg("Could not set deepest cpu idle state.\n");
+ return -1;
+ }
+ }
+ }
+
+ if (!params->no_aa) {
+ tool->aa = osnoise_init_tool("timerlat_aa");
+ if (!tool->aa)
+ return -1;
+
+ retval = timerlat_aa_init(tool->aa, params->dump_tasks, params->stack_format);
+ if (retval) {
+ err_msg("Failed to enable the auto analysis instance\n");
+ return retval;
+ }
+
+ retval = enable_tracer_by_name(tool->aa->trace.inst, "timerlat");
+ if (retval) {
+ err_msg("Failed to enable aa tracer\n");
+ return retval;
+ }
+ }
+
+ if (params->common.warmup > 0) {
+ debug_msg("Warming up for %d seconds\n", params->common.warmup);
+ sleep(params->common.warmup);
+ if (stop_tracing)
+ return -1;
+ }
+
+ /*
+ * Start the tracers here, after having set all instances.
+ *
+ * Let the trace instance start first for the case of hitting a stop
+ * tracing while enabling other instances. The trace instance is the
+ * one with most valuable information.
+ */
+ if (tool->record)
+ trace_instance_start(&tool->record->trace);
+ if (!params->no_aa)
+ trace_instance_start(&tool->aa->trace);
+ if (params->mode == TRACING_MODE_TRACEFS) {
+ trace_instance_start(&tool->trace);
+ } else {
+ retval = timerlat_bpf_attach();
+ if (retval) {
+ err_msg("Error attaching BPF program\n");
+ return retval;
+ }
+ }
+
+ /*
+ * In tracefs and mixed mode, timerlat tracer handles stopping
+ * on threshold
+ */
+ if (params->mode != TRACING_MODE_BPF) {
+ retval = osn_set_stop(tool);
+ if (retval)
+ return retval;
+ }
+
+ return 0;
+}
+
+void timerlat_analyze(struct osnoise_tool *tool, bool stopped)
+{
+ struct timerlat_params *params = to_timerlat_params(tool->params);
+
+ if (stopped) {
+ if (!params->no_aa)
+ timerlat_auto_analysis(params->common.stop_us,
+ params->common.stop_total_us);
+ } else if (params->common.aa_only) {
+ char *max_lat;
+
+ /*
+ * If the trace did not stop with --aa-only, at least print
+ * the max known latency.
+ */
+ max_lat = tracefs_instance_file_read(trace_inst->inst, "tracing_max_latency", NULL);
+ if (max_lat) {
+ printf(" Max latency was %s\n", max_lat);
+ free(max_lat);
+ }
+ }
+}
+
+void timerlat_free(struct osnoise_tool *tool)
+{
+ struct timerlat_params *params = to_timerlat_params(tool->params);
+ int i;
+
+ timerlat_aa_destroy();
+ if (dma_latency_fd >= 0)
+ close(dma_latency_fd);
+ if (params->deepest_idle_state >= -1) {
+ for_each_monitored_cpu(i, &params->common) {
+ restore_cpu_idle_disable_state(i);
+ }
+ }
+
+ osnoise_destroy_tool(tool->aa);
+
+ if (params->mode != TRACING_MODE_TRACEFS)
+ timerlat_bpf_destroy();
+ free_cpu_idle_disable_states();
+}
static void timerlat_usage(int err)
{
@@ -47,21 +264,21 @@ int timerlat_main(int argc, char *argv[])
* default cmdline.
*/
if (argc == 1) {
- timerlat_top_main(argc, argv);
+ run_tool(&timerlat_top_ops, argc, argv);
exit(0);
}
if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
timerlat_usage(0);
- } else if (strncmp(argv[1], "-", 1) == 0) {
+ } else if (str_has_prefix(argv[1], "-")) {
/* the user skipped the tool, call the default one */
- timerlat_top_main(argc, argv);
+ run_tool(&timerlat_top_ops, argc, argv);
exit(0);
} else if (strcmp(argv[1], "top") == 0) {
- timerlat_top_main(argc-1, &argv[1]);
+ run_tool(&timerlat_top_ops, argc-1, &argv[1]);
exit(0);
} else if (strcmp(argv[1], "hist") == 0) {
- timerlat_hist_main(argc-1, &argv[1]);
+ run_tool(&timerlat_hist_ops, argc-1, &argv[1]);
exit(0);
}
diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h
index 88561bfd14f3..364203a29abd 100644
--- a/tools/tracing/rtla/src/timerlat.h
+++ b/tools/tracing/rtla/src/timerlat.h
@@ -1,4 +1,40 @@
// SPDX-License-Identifier: GPL-2.0
-int timerlat_hist_main(int argc, char *argv[]);
-int timerlat_top_main(int argc, char *argv[]);
+#include "osnoise.h"
+
+/*
+ * Define timerlat tracing mode.
+ *
+ * There are three tracing modes:
+ * - tracefs-only, used when BPF is unavailable.
+ * - BPF-only, used when BPF is available and neither trace saving nor
+ * auto-analysis are enabled.
+ * - mixed mode, used when BPF is available and either trace saving or
+ * auto-analysis is enabled (which rely on sample collection through
+ * tracefs).
+ */
+enum timerlat_tracing_mode {
+ TRACING_MODE_BPF,
+ TRACING_MODE_TRACEFS,
+ TRACING_MODE_MIXED,
+};
+
+struct timerlat_params {
+ struct common_params common;
+ long long timerlat_period_us;
+ long long print_stack;
+ int dma_latency;
+ int no_aa;
+ int dump_tasks;
+ 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)
+
+int timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params);
int timerlat_main(int argc, char *argv[]);
+int timerlat_enable(struct osnoise_tool *tool);
+void timerlat_analyze(struct osnoise_tool *tool, bool stopped);
+void timerlat_free(struct osnoise_tool *tool);
diff --git a/tools/tracing/rtla/src/timerlat_aa.c b/tools/tracing/rtla/src/timerlat_aa.c
index 7bd80ee2a5b4..a121ff9d2c57 100644
--- a/tools/tracing/rtla/src/timerlat_aa.c
+++ b/tools/tracing/rtla/src/timerlat_aa.c
@@ -5,8 +5,6 @@
#include <stdlib.h>
#include <errno.h>
-#include "utils.h"
-#include "osnoise.h"
#include "timerlat.h"
#include <unistd.h>
@@ -104,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;
@@ -419,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;
}
@@ -483,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;
}
@@ -740,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) {
@@ -768,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;
@@ -794,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) {
@@ -844,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);
@@ -1022,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;
@@ -1034,9 +1051,9 @@ int timerlat_aa_init(struct osnoise_tool *tool, int dump_tasks)
__timerlat_aa_ctx = taa_ctx;
- taa_ctx->nr_cpus = nr_cpus;
taa_ctx->tool = tool;
taa_ctx->dump_tasks = dump_tasks;
+ taa_ctx->stack_format = stack_format;
taa_ctx->taa_data = calloc(nr_cpus, sizeof(*taa_ctx->taa_data));
if (!taa_ctx->taa_data)
diff --git a/tools/tracing/rtla/src/timerlat_aa.h b/tools/tracing/rtla/src/timerlat_aa.h
index cea4bb1531a8..a11b5f30cdce 100644
--- a/tools/tracing/rtla/src/timerlat_aa.h
+++ b/tools/tracing/rtla/src/timerlat_aa.h
@@ -3,7 +3,7 @@
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <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);
diff --git a/tools/tracing/rtla/src/timerlat_bpf.c b/tools/tracing/rtla/src/timerlat_bpf.c
new file mode 100644
index 000000000000..dd3cf71d74e5
--- /dev/null
+++ b/tools/tracing/rtla/src/timerlat_bpf.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifdef HAVE_BPF_SKEL
+#define _GNU_SOURCE
+#include "timerlat.h"
+#include "timerlat_bpf.h"
+#include "timerlat.skel.h"
+
+static struct timerlat_bpf *bpf;
+
+/* BPF object and program for action program */
+static struct bpf_object *obj;
+static struct bpf_program *prog;
+
+/*
+ * timerlat_bpf_init - load and initialize BPF program to collect timerlat data
+ */
+int timerlat_bpf_init(struct timerlat_params *params)
+{
+ int err;
+
+ debug_msg("Loading BPF program\n");
+
+ bpf = timerlat_bpf__open();
+ if (!bpf)
+ return 1;
+
+ /* Pass common options */
+ bpf->rodata->output_divisor = params->common.output_divisor;
+ bpf->rodata->entries = params->common.hist.entries;
+ bpf->rodata->irq_threshold = params->common.stop_us;
+ bpf->rodata->thread_threshold = params->common.stop_total_us;
+ bpf->rodata->aa_only = params->common.aa_only;
+
+ if (params->common.hist.entries != 0) {
+ /* Pass histogram options */
+ bpf->rodata->bucket_size = params->common.hist.bucket_size;
+
+ /* Set histogram array sizes */
+ bpf_map__set_max_entries(bpf->maps.hist_irq, params->common.hist.entries);
+ bpf_map__set_max_entries(bpf->maps.hist_thread, params->common.hist.entries);
+ bpf_map__set_max_entries(bpf->maps.hist_user, params->common.hist.entries);
+ } else {
+ /* No entries, disable histogram */
+ bpf_map__set_autocreate(bpf->maps.hist_irq, false);
+ bpf_map__set_autocreate(bpf->maps.hist_thread, false);
+ bpf_map__set_autocreate(bpf->maps.hist_user, false);
+ }
+
+ if (params->common.aa_only) {
+ /* Auto-analysis only, disable summary */
+ bpf_map__set_autocreate(bpf->maps.summary_irq, false);
+ bpf_map__set_autocreate(bpf->maps.summary_thread, false);
+ bpf_map__set_autocreate(bpf->maps.summary_user, false);
+ }
+
+ /* Load and verify BPF program */
+ err = timerlat_bpf__load(bpf);
+ if (err) {
+ timerlat_bpf__destroy(bpf);
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * timerlat_bpf_set_action - set action on threshold executed on BPF side
+ */
+static int timerlat_bpf_set_action(struct bpf_program *prog)
+{
+ unsigned int key = 0, value = bpf_program__fd(prog);
+
+ return bpf_map__update_elem(bpf->maps.bpf_action,
+ &key, sizeof(key),
+ &value, sizeof(value),
+ BPF_ANY);
+}
+
+/*
+ * timerlat_bpf_attach - attach BPF program to collect timerlat data
+ */
+int timerlat_bpf_attach(void)
+{
+ debug_msg("Attaching BPF program\n");
+
+ return timerlat_bpf__attach(bpf);
+}
+
+/*
+ * timerlat_bpf_detach - detach BPF program to collect timerlat data
+ */
+void timerlat_bpf_detach(void)
+{
+ timerlat_bpf__detach(bpf);
+}
+
+/*
+ * timerlat_bpf_detach - destroy BPF program to collect timerlat data
+ */
+void timerlat_bpf_destroy(void)
+{
+ timerlat_bpf__destroy(bpf);
+ bpf = NULL;
+ if (obj)
+ bpf_object__close(obj);
+ obj = NULL;
+ prog = NULL;
+}
+
+static int handle_rb_event(void *ctx, void *data, size_t data_sz)
+{
+ return 0;
+}
+
+/*
+ * timerlat_bpf_wait - wait until tracing is stopped or signal
+ */
+int timerlat_bpf_wait(int timeout)
+{
+ struct ring_buffer *rb;
+ int retval;
+
+ rb = ring_buffer__new(bpf_map__fd(bpf->maps.signal_stop_tracing),
+ handle_rb_event, NULL, NULL);
+ retval = ring_buffer__poll(rb, timeout * 1000);
+ ring_buffer__free(rb);
+
+ return retval;
+}
+
+/*
+ * timerlat_bpf_restart_tracing - restart stopped tracing
+ */
+int timerlat_bpf_restart_tracing(void)
+{
+ unsigned int key = 0;
+ unsigned long long value = 0;
+
+ return bpf_map__update_elem(bpf->maps.stop_tracing,
+ &key, sizeof(key),
+ &value, sizeof(value), BPF_ANY);
+}
+
+static int get_value(struct bpf_map *map_irq,
+ struct bpf_map *map_thread,
+ struct bpf_map *map_user,
+ int key,
+ long long *value_irq,
+ long long *value_thread,
+ long long *value_user)
+{
+ int err;
+
+ err = bpf_map__lookup_elem(map_irq, &key,
+ sizeof(unsigned int), value_irq,
+ 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) * nr_cpus, 0);
+ if (err)
+ return err;
+ err = bpf_map__lookup_elem(map_user, &key,
+ sizeof(unsigned int), value_user,
+ sizeof(long long) * nr_cpus, 0);
+ if (err)
+ return err;
+ return 0;
+}
+
+/*
+ * timerlat_bpf_get_hist_value - get value from BPF hist map
+ */
+int timerlat_bpf_get_hist_value(int key,
+ long long *value_irq,
+ long long *value_thread,
+ 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);
+}
+
+/*
+ * timerlat_bpf_get_summary_value - get value from BPF summary map
+ */
+int timerlat_bpf_get_summary_value(enum summary_field key,
+ long long *value_irq,
+ long long *value_thread,
+ 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);
+}
+
+/*
+ * timerlat_load_bpf_action_program - load and register a BPF action program
+ */
+int timerlat_load_bpf_action_program(const char *program_path)
+{
+ int err;
+
+ obj = bpf_object__open_file(program_path, NULL);
+ if (!obj) {
+ err_msg("Failed to open BPF action program: %s\n", program_path);
+ goto out_err;
+ }
+
+ err = bpf_object__load(obj);
+ if (err) {
+ err_msg("Failed to load BPF action program: %s\n", program_path);
+ goto out_obj_err;
+ }
+
+ prog = bpf_object__find_program_by_name(obj, "action_handler");
+ if (!prog) {
+ err_msg("BPF action program must have 'action_handler' function: %s\n",
+ program_path);
+ goto out_obj_err;
+ }
+
+ err = timerlat_bpf_set_action(prog);
+ if (err) {
+ err_msg("Failed to register BPF action program: %s\n", program_path);
+ goto out_prog_err;
+ }
+
+ return 0;
+
+out_prog_err:
+ prog = NULL;
+out_obj_err:
+ bpf_object__close(obj);
+ obj = NULL;
+out_err:
+ return 1;
+}
+
+#endif /* HAVE_BPF_SKEL */
diff --git a/tools/tracing/rtla/src/timerlat_bpf.h b/tools/tracing/rtla/src/timerlat_bpf.h
new file mode 100644
index 000000000000..33f0df6a9060
--- /dev/null
+++ b/tools/tracing/rtla/src/timerlat_bpf.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+enum summary_field {
+ SUMMARY_CURRENT,
+ SUMMARY_MIN,
+ SUMMARY_MAX,
+ SUMMARY_COUNT,
+ SUMMARY_SUM,
+ SUMMARY_OVERFLOW,
+ SUMMARY_FIELD_N
+};
+
+#ifndef __bpf__
+#ifdef HAVE_BPF_SKEL
+int timerlat_bpf_init(struct timerlat_params *params);
+int timerlat_bpf_attach(void);
+void timerlat_bpf_detach(void);
+void timerlat_bpf_destroy(void);
+int timerlat_bpf_wait(int timeout);
+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 timerlat_bpf_get_summary_value(enum summary_field key,
+ long long *value_irq,
+ long long *value_thread,
+ long long *value_user);
+int timerlat_load_bpf_action_program(const char *program_path);
+static inline int have_libbpf_support(void) { return 1; }
+#else
+static inline int timerlat_bpf_init(struct timerlat_params *params)
+{
+ return -1;
+}
+static inline int timerlat_bpf_attach(void) { return -1; }
+static inline void timerlat_bpf_detach(void) { };
+static inline void timerlat_bpf_destroy(void) { };
+static inline int timerlat_bpf_wait(int timeout) { return -1; }
+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)
+{
+ 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)
+{
+ return -1;
+}
+static inline int timerlat_load_bpf_action_program(const char *program_path)
+{
+ return -1;
+}
+static inline int have_libbpf_support(void) { return 0; }
+#endif /* HAVE_BPF_SKEL */
+#endif /* __bpf__ */
diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c
index 6d7d0a2d45b4..79142af4f566 100644
--- a/tools/tracing/rtla/src/timerlat_hist.c
+++ b/tools/tracing/rtla/src/timerlat_hist.c
@@ -14,49 +14,10 @@
#include <sched.h>
#include <pthread.h>
-#include "utils.h"
-#include "osnoise.h"
#include "timerlat.h"
#include "timerlat_aa.h"
-#include "timerlat_u.h"
-
-struct timerlat_hist_params {
- char *cpus;
- cpu_set_t monitored_cpus;
- char *trace_output;
- char *cgroup_name;
- unsigned long long runtime;
- long long stop_us;
- long long stop_total_us;
- long long timerlat_period_us;
- long long print_stack;
- int sleep_time;
- int output_divisor;
- int duration;
- int set_sched;
- int dma_latency;
- int cgroup;
- int hk_cpus;
- int no_aa;
- int dump_tasks;
- int user_workload;
- int kernel_workload;
- int user_hist;
- cpu_set_t hk_cpu_set;
- struct sched_attr sched_param;
- struct trace_events *events;
- char no_irq;
- char no_thread;
- char no_header;
- char no_summary;
- char no_index;
- char with_zeros;
- int bucket_size;
- int entries;
- int warmup;
- int buffer_size;
- int deepest_idle_state;
-};
+#include "timerlat_bpf.h"
+#include "common.h"
struct timerlat_hist_cpu {
int *irq;
@@ -84,7 +45,6 @@ struct timerlat_hist_data {
struct timerlat_hist_cpu *hist;
int entries;
int bucket_size;
- int nr_cpus;
};
/*
@@ -96,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);
@@ -111,15 +71,19 @@ timerlat_free_histogram(struct timerlat_hist_data *data)
/* one set of histograms per CPU */
if (data->hist)
free(data->hist);
+}
- free(data);
+static void timerlat_free_histogram_tool(struct osnoise_tool *tool)
+{
+ timerlat_free_histogram(tool->data);
+ timerlat_free(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;
@@ -130,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);
@@ -174,14 +137,14 @@ timerlat_hist_update(struct osnoise_tool *tool, int cpu,
unsigned long long context,
unsigned long long latency)
{
- struct timerlat_hist_params *params = tool->params;
+ struct timerlat_params *params = to_timerlat_params(tool->params);
struct timerlat_hist_data *data = tool->data;
int entries = data->entries;
int bucket;
int *hist;
- if (params->output_divisor)
- latency = latency / params->output_divisor;
+ if (params->common.output_divisor)
+ latency = latency / params->common.output_divisor;
bucket = latency / data->bucket_size;
@@ -234,44 +197,120 @@ timerlat_hist_handler(struct trace_seq *s, struct tep_record *record,
}
/*
+ * timerlat_hist_bpf_pull_data - copy data from BPF maps into userspace
+ */
+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[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);
+ if (err)
+ return err;
+ 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];
+ }
+ }
+
+ /* Pull summary */
+ err = timerlat_bpf_get_summary_value(SUMMARY_COUNT,
+ value_irq, value_thread, value_user);
+ if (err)
+ return err;
+ 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);
+ if (err)
+ return err;
+ 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);
+ if (err)
+ return err;
+ 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);
+ if (err)
+ return err;
+ 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);
+ if (err)
+ return err;
+ 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];
+ }
+
+ return 0;
+}
+
+/*
* timerlat_hist_header - print the header of the tracer to the output
*/
static void timerlat_hist_header(struct osnoise_tool *tool)
{
- struct timerlat_hist_params *params = tool->params;
+ struct timerlat_params *params = to_timerlat_params(tool->params);
struct timerlat_hist_data *data = tool->data;
struct trace_seq *s = tool->trace.seq;
char duration[26];
int cpu;
- if (params->no_header)
+ if (params->common.hist.no_header)
return;
get_duration(tool->start_time, duration, sizeof(duration));
trace_seq_printf(s, "# RTLA timerlat histogram\n");
trace_seq_printf(s, "# Time unit is %s (%s)\n",
- params->output_divisor == 1 ? "nanoseconds" : "microseconds",
- params->output_divisor == 1 ? "ns" : "us");
+ params->common.output_divisor == 1 ? "nanoseconds" : "microseconds",
+ params->common.output_divisor == 1 ? "ns" : "us");
trace_seq_printf(s, "# Duration: %s\n", duration);
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(s, "Index");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
trace_seq_printf(s, " IRQ-%03d", cpu);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
trace_seq_printf(s, " Thr-%03d", cpu);
- if (params->user_hist)
+ if (params->common.user_data)
trace_seq_printf(s, " Usr-%03d", cpu);
}
trace_seq_printf(s, "\n");
@@ -300,62 +339,58 @@ static void format_summary_value(struct trace_seq *seq,
* timerlat_print_summary - print the summary of the hist data to the output
*/
static void
-timerlat_print_summary(struct timerlat_hist_params *params,
+timerlat_print_summary(struct timerlat_params *params,
struct trace_instance *trace,
struct timerlat_hist_data *data)
{
int cpu;
- if (params->no_summary)
+ if (params->common.hist.no_summary)
return;
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "count:");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
trace_seq_printf(trace->seq, "%9llu ",
data->hist[cpu].irq_count);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
trace_seq_printf(trace->seq, "%9llu ",
data->hist[cpu].thread_count);
- if (params->user_hist)
+ if (params->common.user_data)
trace_seq_printf(trace->seq, "%9llu ",
data->hist[cpu].user_count);
}
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "min: ");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
data->hist[cpu].irq_count,
data->hist[cpu].min_irq,
false);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
data->hist[cpu].thread_count,
data->hist[cpu].min_thread,
false);
- if (params->user_hist)
+ if (params->common.user_data)
format_summary_value(trace->seq,
data->hist[cpu].user_count,
data->hist[cpu].min_user,
@@ -363,29 +398,27 @@ timerlat_print_summary(struct timerlat_hist_params *params,
}
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "avg: ");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
data->hist[cpu].irq_count,
data->hist[cpu].sum_irq,
true);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
data->hist[cpu].thread_count,
data->hist[cpu].sum_thread,
true);
- if (params->user_hist)
+ if (params->common.user_data)
format_summary_value(trace->seq,
data->hist[cpu].user_count,
data->hist[cpu].sum_user,
@@ -393,29 +426,27 @@ timerlat_print_summary(struct timerlat_hist_params *params,
}
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "max: ");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
data->hist[cpu].irq_count,
data->hist[cpu].max_irq,
false);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
data->hist[cpu].thread_count,
data->hist[cpu].max_thread,
false);
- if (params->user_hist)
+ if (params->common.user_data)
format_summary_value(trace->seq,
data->hist[cpu].user_count,
data->hist[cpu].max_user,
@@ -427,7 +458,7 @@ timerlat_print_summary(struct timerlat_hist_params *params,
}
static void
-timerlat_print_stats_all(struct timerlat_hist_params *params,
+timerlat_print_stats_all(struct timerlat_params *params,
struct trace_instance *trace,
struct timerlat_hist_data *data)
{
@@ -435,7 +466,7 @@ timerlat_print_stats_all(struct timerlat_hist_params *params,
struct timerlat_hist_cpu sum;
int cpu;
- if (params->no_summary)
+ if (params->common.hist.no_summary)
return;
memset(&sum, 0, sizeof(sum));
@@ -443,9 +474,7 @@ timerlat_print_stats_all(struct timerlat_hist_params *params,
sum.min_thread = ~0;
sum.min_user = ~0;
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
@@ -468,53 +497,53 @@ timerlat_print_stats_all(struct timerlat_hist_params *params,
update_max(&sum.max_user, &cpu_data->max_user);
}
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "ALL: ");
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
trace_seq_printf(trace->seq, " IRQ");
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
trace_seq_printf(trace->seq, " Thr");
- if (params->user_hist)
+ if (params->common.user_data)
trace_seq_printf(trace->seq, " Usr");
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "count:");
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
trace_seq_printf(trace->seq, "%9llu ",
sum.irq_count);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
trace_seq_printf(trace->seq, "%9llu ",
sum.thread_count);
- if (params->user_hist)
+ if (params->common.user_data)
trace_seq_printf(trace->seq, "%9llu ",
sum.user_count);
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "min: ");
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
sum.irq_count,
sum.min_irq,
false);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
sum.thread_count,
sum.min_thread,
false);
- if (params->user_hist)
+ if (params->common.user_data)
format_summary_value(trace->seq,
sum.user_count,
sum.min_user,
@@ -522,22 +551,22 @@ timerlat_print_stats_all(struct timerlat_hist_params *params,
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "avg: ");
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
sum.irq_count,
sum.sum_irq,
true);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
sum.thread_count,
sum.sum_thread,
true);
- if (params->user_hist)
+ if (params->common.user_data)
format_summary_value(trace->seq,
sum.user_count,
sum.sum_user,
@@ -545,22 +574,22 @@ timerlat_print_stats_all(struct timerlat_hist_params *params,
trace_seq_printf(trace->seq, "\n");
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "max: ");
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
sum.irq_count,
sum.max_irq,
false);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
sum.thread_count,
sum.max_thread,
false);
- if (params->user_hist)
+ if (params->common.user_data)
format_summary_value(trace->seq,
sum.user_count,
sum.max_user,
@@ -575,8 +604,9 @@ timerlat_print_stats_all(struct timerlat_hist_params *params,
* timerlat_print_stats - print data for each CPUs
*/
static void
-timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *tool)
+timerlat_print_stats(struct osnoise_tool *tool)
{
+ struct timerlat_params *params = to_timerlat_params(tool->params);
struct timerlat_hist_data *data = tool->data;
struct trace_instance *trace = &tool->trace;
int bucket, cpu;
@@ -587,30 +617,28 @@ timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *t
for (bucket = 0; bucket < data->entries; bucket++) {
total = 0;
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "%-6d",
bucket * data->bucket_size);
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
- if (!params->no_irq) {
+ if (!params->common.hist.no_irq) {
total += data->hist[cpu].irq[bucket];
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].irq[bucket]);
}
- if (!params->no_thread) {
+ if (!params->common.hist.no_thread) {
total += data->hist[cpu].thread[bucket];
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].thread[bucket]);
}
- if (params->user_hist) {
+ if (params->common.user_data) {
total += data->hist[cpu].user[bucket];
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].user[bucket]);
@@ -618,7 +646,7 @@ timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *t
}
- if (total == 0 && !params->with_zeros) {
+ if (total == 0 && !params->common.hist.with_zeros) {
trace_seq_reset(trace->seq);
continue;
}
@@ -628,25 +656,23 @@ timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *t
trace_seq_reset(trace->seq);
}
- if (!params->no_index)
+ if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "over: ");
- for (cpu = 0; cpu < data->nr_cpus; cpu++) {
- if (params->cpus && !CPU_ISSET(cpu, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
- if (!params->no_irq)
+ if (!params->common.hist.no_irq)
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].irq[data->entries]);
- if (!params->no_thread)
+ if (!params->common.hist.no_thread)
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].thread[data->entries]);
- if (params->user_hist)
+ if (params->common.user_data)
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].user[data->entries]);
}
@@ -662,19 +688,18 @@ timerlat_print_stats(struct timerlat_hist_params *params, struct osnoise_tool *t
/*
* timerlat_hist_usage - prints timerlat top usage message
*/
-static void timerlat_hist_usage(char *usage)
+static void timerlat_hist_usage(void)
{
- int i;
-
- char *msg[] = {
- "",
- " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\",
- " [-t[file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
+ static const char * const msg_start[] = {
+ "[-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\",
+ " [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
" [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\",
- " [--no-index] [--with-zeros] [--dma-latency us] [-C[=cgroup_name]] [--no-aa] [--dump-task] [-u|-k]",
+ " [--no-index] [--with-zeros] [--dma-latency us] [-C [cgroup_name]] [--no-aa] [--dump-task] [-u|-k]",
" [--warm-up s] [--deepest-idle-state n]",
- "",
- " -h/--help: print this menu",
+ NULL,
+ };
+
+ static const char * const msg_opts[] = {
" -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
" -p/--period us: timerlat period in us",
" -i/--irq us: stop trace if the irq latency is higher than the argument in us",
@@ -682,11 +707,11 @@ static void timerlat_hist_usage(char *usage)
" -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
" -c/--cpus cpus: run the tracer only on the given cpus",
" -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
+ " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
" -d/--duration time[m|h|d]: duration of the session in seconds",
" --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)",
" -D/--debug: print debug info",
- " -t/--trace[file]: save the stopped trace to [file|timerlat_trace.txt]",
+ " -t/--trace [file]: save the stopped trace to [file|timerlat_trace.txt]",
" -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
" --filter <filter>: enable a trace event filter to the previous -e event",
" --trigger <trigger>: enable a trace event trigger to the previous -e event",
@@ -713,39 +738,33 @@ static void timerlat_hist_usage(char *usage)
" --warm-up s: let the workload run for s seconds before collecting data",
" --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
" --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
+ " --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
+ " --on-end <action>: define action to be executed at measurement end, multiple are allowed",
+ " --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
+ " --stack-format <format>: set the stack format (truncate, skip, full)",
NULL,
};
- if (usage)
- fprintf(stderr, "%s\n", usage);
-
- fprintf(stderr, "rtla timerlat hist: a per-cpu histogram of the timer latency (version %s)\n",
- VERSION);
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
-
- if (usage)
- exit(EXIT_FAILURE);
-
- exit(EXIT_SUCCESS);
+ common_usage("timerlat", "hist", "a per-cpu histogram of the timer latency",
+ msg_start, msg_opts);
}
/*
* timerlat_hist_parse_args - allocs, parse and fill the cmd line parameters
*/
-static struct timerlat_hist_params
+static struct common_params
*timerlat_hist_parse_args(int argc, char *argv[])
{
- struct timerlat_hist_params *params;
- struct trace_events *tevent;
+ struct timerlat_params *params;
int auto_thresh;
int retval;
int c;
+ char *trace_output = NULL;
+
+ params = calloc_fatal(1, sizeof(*params));
- params = calloc(1, sizeof(*params));
- if (!params)
- exit(1);
+ actions_init(&params->common.threshold_actions);
+ actions_init(&params->common.end_actions);
/* disabled by default */
params->dma_latency = -1;
@@ -754,32 +773,31 @@ static struct timerlat_hist_params
params->deepest_idle_state = -2;
/* display data in microseconds */
- params->output_divisor = 1000;
- params->bucket_size = 1;
- params->entries = 256;
+ params->common.output_divisor = 1000;
+ params->common.hist.bucket_size = 1;
+ params->common.hist.entries = 256;
+
+ /* 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'},
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
{"bucket-size", required_argument, 0, 'b'},
- {"debug", no_argument, 0, 'D'},
{"entries", required_argument, 0, 'E'},
- {"duration", required_argument, 0, 'd'},
- {"house-keeping", required_argument, 0, 'H'},
{"help", no_argument, 0, 'h'},
{"irq", required_argument, 0, 'i'},
{"nano", no_argument, 0, 'n'},
{"period", required_argument, 0, 'p'},
- {"priority", required_argument, 0, 'P'},
{"stack", required_argument, 0, 's'},
{"thread", required_argument, 0, 'T'},
{"trace", optional_argument, 0, 't'},
{"user-threads", no_argument, 0, 'u'},
{"kernel-threads", no_argument, 0, 'k'},
{"user-load", no_argument, 0, 'U'},
- {"event", required_argument, 0, 'e'},
{"no-irq", no_argument, 0, '0'},
{"no-thread", no_argument, 0, '1'},
{"no-header", no_argument, 0, '2'},
@@ -794,14 +812,17 @@ static struct timerlat_hist_params
{"warm-up", required_argument, 0, '\2'},
{"trace-buffer-size", required_argument, 0, '\3'},
{"deepest-idle-state", required_argument, 0, '\4'},
+ {"on-threshold", required_argument, 0, '\5'},
+ {"on-end", required_argument, 0, '\6'},
+ {"bpf-action", required_argument, 0, '\7'},
+ {"stack-format", required_argument, 0, '\10'},
{0, 0, 0, 0}
};
- /* getopt_long stores the option index here. */
- int option_index = 0;
+ if (common_parse_options(argc, argv, &params->common))
+ continue;
- c = getopt_long(argc, argv, "a:c:C::b:d:e:E:DhH:i:knp:P:s:t::T:uU0123456:7:8:9\1\2:\3:",
- long_options, &option_index);
+ c = getopt_auto(argc, argv, long_options);
/* detect the end of the options. */
if (c == -1)
@@ -812,163 +833,98 @@ static struct timerlat_hist_params
auto_thresh = get_llong_from_str(optarg);
/* set thread stop to auto_thresh */
- params->stop_total_us = auto_thresh;
- params->stop_us = auto_thresh;
+ params->common.stop_total_us = auto_thresh;
+ params->common.stop_us = auto_thresh;
/* get stack trace */
params->print_stack = auto_thresh;
/* set trace */
- params->trace_output = "timerlat_trace.txt";
+ if (!trace_output)
+ trace_output = "timerlat_trace.txt";
break;
- case 'c':
- retval = parse_cpu_set(optarg, &params->monitored_cpus);
- if (retval)
- timerlat_hist_usage("\nInvalid -c cpu list\n");
- params->cpus = optarg;
- break;
- case 'C':
- params->cgroup = 1;
- if (!optarg) {
- /* will inherit this cgroup */
- params->cgroup_name = NULL;
- } else if (*optarg == '=') {
- /* skip the = */
- params->cgroup_name = ++optarg;
- }
- break;
case 'b':
- params->bucket_size = get_llong_from_str(optarg);
- if ((params->bucket_size == 0) || (params->bucket_size >= 1000000))
- timerlat_hist_usage("Bucket size needs to be > 0 and <= 1000000\n");
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- params->duration = parse_seconds_duration(optarg);
- if (!params->duration)
- timerlat_hist_usage("Invalid -D duration\n");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent) {
- err_msg("Error alloc trace event");
- exit(EXIT_FAILURE);
- }
-
- if (params->events)
- tevent->next = params->events;
-
- params->events = tevent;
+ params->common.hist.bucket_size = get_llong_from_str(optarg);
+ if (params->common.hist.bucket_size == 0 ||
+ params->common.hist.bucket_size >= 1000000)
+ fatal("Bucket size needs to be > 0 and <= 1000000");
break;
case 'E':
- params->entries = get_llong_from_str(optarg);
- if ((params->entries < 10) || (params->entries > 9999999))
- timerlat_hist_usage("Entries must be > 10 and < 9999999\n");
+ params->common.hist.entries = get_llong_from_str(optarg);
+ if (params->common.hist.entries < 10 ||
+ params->common.hist.entries > 9999999)
+ fatal("Entries must be > 10 and < 9999999");
break;
case 'h':
case '?':
- timerlat_hist_usage(NULL);
- break;
- case 'H':
- params->hk_cpus = 1;
- retval = parse_cpu_set(optarg, &params->hk_cpu_set);
- if (retval) {
- err_msg("Error parsing house keeping CPUs\n");
- exit(EXIT_FAILURE);
- }
+ timerlat_hist_usage();
break;
case 'i':
- params->stop_us = get_llong_from_str(optarg);
+ params->common.stop_us = get_llong_from_str(optarg);
break;
case 'k':
- params->kernel_workload = 1;
+ params->common.kernel_workload = 1;
break;
case 'n':
- params->output_divisor = 1;
+ params->common.output_divisor = 1;
break;
case 'p':
params->timerlat_period_us = get_llong_from_str(optarg);
if (params->timerlat_period_us > 1000000)
- timerlat_hist_usage("Period longer than 1 s\n");
- break;
- case 'P':
- retval = parse_prio(optarg, &params->sched_param);
- if (retval == -1)
- timerlat_hist_usage("Invalid -P priority");
- params->set_sched = 1;
+ fatal("Period longer than 1 s");
break;
case 's':
params->print_stack = get_llong_from_str(optarg);
break;
case 'T':
- params->stop_total_us = get_llong_from_str(optarg);
+ params->common.stop_total_us = get_llong_from_str(optarg);
break;
case 't':
- if (optarg) {
- if (optarg[0] == '=')
- params->trace_output = &optarg[1];
- else
- params->trace_output = &optarg[0];
- } else if (optind < argc && argv[optind][0] != '-')
- params->trace_output = argv[optind];
- else
- params->trace_output = "timerlat_trace.txt";
+ trace_output = parse_optional_arg(argc, argv);
+ if (!trace_output)
+ trace_output = "timerlat_trace.txt";
break;
case 'u':
- params->user_workload = 1;
+ params->common.user_workload = 1;
/* fallback: -u implies in -U */
case 'U':
- params->user_hist = 1;
+ params->common.user_data = 1;
break;
case '0': /* no irq */
- params->no_irq = 1;
+ params->common.hist.no_irq = 1;
break;
case '1': /* no thread */
- params->no_thread = 1;
+ params->common.hist.no_thread = 1;
break;
case '2': /* no header */
- params->no_header = 1;
+ params->common.hist.no_header = 1;
break;
case '3': /* no summary */
- params->no_summary = 1;
+ params->common.hist.no_summary = 1;
break;
case '4': /* no index */
- params->no_index = 1;
+ params->common.hist.no_index = 1;
break;
case '5': /* with zeros */
- params->with_zeros = 1;
+ params->common.hist.with_zeros = 1;
break;
case '6': /* trigger */
- if (params->events) {
- retval = trace_event_add_trigger(params->events, optarg);
- if (retval) {
- err_msg("Error adding trigger %s\n", optarg);
- exit(EXIT_FAILURE);
- }
- } else {
- timerlat_hist_usage("--trigger requires a previous -e\n");
- }
+ 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->events) {
- retval = trace_event_add_filter(params->events, optarg);
- if (retval) {
- err_msg("Error adding filter %s\n", optarg);
- exit(EXIT_FAILURE);
- }
- } else {
- timerlat_hist_usage("--filter requires a previous -e\n");
- }
+ 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);
- if (params->dma_latency < 0 || params->dma_latency > 10000) {
- err_msg("--dma-latency needs to be >= 0 and < 10000");
- exit(EXIT_FAILURE);
- }
+ if (params->dma_latency < 0 || params->dma_latency > 10000)
+ fatal("--dma-latency needs to be >= 0 and < 10000");
break;
case '9':
params->no_aa = 1;
@@ -977,140 +933,85 @@ static struct timerlat_hist_params
params->dump_tasks = 1;
break;
case '\2':
- params->warmup = get_llong_from_str(optarg);
+ params->common.warmup = get_llong_from_str(optarg);
break;
case '\3':
- params->buffer_size = get_llong_from_str(optarg);
+ params->common.buffer_size = get_llong_from_str(optarg);
break;
case '\4':
params->deepest_idle_state = get_llong_from_str(optarg);
break;
+ case '\5':
+ retval = actions_parse(&params->common.threshold_actions, optarg,
+ "timerlat_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", optarg);
+ break;
+ case '\6':
+ retval = actions_parse(&params->common.end_actions, optarg,
+ "timerlat_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", optarg);
+ break;
+ 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:
- timerlat_hist_usage("Invalid option");
+ fatal("Invalid option");
}
}
- if (geteuid()) {
- err_msg("rtla needs root permission\n");
- exit(EXIT_FAILURE);
- }
+ if (trace_output)
+ actions_add_trace_output(&params->common.threshold_actions, trace_output);
+
+ if (geteuid())
+ fatal("rtla needs root permission");
- if (params->no_irq && params->no_thread)
- timerlat_hist_usage("no-irq and no-thread set, there is nothing to do here");
+ if (params->common.hist.no_irq && params->common.hist.no_thread)
+ fatal("no-irq and no-thread set, there is nothing to do here");
- if (params->no_index && !params->with_zeros)
- timerlat_hist_usage("no-index set with with-zeros is not set - it does not make sense");
+ if (params->common.hist.no_index && !params->common.hist.with_zeros)
+ fatal("no-index set with with-zeros is not set - it does not make sense");
/*
* Auto analysis only happens if stop tracing, thus:
*/
- if (!params->stop_us && !params->stop_total_us)
+ if (!params->common.stop_us && !params->common.stop_total_us)
params->no_aa = 1;
- if (params->kernel_workload && params->user_workload)
- timerlat_hist_usage("--kernel-threads and --user-threads are mutually exclusive!");
+ if (params->common.kernel_workload && params->common.user_workload)
+ fatal("--kernel-threads and --user-threads are mutually exclusive!");
- return params;
+ /*
+ * If auto-analysis or trace output is enabled, switch from BPF mode to
+ * mixed mode
+ */
+ if (params->mode == TRACING_MODE_BPF &&
+ (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
+ params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
+ !params->no_aa))
+ params->mode = TRACING_MODE_MIXED;
+
+ return &params->common;
}
/*
* timerlat_hist_apply_config - apply the hist configs to the initialized tool
*/
static int
-timerlat_hist_apply_config(struct osnoise_tool *tool, struct timerlat_hist_params *params)
+timerlat_hist_apply_config(struct osnoise_tool *tool)
{
- int retval, i;
-
- if (!params->sleep_time)
- params->sleep_time = 1;
-
- if (params->cpus) {
- retval = osnoise_set_cpus(tool->context, params->cpus);
- if (retval) {
- err_msg("Failed to apply CPUs config\n");
- goto out_err;
- }
- } else {
- for (i = 0; i < sysconf(_SC_NPROCESSORS_CONF); i++)
- CPU_SET(i, &params->monitored_cpus);
- }
-
- if (params->stop_us) {
- retval = osnoise_set_stop_us(tool->context, params->stop_us);
- if (retval) {
- err_msg("Failed to set stop us\n");
- goto out_err;
- }
- }
-
- if (params->stop_total_us) {
- retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
- if (retval) {
- err_msg("Failed to set stop total us\n");
- goto out_err;
- }
- }
-
- if (params->timerlat_period_us) {
- retval = osnoise_set_timerlat_period_us(tool->context, params->timerlat_period_us);
- if (retval) {
- err_msg("Failed to set timerlat period\n");
- goto out_err;
- }
- }
-
- if (params->print_stack) {
- retval = osnoise_set_print_stack(tool->context, params->print_stack);
- if (retval) {
- err_msg("Failed to set print stack\n");
- goto out_err;
- }
- }
-
- if (params->hk_cpus) {
- retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set),
- &params->hk_cpu_set);
- if (retval == -1) {
- err_msg("Failed to set rtla to the house keeping CPUs\n");
- goto out_err;
- }
- } else if (params->cpus) {
- /*
- * Even if the user do not set a house-keeping CPU, try to
- * move rtla to a CPU set different to the one where the user
- * set the workload to run.
- *
- * No need to check results as this is an automatic attempt.
- */
- auto_house_keeping(&params->monitored_cpus);
- }
-
- /*
- * If the user did not specify a type of thread, try user-threads first.
- * Fall back to kernel threads otherwise.
- */
- if (!params->kernel_workload && !params->user_hist) {
- retval = tracefs_file_exists(NULL, "osnoise/per_cpu/cpu0/timerlat_fd");
- if (retval) {
- debug_msg("User-space interface detected, setting user-threads\n");
- params->user_workload = 1;
- params->user_hist = 1;
- } else {
- debug_msg("User-space interface not detected, setting kernel-threads\n");
- params->kernel_workload = 1;
- }
- }
+ struct timerlat_params *params = to_timerlat_params(tool->params);
+ int retval;
- /*
- * Set workload according to type of thread if the kernel supports it.
- * On kernels without support, user threads will have already failed
- * on missing timerlat_fd, and kernel threads do not need it.
- */
- retval = osnoise_set_workload(tool->context, params->kernel_workload);
- if (retval < -1) {
- err_msg("Failed to set OSNOISE_WORKLOAD option\n");
+ retval = timerlat_apply_config(tool, params);
+ if (retval)
goto out_err;
- }
return 0;
@@ -1122,23 +1023,19 @@ out_err:
* timerlat_init_hist - initialize a timerlat hist tool with parameters
*/
static struct osnoise_tool
-*timerlat_init_hist(struct timerlat_hist_params *params)
+*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->entries, params->bucket_size);
+ tool->data = timerlat_alloc_histogram(params->hist.entries,
+ params->hist.bucket_size);
if (!tool->data)
goto out_err;
- tool->params = params;
-
tep_register_event_handler(tool->trace.tep, -1, "ftrace", "timerlat",
timerlat_hist_handler, tool);
@@ -1149,273 +1046,59 @@ out_err:
return NULL;
}
-static int stop_tracing;
-static struct trace_instance *hist_inst = NULL;
-static void stop_hist(int sig)
-{
- if (stop_tracing) {
- /*
- * Stop requested twice in a row; abort event processing and
- * exit immediately
- */
- tracefs_iterate_stop(hist_inst->inst);
- return;
- }
- stop_tracing = 1;
- if (hist_inst)
- trace_instance_stop(hist_inst);
-}
-
-/*
- * timerlat_hist_set_signals - handles the signal to stop the tool
- */
-static void
-timerlat_hist_set_signals(struct timerlat_hist_params *params)
-{
- signal(SIGINT, stop_hist);
- if (params->duration) {
- signal(SIGALRM, stop_hist);
- alarm(params->duration);
- }
-}
-
-int timerlat_hist_main(int argc, char *argv[])
+static int timerlat_hist_bpf_main_loop(struct osnoise_tool *tool)
{
- struct timerlat_hist_params *params;
- struct osnoise_tool *record = NULL;
- struct timerlat_u_params params_u;
- struct osnoise_tool *tool = NULL;
- struct osnoise_tool *aa = NULL;
- struct trace_instance *trace;
- int dma_latency_fd = -1;
- int return_value = 1;
- pthread_t timerlat_u;
int retval;
- int nr_cpus, i;
-
- params = timerlat_hist_parse_args(argc, argv);
- if (!params)
- exit(1);
-
- tool = timerlat_init_hist(params);
- if (!tool) {
- err_msg("Could not init osnoise hist\n");
- goto out_exit;
- }
-
- retval = timerlat_hist_apply_config(tool, params);
- if (retval) {
- err_msg("Could not apply config\n");
- goto out_free;
- }
-
- trace = &tool->trace;
- /*
- * Save trace instance into global variable so that SIGINT can stop
- * the timerlat tracer.
- * Otherwise, rtla could loop indefinitely when overloaded.
- */
- hist_inst = trace;
-
- retval = enable_timerlat(trace);
- if (retval) {
- err_msg("Failed to enable timerlat tracer\n");
- goto out_free;
- }
-
- if (params->set_sched) {
- retval = set_comm_sched_attr("timerlat/", &params->sched_param);
- if (retval) {
- err_msg("Failed to set sched parameters\n");
- goto out_free;
- }
- }
-
- if (params->cgroup && !params->user_workload) {
- retval = set_comm_cgroup("timerlat/", params->cgroup_name);
- if (!retval) {
- err_msg("Failed to move threads to cgroup\n");
- goto out_free;
- }
- }
-
- if (params->dma_latency >= 0) {
- dma_latency_fd = set_cpu_dma_latency(params->dma_latency);
- if (dma_latency_fd < 0) {
- err_msg("Could not set /dev/cpu_dma_latency.\n");
- goto out_free;
- }
- }
-
- if (params->deepest_idle_state >= -1) {
- if (!have_libcpupower_support()) {
- err_msg("rtla built without libcpupower, --deepest-idle-state is not supported\n");
- goto out_free;
- }
-
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
-
- for (i = 0; i < nr_cpus; i++) {
- if (params->cpus && !CPU_ISSET(i, &params->monitored_cpus))
- continue;
- if (save_cpu_idle_disable_state(i) < 0) {
- err_msg("Could not save cpu idle state.\n");
- goto out_free;
- }
- if (set_deepest_cpu_idle_state(i, params->deepest_idle_state) < 0) {
- err_msg("Could not set deepest cpu idle state.\n");
- goto out_free;
- }
- }
- }
-
- if (params->trace_output) {
- record = osnoise_init_trace_tool("timerlat");
- if (!record) {
- err_msg("Failed to enable the trace instance\n");
- goto out_free;
- }
-
- if (params->events) {
- retval = trace_events_enable(&record->trace, params->events);
- if (retval)
- goto out_hist;
- }
-
- if (params->buffer_size > 0) {
- retval = trace_set_buffer_size(&record->trace, params->buffer_size);
- if (retval)
- goto out_hist;
- }
- }
-
- if (!params->no_aa) {
- aa = osnoise_init_tool("timerlat_aa");
- if (!aa)
- goto out_hist;
-
- retval = timerlat_aa_init(aa, params->dump_tasks);
- if (retval) {
- err_msg("Failed to enable the auto analysis instance\n");
- goto out_hist;
- }
-
- retval = enable_timerlat(&aa->trace);
- if (retval) {
- err_msg("Failed to enable timerlat tracer\n");
- goto out_hist;
- }
- }
-
- if (params->user_workload) {
- /* rtla asked to stop */
- params_u.should_run = 1;
- /* all threads left */
- params_u.stopped_running = 0;
-
- params_u.set = &params->monitored_cpus;
- if (params->set_sched)
- params_u.sched_param = &params->sched_param;
- else
- params_u.sched_param = NULL;
-
- params_u.cgroup_name = params->cgroup_name;
-
- retval = pthread_create(&timerlat_u, NULL, timerlat_u_dispatcher, &params_u);
- if (retval)
- err_msg("Error creating timerlat user-space threads\n");
- }
-
- if (params->warmup > 0) {
- debug_msg("Warming up for %d seconds\n", params->warmup);
- sleep(params->warmup);
- if (stop_tracing)
- goto out_hist;
- }
-
- /*
- * Start the tracers here, after having set all instances.
- *
- * Let the trace instance start first for the case of hitting a stop
- * tracing while enabling other instances. The trace instance is the
- * one with most valuable information.
- */
- if (params->trace_output)
- trace_instance_start(&record->trace);
- if (!params->no_aa)
- trace_instance_start(&aa->trace);
- trace_instance_start(trace);
-
- tool->start_time = time(NULL);
- timerlat_hist_set_signals(params);
while (!stop_tracing) {
- sleep(params->sleep_time);
-
- retval = tracefs_iterate_raw_events(trace->tep,
- trace->inst,
- NULL,
- 0,
- collect_registered_events,
- trace);
- if (retval < 0) {
- err_msg("Error iterating on events\n");
- goto out_hist;
- }
+ timerlat_bpf_wait(-1);
- if (osnoise_trace_is_off(tool, record))
- break;
+ if (!stop_tracing) {
+ /* Threshold overflow, perform actions on threshold */
+ retval = common_threshold_handler(tool);
+ if (retval)
+ return retval;
- /* is there still any user-threads ? */
- if (params->user_workload) {
- if (params_u.stopped_running) {
- debug_msg("timerlat user-space threads stopped!\n");
+ if (!should_continue_tracing(tool->params))
break;
+
+ if (timerlat_bpf_restart_tracing()) {
+ err_msg("Error restarting BPF trace\n");
+ return -1;
}
}
}
+ timerlat_bpf_detach();
- if (params->user_workload && !params_u.stopped_running) {
- params_u.should_run = 0;
- sleep(1);
- }
-
- timerlat_print_stats(params, tool);
+ retval = timerlat_hist_bpf_pull_data(tool);
+ if (retval)
+ err_msg("Error pulling BPF data\n");
- return_value = 0;
-
- if (osnoise_trace_is_off(tool, record) && !stop_tracing) {
- printf("rtla timerlat hit stop tracing\n");
+ return retval;
+}
- if (!params->no_aa)
- timerlat_auto_analysis(params->stop_us, params->stop_total_us);
+static int timerlat_hist_main(struct osnoise_tool *tool)
+{
+ struct timerlat_params *params = to_timerlat_params(tool->params);
+ int retval;
- if (params->trace_output) {
- printf(" Saving trace to %s\n", params->trace_output);
- save_trace_to_file(record->trace.inst, params->trace_output);
- }
- }
+ if (params->mode == TRACING_MODE_TRACEFS)
+ retval = hist_main_loop(tool);
+ else
+ retval = timerlat_hist_bpf_main_loop(tool);
-out_hist:
- timerlat_aa_destroy();
- if (dma_latency_fd >= 0)
- close(dma_latency_fd);
- if (params->deepest_idle_state >= -1) {
- for (i = 0; i < nr_cpus; i++) {
- if (params->cpus && !CPU_ISSET(i, &params->monitored_cpus))
- continue;
- restore_cpu_idle_disable_state(i);
- }
- }
- trace_events_destroy(&record->trace, params->events);
- params->events = NULL;
-out_free:
- timerlat_free_histogram(tool->data);
- osnoise_destroy_tool(aa);
- osnoise_destroy_tool(record);
- osnoise_destroy_tool(tool);
- free(params);
- free_cpu_idle_disable_states();
-out_exit:
- exit(return_value);
+ return retval;
}
+
+struct tool_ops timerlat_hist_ops = {
+ .tracer = "timerlat",
+ .comm_prefix = "timerlat/",
+ .parse_args = timerlat_hist_parse_args,
+ .init_tool = timerlat_init_hist,
+ .apply_config = timerlat_hist_apply_config,
+ .enable = timerlat_enable,
+ .main = timerlat_hist_main,
+ .print_stats = timerlat_print_stats,
+ .analyze = timerlat_analyze,
+ .free = timerlat_free_histogram_tool,
+};
diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c
index 05a9403b01d2..64cbdcc878b0 100644
--- a/tools/tracing/rtla/src/timerlat_top.c
+++ b/tools/tracing/rtla/src/timerlat_top.c
@@ -11,48 +11,13 @@
#include <unistd.h>
#include <stdio.h>
#include <time.h>
-#include <errno.h>
#include <sched.h>
#include <pthread.h>
-#include "utils.h"
-#include "osnoise.h"
#include "timerlat.h"
#include "timerlat_aa.h"
-#include "timerlat_u.h"
-
-struct timerlat_top_params {
- char *cpus;
- cpu_set_t monitored_cpus;
- char *trace_output;
- char *cgroup_name;
- unsigned long long runtime;
- long long stop_us;
- long long stop_total_us;
- long long timerlat_period_us;
- long long print_stack;
- int sleep_time;
- int output_divisor;
- int duration;
- int quiet;
- int set_sched;
- int dma_latency;
- int no_aa;
- int aa_only;
- int dump_tasks;
- int cgroup;
- int hk_cpus;
- int user_top;
- int user_workload;
- int kernel_workload;
- int pretty_output;
- int warmup;
- int buffer_size;
- int deepest_idle_state;
- cpu_set_t hk_cpu_set;
- struct sched_attr sched_param;
- struct trace_events *events;
-};
+#include "timerlat_bpf.h"
+#include "common.h"
struct timerlat_top_cpu {
unsigned long long irq_count;
@@ -77,23 +42,27 @@ struct timerlat_top_cpu {
struct timerlat_top_data {
struct timerlat_top_cpu *cpu_data;
- int nr_cpus;
};
/*
* timerlat_free_top - free runtime data
*/
-static void
-timerlat_free_top(struct timerlat_top_data *data)
+static void timerlat_free_top(struct timerlat_top_data *data)
{
free(data->cpu_data);
free(data);
}
+static void timerlat_free_top_tool(struct osnoise_tool *tool)
+{
+ timerlat_free_top(tool->data);
+ timerlat_free(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;
@@ -102,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)
@@ -162,9 +129,13 @@ timerlat_top_update(struct osnoise_tool *tool, int cpu,
unsigned long long thread,
unsigned long long latency)
{
+ struct timerlat_params *params = to_timerlat_params(tool->params);
struct timerlat_top_data *data = tool->data;
struct timerlat_top_cpu *cpu_data = &data->cpu_data[cpu];
+ if (params->common.output_divisor)
+ latency = latency / params->common.output_divisor;
+
if (!thread) {
cpu_data->irq_count++;
cpu_data->cur_irq = latency;
@@ -194,15 +165,13 @@ timerlat_top_handler(struct trace_seq *s, struct tep_record *record,
struct tep_event *event, void *context)
{
struct trace_instance *trace = context;
- struct timerlat_top_params *params;
unsigned long long latency, thread;
struct osnoise_tool *top;
int cpu = record->cpu;
top = container_of(trace, struct osnoise_tool, trace);
- params = top->params;
- if (!params->aa_only) {
+ if (!top->params->aa_only) {
tep_get_field_val(s, event, "context", record, &thread, 1);
tep_get_field_val(s, event, "timer_latency", record, &latency, 1);
@@ -213,44 +182,110 @@ timerlat_top_handler(struct trace_seq *s, struct tep_record *record,
}
/*
+ * timerlat_top_bpf_pull_data - copy data from BPF maps into userspace
+ */
+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[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);
+ if (err)
+ return err;
+ 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);
+ if (err)
+ return err;
+ 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);
+ if (err)
+ return err;
+ 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);
+ if (err)
+ return err;
+ 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);
+ if (err)
+ return err;
+ 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];
+ }
+
+ return 0;
+}
+
+/*
* timerlat_top_header - print the header of the tool output
*/
-static void timerlat_top_header(struct timerlat_top_params *params, struct osnoise_tool *top)
+static void timerlat_top_header(struct timerlat_params *params, struct osnoise_tool *top)
{
struct trace_seq *s = top->trace.seq;
+ bool pretty = params->common.pretty_output;
char duration[26];
get_duration(top->start_time, duration, sizeof(duration));
- if (params->pretty_output)
+ if (pretty)
trace_seq_printf(s, "\033[2;37;40m");
trace_seq_printf(s, " Timer Latency ");
- if (params->user_top)
+ if (params->common.user_data)
trace_seq_printf(s, " ");
- if (params->pretty_output)
+ if (pretty)
trace_seq_printf(s, "\033[0;0;0m");
trace_seq_printf(s, "\n");
trace_seq_printf(s, "%-6s | IRQ Timer Latency (%s) | Thread Timer Latency (%s)", duration,
- params->output_divisor == 1 ? "ns" : "us",
- params->output_divisor == 1 ? "ns" : "us");
+ params->common.output_divisor == 1 ? "ns" : "us",
+ params->common.output_divisor == 1 ? "ns" : "us");
- if (params->user_top) {
+ if (params->common.user_data) {
trace_seq_printf(s, " | Ret user Timer Latency (%s)",
- params->output_divisor == 1 ? "ns" : "us");
+ params->common.output_divisor == 1 ? "ns" : "us");
}
trace_seq_printf(s, "\n");
- if (params->pretty_output)
+ if (pretty)
trace_seq_printf(s, "\033[2;30;47m");
trace_seq_printf(s, "CPU COUNT | cur min avg max | cur min avg max");
- if (params->user_top)
+ if (params->common.user_data)
trace_seq_printf(s, " | cur min avg max");
- if (params->pretty_output)
+ if (pretty)
trace_seq_printf(s, "\033[0;0;0m");
trace_seq_printf(s, "\n");
}
@@ -262,16 +297,11 @@ static const char *no_value = " -";
*/
static void timerlat_top_print(struct osnoise_tool *top, int cpu)
{
-
- struct timerlat_top_params *params = top->params;
+ struct timerlat_params *params = to_timerlat_params(top->params);
struct timerlat_top_data *data = top->data;
struct timerlat_top_cpu *cpu_data = &data->cpu_data[cpu];
- int divisor = params->output_divisor;
struct trace_seq *s = top->trace.seq;
- if (divisor == 0)
- return;
-
/*
* Skip if no data is available: is this cpu offline?
*/
@@ -286,23 +316,23 @@ static void timerlat_top_print(struct osnoise_tool *top, int cpu)
if (!cpu_data->irq_count) {
trace_seq_printf(s, "%s %s %s %s |", no_value, no_value, no_value, no_value);
} else {
- trace_seq_printf(s, "%9llu ", cpu_data->cur_irq / params->output_divisor);
- trace_seq_printf(s, "%9llu ", cpu_data->min_irq / params->output_divisor);
- trace_seq_printf(s, "%9llu ", (cpu_data->sum_irq / cpu_data->irq_count) / divisor);
- trace_seq_printf(s, "%9llu |", cpu_data->max_irq / divisor);
+ trace_seq_printf(s, "%9llu ", cpu_data->cur_irq);
+ trace_seq_printf(s, "%9llu ", cpu_data->min_irq);
+ trace_seq_printf(s, "%9llu ", cpu_data->sum_irq / cpu_data->irq_count);
+ trace_seq_printf(s, "%9llu |", cpu_data->max_irq);
}
if (!cpu_data->thread_count) {
trace_seq_printf(s, "%s %s %s %s", no_value, no_value, no_value, no_value);
} else {
- trace_seq_printf(s, "%9llu ", cpu_data->cur_thread / divisor);
- trace_seq_printf(s, "%9llu ", cpu_data->min_thread / divisor);
+ trace_seq_printf(s, "%9llu ", cpu_data->cur_thread);
+ trace_seq_printf(s, "%9llu ", cpu_data->min_thread);
trace_seq_printf(s, "%9llu ",
- (cpu_data->sum_thread / cpu_data->thread_count) / divisor);
- trace_seq_printf(s, "%9llu", cpu_data->max_thread / divisor);
+ cpu_data->sum_thread / cpu_data->thread_count);
+ trace_seq_printf(s, "%9llu", cpu_data->max_thread);
}
- if (!params->user_top) {
+ if (!params->common.user_data) {
trace_seq_printf(s, "\n");
return;
}
@@ -312,11 +342,11 @@ static void timerlat_top_print(struct osnoise_tool *top, int cpu)
if (!cpu_data->user_count) {
trace_seq_printf(s, "%s %s %s %s\n", no_value, no_value, no_value, no_value);
} else {
- trace_seq_printf(s, "%9llu ", cpu_data->cur_user / divisor);
- trace_seq_printf(s, "%9llu ", cpu_data->min_user / divisor);
+ trace_seq_printf(s, "%9llu ", cpu_data->cur_user);
+ trace_seq_printf(s, "%9llu ", cpu_data->min_user);
trace_seq_printf(s, "%9llu ",
- (cpu_data->sum_user / cpu_data->user_count) / divisor);
- trace_seq_printf(s, "%9llu\n", cpu_data->max_user / divisor);
+ cpu_data->sum_user / cpu_data->user_count);
+ trace_seq_printf(s, "%9llu\n", cpu_data->max_user);
}
}
@@ -327,15 +357,11 @@ static void
timerlat_top_print_sum(struct osnoise_tool *top, struct timerlat_top_cpu *summary)
{
const char *split = "----------------------------------------";
- struct timerlat_top_params *params = top->params;
+ struct timerlat_params *params = to_timerlat_params(top->params);
unsigned long long count = summary->irq_count;
- int divisor = params->output_divisor;
struct trace_seq *s = top->trace.seq;
int e = 0;
- if (divisor == 0)
- return;
-
/*
* Skip if no data is available: is this cpu offline?
*/
@@ -348,7 +374,7 @@ timerlat_top_print_sum(struct osnoise_tool *top, struct timerlat_top_cpu *summar
}
trace_seq_printf(s, "%.*s|%.*s|%.*s", 15, split, 40, split, 39, split);
- if (params->user_top)
+ if (params->common.user_data)
trace_seq_printf(s, "-|%.*s", 39, split);
trace_seq_printf(s, "\n");
@@ -358,22 +384,22 @@ timerlat_top_print_sum(struct osnoise_tool *top, struct timerlat_top_cpu *summar
trace_seq_printf(s, " %s %s %s |", no_value, no_value, no_value);
} else {
trace_seq_printf(s, " ");
- trace_seq_printf(s, "%9llu ", summary->min_irq / params->output_divisor);
- trace_seq_printf(s, "%9llu ", (summary->sum_irq / summary->irq_count) / divisor);
- trace_seq_printf(s, "%9llu |", summary->max_irq / divisor);
+ trace_seq_printf(s, "%9llu ", summary->min_irq);
+ trace_seq_printf(s, "%9llu ", summary->sum_irq / summary->irq_count);
+ trace_seq_printf(s, "%9llu |", summary->max_irq);
}
if (!summary->thread_count) {
trace_seq_printf(s, "%s %s %s %s", no_value, no_value, no_value, no_value);
} else {
trace_seq_printf(s, " ");
- trace_seq_printf(s, "%9llu ", summary->min_thread / divisor);
+ trace_seq_printf(s, "%9llu ", summary->min_thread);
trace_seq_printf(s, "%9llu ",
- (summary->sum_thread / summary->thread_count) / divisor);
- trace_seq_printf(s, "%9llu", summary->max_thread / divisor);
+ summary->sum_thread / summary->thread_count);
+ trace_seq_printf(s, "%9llu", summary->max_thread);
}
- if (!params->user_top) {
+ if (!params->common.user_data) {
trace_seq_printf(s, "\n");
return;
}
@@ -384,10 +410,10 @@ timerlat_top_print_sum(struct osnoise_tool *top, struct timerlat_top_cpu *summar
trace_seq_printf(s, " %s %s %s |", no_value, no_value, no_value);
} else {
trace_seq_printf(s, " ");
- trace_seq_printf(s, "%9llu ", summary->min_user / divisor);
+ trace_seq_printf(s, "%9llu ", summary->min_user);
trace_seq_printf(s, "%9llu ",
- (summary->sum_user / summary->user_count) / divisor);
- trace_seq_printf(s, "%9llu\n", summary->max_user / divisor);
+ summary->sum_user / summary->user_count);
+ trace_seq_printf(s, "%9llu\n", summary->max_user);
}
}
@@ -404,29 +430,24 @@ static void clear_terminal(struct trace_seq *seq)
* timerlat_print_stats - print data for all cpus
*/
static void
-timerlat_print_stats(struct timerlat_top_params *params, struct osnoise_tool *top)
+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->aa_only)
+ if (params->common.aa_only)
return;
- if (nr_cpus == -1)
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
-
- if (!params->quiet)
+ if (!params->common.quiet)
clear_terminal(trace->seq);
timerlat_top_reset_sum(&summary);
timerlat_top_header(params, top);
- for (i = 0; i < nr_cpus; i++) {
- if (params->cpus && !CPU_ISSET(i, &params->monitored_cpus))
- continue;
+ for_each_monitored_cpu(i, &params->common) {
timerlat_top_print(top, i);
timerlat_top_update_sum(top, i, &summary);
}
@@ -441,17 +462,16 @@ timerlat_print_stats(struct timerlat_top_params *params, struct osnoise_tool *to
/*
* timerlat_top_usage - prints timerlat top usage message
*/
-static void timerlat_top_usage(char *usage)
+static void timerlat_top_usage(void)
{
- int i;
+ static const char *const msg_start[] = {
+ "[-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\",
+ " [[-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
+ " [-P priority] [--dma-latency us] [--aa-only us] [-C [cgroup_name]] [-u|-k] [--warm-up s] [--deepest-idle-state n]",
+ NULL,
+ };
- static const char *const msg[] = {
- "",
- " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\",
- " [[-t[file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
- " [-P priority] [--dma-latency us] [--aa-only us] [-C[=cgroup_name]] [-u|-k] [--warm-up s] [--deepest-idle-state n]",
- "",
- " -h/--help: print this menu",
+ static const char *const msg_opts[] = {
" -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
" --aa-only us: stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)",
" -p/--period us: timerlat period in us",
@@ -460,11 +480,11 @@ static void timerlat_top_usage(char *usage)
" -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
" -c/--cpus cpus: run the tracer only on the given cpus",
" -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup[=cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
+ " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
" -d/--duration time[s|m|h|d]: duration of the session",
" -D/--debug: print debug info",
" --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)",
- " -t/--trace[file]: save the stopped trace to [file|timerlat_trace.txt]",
+ " -t/--trace [file]: save the stopped trace to [file|timerlat_trace.txt]",
" -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
" --filter <command>: enable a trace event filter to the previous -e event",
" --trigger <command>: enable a trace event trigger to the previous -e event",
@@ -484,39 +504,33 @@ static void timerlat_top_usage(char *usage)
" --warm-up s: let the workload run for s seconds before collecting data",
" --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
" --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
+ " --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
+ " --on-end: define action to be executed at measurement end, multiple are allowed",
+ " --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
+ " --stack-format <format>: set the stack format (truncate, skip, full)",
NULL,
};
- if (usage)
- fprintf(stderr, "%s\n", usage);
-
- fprintf(stderr, "rtla timerlat top: a per-cpu summary of the timer latency (version %s)\n",
- VERSION);
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
-
- if (usage)
- exit(EXIT_FAILURE);
-
- exit(EXIT_SUCCESS);
+ common_usage("timerlat", "top", "a per-cpu summary of the timer latency",
+ msg_start, msg_opts);
}
/*
* timerlat_top_parse_args - allocs, parse and fill the cmd line parameters
*/
-static struct timerlat_top_params
+static struct common_params
*timerlat_top_parse_args(int argc, char **argv)
{
- struct timerlat_top_params *params;
- struct trace_events *tevent;
+ struct timerlat_params *params;
long long auto_thresh;
int retval;
int c;
+ char *trace_output = NULL;
+
+ params = calloc_fatal(1, sizeof(*params));
- params = calloc(1, sizeof(*params));
- if (!params)
- exit(1);
+ actions_init(&params->common.threshold_actions);
+ actions_init(&params->common.end_actions);
/* disabled by default */
params->dma_latency = -1;
@@ -525,22 +539,21 @@ static struct timerlat_top_params
params->deepest_idle_state = -2;
/* display data in microseconds */
- params->output_divisor = 1000;
+ params->common.output_divisor = 1000;
+
+ /* 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'},
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
- {"debug", no_argument, 0, 'D'},
- {"duration", required_argument, 0, 'd'},
- {"event", required_argument, 0, 'e'},
{"help", no_argument, 0, 'h'},
- {"house-keeping", required_argument, 0, 'H'},
{"irq", required_argument, 0, 'i'},
{"nano", no_argument, 0, 'n'},
{"period", required_argument, 0, 'p'},
- {"priority", required_argument, 0, 'P'},
{"quiet", no_argument, 0, 'q'},
{"stack", required_argument, 0, 's'},
{"thread", required_argument, 0, 'T'},
@@ -557,14 +570,17 @@ static struct timerlat_top_params
{"warm-up", required_argument, 0, '6'},
{"trace-buffer-size", required_argument, 0, '7'},
{"deepest-idle-state", required_argument, 0, '8'},
+ {"on-threshold", required_argument, 0, '9'},
+ {"on-end", required_argument, 0, '\1'},
+ {"bpf-action", required_argument, 0, '\2'},
+ {"stack-format", required_argument, 0, '\3'},
{0, 0, 0, 0}
};
- /* getopt_long stores the option index here. */
- int option_index = 0;
+ if (common_parse_options(argc, argv, &params->common))
+ continue;
- c = getopt_long(argc, argv, "a:c:C::d:De:hH:i:knp:P:qs:t::T:uU0:1:2:345:6:7:",
- long_options, &option_index);
+ c = getopt_auto(argc, argv, long_options);
/* detect the end of the options. */
if (c == -1)
@@ -575,151 +591,85 @@ static struct timerlat_top_params
auto_thresh = get_llong_from_str(optarg);
/* set thread stop to auto_thresh */
- params->stop_total_us = auto_thresh;
- params->stop_us = auto_thresh;
+ params->common.stop_total_us = auto_thresh;
+ params->common.stop_us = auto_thresh;
/* get stack trace */
params->print_stack = auto_thresh;
/* set trace */
- params->trace_output = "timerlat_trace.txt";
+ if (!trace_output)
+ trace_output = "timerlat_trace.txt";
+
break;
case '5':
/* it is here because it is similar to -a */
auto_thresh = get_llong_from_str(optarg);
/* set thread stop to auto_thresh */
- params->stop_total_us = auto_thresh;
- params->stop_us = auto_thresh;
+ params->common.stop_total_us = auto_thresh;
+ params->common.stop_us = auto_thresh;
/* get stack trace */
params->print_stack = auto_thresh;
/* set aa_only to avoid parsing the trace */
- params->aa_only = 1;
- break;
- case 'c':
- retval = parse_cpu_set(optarg, &params->monitored_cpus);
- if (retval)
- timerlat_top_usage("\nInvalid -c cpu list\n");
- params->cpus = optarg;
- break;
- case 'C':
- params->cgroup = 1;
- if (!optarg) {
- /* will inherit this cgroup */
- params->cgroup_name = NULL;
- } else if (*optarg == '=') {
- /* skip the = */
- params->cgroup_name = ++optarg;
- }
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- params->duration = parse_seconds_duration(optarg);
- if (!params->duration)
- timerlat_top_usage("Invalid -d duration\n");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent) {
- err_msg("Error alloc trace event");
- exit(EXIT_FAILURE);
- }
-
- if (params->events)
- tevent->next = params->events;
- params->events = tevent;
+ params->common.aa_only = 1;
break;
case 'h':
case '?':
- timerlat_top_usage(NULL);
- break;
- case 'H':
- params->hk_cpus = 1;
- retval = parse_cpu_set(optarg, &params->hk_cpu_set);
- if (retval) {
- err_msg("Error parsing house keeping CPUs\n");
- exit(EXIT_FAILURE);
- }
+ timerlat_top_usage();
break;
case 'i':
- params->stop_us = get_llong_from_str(optarg);
+ params->common.stop_us = get_llong_from_str(optarg);
break;
case 'k':
- params->kernel_workload = true;
+ params->common.kernel_workload = true;
break;
case 'n':
- params->output_divisor = 1;
+ params->common.output_divisor = 1;
break;
case 'p':
params->timerlat_period_us = get_llong_from_str(optarg);
if (params->timerlat_period_us > 1000000)
- timerlat_top_usage("Period longer than 1 s\n");
- break;
- case 'P':
- retval = parse_prio(optarg, &params->sched_param);
- if (retval == -1)
- timerlat_top_usage("Invalid -P priority");
- params->set_sched = 1;
+ fatal("Period longer than 1 s");
break;
case 'q':
- params->quiet = 1;
+ params->common.quiet = 1;
break;
case 's':
params->print_stack = get_llong_from_str(optarg);
break;
case 'T':
- params->stop_total_us = get_llong_from_str(optarg);
+ params->common.stop_total_us = get_llong_from_str(optarg);
break;
case 't':
- if (optarg) {
- if (optarg[0] == '=')
- params->trace_output = &optarg[1];
- else
- params->trace_output = &optarg[0];
- } else if (optind < argc && argv[optind][0] != '-')
- params->trace_output = argv[optind];
- else
- params->trace_output = "timerlat_trace.txt";
-
+ trace_output = parse_optional_arg(argc, argv);
+ if (!trace_output)
+ trace_output = "timerlat_trace.txt";
break;
case 'u':
- params->user_workload = true;
+ params->common.user_workload = true;
/* fallback: -u implies -U */
case 'U':
- params->user_top = true;
+ params->common.user_data = true;
break;
case '0': /* trigger */
- if (params->events) {
- retval = trace_event_add_trigger(params->events, optarg);
- if (retval) {
- err_msg("Error adding trigger %s\n", optarg);
- exit(EXIT_FAILURE);
- }
- } else {
- timerlat_top_usage("--trigger requires a previous -e\n");
- }
+ 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->events) {
- retval = trace_event_add_filter(params->events, optarg);
- if (retval) {
- err_msg("Error adding filter %s\n", optarg);
- exit(EXIT_FAILURE);
- }
- } else {
- timerlat_top_usage("--filter requires a previous -e\n");
- }
+ 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);
- if (params->dma_latency < 0 || params->dma_latency > 10000) {
- err_msg("--dma-latency needs to be >= 0 and < 10000");
- exit(EXIT_FAILURE);
- }
+ if (params->dma_latency < 0 || params->dma_latency > 10000)
+ fatal("--dma-latency needs to be >= 0 and < 10000");
break;
case '3': /* no-aa */
params->no_aa = 1;
@@ -728,143 +678,85 @@ static struct timerlat_top_params
params->dump_tasks = 1;
break;
case '6':
- params->warmup = get_llong_from_str(optarg);
+ params->common.warmup = get_llong_from_str(optarg);
break;
case '7':
- params->buffer_size = get_llong_from_str(optarg);
+ params->common.buffer_size = get_llong_from_str(optarg);
break;
case '8':
params->deepest_idle_state = get_llong_from_str(optarg);
break;
+ case '9':
+ retval = actions_parse(&params->common.threshold_actions, optarg,
+ "timerlat_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", optarg);
+ break;
+ case '\1':
+ retval = actions_parse(&params->common.end_actions, optarg,
+ "timerlat_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", optarg);
+ break;
+ 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:
- timerlat_top_usage("Invalid option");
+ fatal("Invalid option");
}
}
- if (geteuid()) {
- err_msg("rtla needs root permission\n");
- exit(EXIT_FAILURE);
- }
+ if (trace_output)
+ actions_add_trace_output(&params->common.threshold_actions, trace_output);
+
+ if (geteuid())
+ fatal("rtla needs root permission");
/*
* Auto analysis only happens if stop tracing, thus:
*/
- if (!params->stop_us && !params->stop_total_us)
+ if (!params->common.stop_us && !params->common.stop_total_us)
params->no_aa = 1;
- if (params->no_aa && params->aa_only)
- timerlat_top_usage("--no-aa and --aa-only are mutually exclusive!");
+ if (params->no_aa && params->common.aa_only)
+ fatal("--no-aa and --aa-only are mutually exclusive!");
- if (params->kernel_workload && params->user_workload)
- timerlat_top_usage("--kernel-threads and --user-threads are mutually exclusive!");
+ if (params->common.kernel_workload && params->common.user_workload)
+ fatal("--kernel-threads and --user-threads are mutually exclusive!");
- return params;
+ /*
+ * If auto-analysis or trace output is enabled, switch from BPF mode to
+ * mixed mode
+ */
+ if (params->mode == TRACING_MODE_BPF &&
+ (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
+ params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
+ !params->no_aa))
+ params->mode = TRACING_MODE_MIXED;
+
+ return &params->common;
}
/*
* timerlat_top_apply_config - apply the top configs to the initialized tool
*/
static int
-timerlat_top_apply_config(struct osnoise_tool *top, struct timerlat_top_params *params)
+timerlat_top_apply_config(struct osnoise_tool *top)
{
+ struct timerlat_params *params = to_timerlat_params(top->params);
int retval;
- int i;
-
- if (!params->sleep_time)
- params->sleep_time = 1;
-
- if (params->cpus) {
- retval = osnoise_set_cpus(top->context, params->cpus);
- if (retval) {
- err_msg("Failed to apply CPUs config\n");
- goto out_err;
- }
- } else {
- for (i = 0; i < sysconf(_SC_NPROCESSORS_CONF); i++)
- CPU_SET(i, &params->monitored_cpus);
- }
-
- if (params->stop_us) {
- retval = osnoise_set_stop_us(top->context, params->stop_us);
- if (retval) {
- err_msg("Failed to set stop us\n");
- goto out_err;
- }
- }
-
- if (params->stop_total_us) {
- retval = osnoise_set_stop_total_us(top->context, params->stop_total_us);
- if (retval) {
- err_msg("Failed to set stop total us\n");
- goto out_err;
- }
- }
-
-
- if (params->timerlat_period_us) {
- retval = osnoise_set_timerlat_period_us(top->context, params->timerlat_period_us);
- if (retval) {
- err_msg("Failed to set timerlat period\n");
- goto out_err;
- }
- }
-
-
- if (params->print_stack) {
- retval = osnoise_set_print_stack(top->context, params->print_stack);
- if (retval) {
- err_msg("Failed to set print stack\n");
- goto out_err;
- }
- }
- if (params->hk_cpus) {
- retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set),
- &params->hk_cpu_set);
- if (retval == -1) {
- err_msg("Failed to set rtla to the house keeping CPUs\n");
- goto out_err;
- }
- } else if (params->cpus) {
- /*
- * Even if the user do not set a house-keeping CPU, try to
- * move rtla to a CPU set different to the one where the user
- * set the workload to run.
- *
- * No need to check results as this is an automatic attempt.
- */
- auto_house_keeping(&params->monitored_cpus);
- }
-
- /*
- * If the user did not specify a type of thread, try user-threads first.
- * Fall back to kernel threads otherwise.
- */
- if (!params->kernel_workload && !params->user_top) {
- retval = tracefs_file_exists(NULL, "osnoise/per_cpu/cpu0/timerlat_fd");
- if (retval) {
- debug_msg("User-space interface detected, setting user-threads\n");
- params->user_workload = 1;
- params->user_top = 1;
- } else {
- debug_msg("User-space interface not detected, setting kernel-threads\n");
- params->kernel_workload = 1;
- }
- }
-
- /*
- * Set workload according to type of thread if the kernel supports it.
- * On kernels without support, user threads will have already failed
- * on missing timerlat_fd, and kernel threads do not need it.
- */
- retval = osnoise_set_workload(top->context, params->kernel_workload);
- if (retval < -1) {
- err_msg("Failed to set OSNOISE_WORKLOAD option\n");
+ retval = timerlat_apply_config(top, params);
+ if (retval)
goto out_err;
- }
- if (isatty(STDOUT_FILENO) && !params->quiet)
- params->pretty_output = 1;
+ if (isatty(STDOUT_FILENO) && !params->common.quiet)
+ params->common.pretty_output = 1;
return 0;
@@ -876,23 +768,18 @@ out_err:
* timerlat_init_top - initialize a timerlat top tool with parameters
*/
static struct osnoise_tool
-*timerlat_init_top(struct timerlat_top_params *params)
+*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;
- top->params = params;
-
tep_register_event_handler(top->trace.tep, -1, "ftrace", "timerlat",
timerlat_top_handler, top);
@@ -903,299 +790,86 @@ out_err:
return NULL;
}
-static int stop_tracing;
-static struct trace_instance *top_inst = NULL;
-static void stop_top(int sig)
-{
- if (stop_tracing) {
- /*
- * Stop requested twice in a row; abort event processing and
- * exit immediately
- */
- tracefs_iterate_stop(top_inst->inst);
- return;
- }
- stop_tracing = 1;
- if (top_inst)
- trace_instance_stop(top_inst);
-}
-
/*
- * timerlat_top_set_signals - handles the signal to stop the tool
+ * timerlat_top_bpf_main_loop - main loop to process events (BPF variant)
*/
-static void
-timerlat_top_set_signals(struct timerlat_top_params *params)
-{
- signal(SIGINT, stop_top);
- if (params->duration) {
- signal(SIGALRM, stop_top);
- alarm(params->duration);
- }
-}
-
-int timerlat_top_main(int argc, char *argv[])
+static int
+timerlat_top_bpf_main_loop(struct osnoise_tool *tool)
{
- struct timerlat_top_params *params;
- struct osnoise_tool *record = NULL;
- struct timerlat_u_params params_u;
- struct osnoise_tool *top = NULL;
- struct osnoise_tool *aa = NULL;
- struct trace_instance *trace;
- int dma_latency_fd = -1;
- pthread_t timerlat_u;
- int return_value = 1;
- char *max_lat;
- int retval;
- int nr_cpus, i;
+ const struct common_params *params = tool->params;
+ int retval, wait_retval;
- params = timerlat_top_parse_args(argc, argv);
- if (!params)
- exit(1);
-
- top = timerlat_init_top(params);
- if (!top) {
- err_msg("Could not init osnoise top\n");
- goto out_exit;
+ if (params->aa_only) {
+ /* Auto-analysis only, just wait for stop tracing */
+ timerlat_bpf_wait(-1);
+ return 0;
}
- retval = timerlat_top_apply_config(top, params);
- if (retval) {
- err_msg("Could not apply config\n");
- goto out_free;
- }
-
- trace = &top->trace;
- /*
- * Save trace instance into global variable so that SIGINT can stop
- * the timerlat tracer.
- * Otherwise, rtla could loop indefinitely when overloaded.
- */
- top_inst = trace;
-
-
- retval = enable_timerlat(trace);
- if (retval) {
- err_msg("Failed to enable timerlat tracer\n");
- goto out_free;
- }
+ /* Pull and display data in a loop */
+ while (!stop_tracing) {
+ wait_retval = timerlat_bpf_wait(params->quiet ? -1 :
+ params->sleep_time);
- if (params->set_sched) {
- retval = set_comm_sched_attr("timerlat/", &params->sched_param);
+ retval = timerlat_top_bpf_pull_data(tool);
if (retval) {
- err_msg("Failed to set sched parameters\n");
- goto out_free;
- }
- }
-
- if (params->cgroup && !params->user_top) {
- retval = set_comm_cgroup("timerlat/", params->cgroup_name);
- if (!retval) {
- err_msg("Failed to move threads to cgroup\n");
- goto out_free;
+ err_msg("Error pulling BPF data\n");
+ return retval;
}
- }
-
- if (params->dma_latency >= 0) {
- dma_latency_fd = set_cpu_dma_latency(params->dma_latency);
- if (dma_latency_fd < 0) {
- err_msg("Could not set /dev/cpu_dma_latency.\n");
- goto out_free;
- }
- }
-
- if (params->deepest_idle_state >= -1) {
- if (!have_libcpupower_support()) {
- err_msg("rtla built without libcpupower, --deepest-idle-state is not supported\n");
- goto out_free;
- }
-
- nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
-
- for (i = 0; i < nr_cpus; i++) {
- if (params->cpus && !CPU_ISSET(i, &params->monitored_cpus))
- continue;
- if (save_cpu_idle_disable_state(i) < 0) {
- err_msg("Could not save cpu idle state.\n");
- goto out_free;
- }
- if (set_deepest_cpu_idle_state(i, params->deepest_idle_state) < 0) {
- err_msg("Could not set deepest cpu idle state.\n");
- goto out_free;
- }
- }
- }
- if (params->trace_output) {
- record = osnoise_init_trace_tool("timerlat");
- if (!record) {
- err_msg("Failed to enable the trace instance\n");
- goto out_free;
- }
-
- if (params->events) {
- retval = trace_events_enable(&record->trace, params->events);
- if (retval)
- goto out_top;
- }
+ if (!params->quiet)
+ timerlat_print_stats(tool);
- if (params->buffer_size > 0) {
- retval = trace_set_buffer_size(&record->trace, params->buffer_size);
+ if (wait_retval != 0) {
+ /* Stopping requested by tracer */
+ retval = common_threshold_handler(tool);
if (retval)
- goto out_top;
- }
- }
+ return retval;
- if (!params->no_aa) {
- if (params->aa_only) {
- /* as top is not used for display, use it for aa */
- aa = top;
- } else {
- /* otherwise, a new instance is needed */
- aa = osnoise_init_tool("timerlat_aa");
- if (!aa)
- goto out_top;
- }
-
- retval = timerlat_aa_init(aa, params->dump_tasks);
- if (retval) {
- err_msg("Failed to enable the auto analysis instance\n");
- goto out_top;
- }
+ if (!should_continue_tracing(tool->params))
+ break;
- /* if it is re-using the main instance, there is no need to start it */
- if (aa != top) {
- retval = enable_timerlat(&aa->trace);
- if (retval) {
- err_msg("Failed to enable timerlat tracer\n");
- goto out_top;
+ if (timerlat_bpf_restart_tracing()) {
+ err_msg("Error restarting BPF trace\n");
+ return -1;
}
}
- }
-
- if (params->user_workload) {
- /* rtla asked to stop */
- params_u.should_run = 1;
- /* all threads left */
- params_u.stopped_running = 0;
-
- params_u.set = &params->monitored_cpus;
- if (params->set_sched)
- params_u.sched_param = &params->sched_param;
- else
- params_u.sched_param = NULL;
-
- params_u.cgroup_name = params->cgroup_name;
-
- retval = pthread_create(&timerlat_u, NULL, timerlat_u_dispatcher, &params_u);
- if (retval)
- err_msg("Error creating timerlat user-space threads\n");
- }
-
- if (params->warmup > 0) {
- debug_msg("Warming up for %d seconds\n", params->warmup);
- sleep(params->warmup);
- }
-
- /*
- * Start the tracers here, after having set all instances.
- *
- * Let the trace instance start first for the case of hitting a stop
- * tracing while enabling other instances. The trace instance is the
- * one with most valuable information.
- */
- if (params->trace_output)
- trace_instance_start(&record->trace);
- if (!params->no_aa && aa != top)
- trace_instance_start(&aa->trace);
- trace_instance_start(trace);
-
- top->start_time = time(NULL);
- timerlat_top_set_signals(params);
-
- while (!stop_tracing) {
- sleep(params->sleep_time);
-
- if (params->aa_only && !osnoise_trace_is_off(top, record))
- continue;
-
- retval = tracefs_iterate_raw_events(trace->tep,
- trace->inst,
- NULL,
- 0,
- collect_registered_events,
- trace);
- if (retval < 0) {
- err_msg("Error iterating on events\n");
- goto out_top;
- }
-
- if (!params->quiet)
- timerlat_print_stats(params, top);
-
- if (osnoise_trace_is_off(top, record))
- break;
/* is there still any user-threads ? */
if (params->user_workload) {
- if (params_u.stopped_running) {
+ if (params->user.stopped_running) {
debug_msg("timerlat user space threads stopped!\n");
break;
}
}
}
- if (params->user_workload && !params_u.stopped_running) {
- params_u.should_run = 0;
- sleep(1);
- }
-
- timerlat_print_stats(params, top);
-
- return_value = 0;
-
- if (osnoise_trace_is_off(top, record) && !stop_tracing) {
- printf("rtla timerlat hit stop tracing\n");
+ return 0;
+}
- if (!params->no_aa)
- timerlat_auto_analysis(params->stop_us, params->stop_total_us);
+static int timerlat_top_main_loop(struct osnoise_tool *tool)
+{
+ struct timerlat_params *params = to_timerlat_params(tool->params);
+ int retval;
- if (params->trace_output) {
- printf(" Saving trace to %s\n", params->trace_output);
- save_trace_to_file(record->trace.inst, params->trace_output);
- }
- } else if (params->aa_only) {
- /*
- * If the trace did not stop with --aa-only, at least print the
- * max known latency.
- */
- max_lat = tracefs_instance_file_read(trace->inst, "tracing_max_latency", NULL);
- if (max_lat) {
- printf(" Max latency was %s\n", max_lat);
- free(max_lat);
- }
+ if (params->mode == TRACING_MODE_TRACEFS) {
+ retval = top_main_loop(tool);
+ } else {
+ retval = timerlat_top_bpf_main_loop(tool);
+ timerlat_bpf_detach();
}
-out_top:
- timerlat_aa_destroy();
- if (dma_latency_fd >= 0)
- close(dma_latency_fd);
- if (params->deepest_idle_state >= -1) {
- for (i = 0; i < nr_cpus; i++) {
- if (params->cpus && !CPU_ISSET(i, &params->monitored_cpus))
- continue;
- restore_cpu_idle_disable_state(i);
- }
- }
- trace_events_destroy(&record->trace, params->events);
- params->events = NULL;
-out_free:
- timerlat_free_top(top->data);
- if (aa && aa != top)
- osnoise_destroy_tool(aa);
- osnoise_destroy_tool(record);
- osnoise_destroy_tool(top);
- free(params);
- free_cpu_idle_disable_states();
-out_exit:
- exit(return_value);
+ return retval;
}
+
+struct tool_ops timerlat_top_ops = {
+ .tracer = "timerlat",
+ .comm_prefix = "timerlat/",
+ .parse_args = timerlat_top_parse_args,
+ .init_tool = timerlat_init_top,
+ .apply_config = timerlat_top_apply_config,
+ .enable = timerlat_enable,
+ .main = timerlat_top_main_loop,
+ .print_stats = timerlat_print_stats,
+ .analyze = timerlat_analyze,
+ .free = timerlat_free_top_tool,
+};
diff --git a/tools/tracing/rtla/src/timerlat_u.c b/tools/tracing/rtla/src/timerlat_u.c
index 01dbf9a6b5a5..c80edaf07b00 100644
--- a/tools/tracing/rtla/src/timerlat_u.c
+++ b/tools/tracing/rtla/src/timerlat_u.c
@@ -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;
@@ -51,10 +51,8 @@ static int timerlat_u_main(int cpu, struct timerlat_u_params *params)
if (!params->sched_param) {
retval = sched_setscheduler(0, SCHED_FIFO, &sp);
- if (retval < 0) {
- err_msg("Error setting timerlat u default priority: %s\n", strerror(errno));
- exit(1);
- }
+ if (retval < 0)
+ fatal("Error setting timerlat u default priority: %s", strerror(errno));
} else {
retval = __set_sched_attr(getpid(), params->sched_param);
if (retval) {
@@ -78,16 +76,14 @@ static int timerlat_u_main(int cpu, struct timerlat_u_params *params)
snprintf(buffer, sizeof(buffer), "osnoise/per_cpu/cpu%d/timerlat_fd", cpu);
timerlat_fd = tracefs_instance_file_open(NULL, buffer, O_RDONLY);
- if (timerlat_fd < 0) {
- err_msg("Error opening %s:%s\n", buffer, strerror(errno));
- exit(1);
- }
+ if (timerlat_fd < 0)
+ fatal("Error opening %s:%s", buffer, strerror(errno));
debug_msg("User-space timerlat pid %d on cpu %d\n", gettid(), cpu);
/* 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;
}
@@ -103,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;
@@ -135,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;
@@ -174,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);
}
@@ -201,7 +196,7 @@ void *timerlat_u_dispatcher(void *data)
sleep(1);
}
- timerlat_u_send_kill(procs, nr_cpus);
+ timerlat_u_send_kill(procs);
while (procs_count) {
pid = waitpid(-1, &wstatus, 0);
diff --git a/tools/tracing/rtla/src/timerlat_u.h b/tools/tracing/rtla/src/timerlat_u.h
index 661511908957..a692331bd1c7 100644
--- a/tools/tracing/rtla/src/timerlat_u.h
+++ b/tools/tracing/rtla/src/timerlat_u.h
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
+#pragma once
/*
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c
index 728f5029d533..e407447773d0 100644
--- a/tools/tracing/rtla/src/trace.c
+++ b/tools/tracing/rtla/src/trace.c
@@ -2,7 +2,6 @@
#define _GNU_SOURCE
#include <sys/sendfile.h>
#include <tracefs.h>
-#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
@@ -74,6 +73,11 @@ 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;
in_fd = tracefs_instance_file_open(inst, file, O_RDONLY);
if (in_fd < 0) {
@@ -81,21 +85,37 @@ int save_trace_to_file(struct tracefs_instance *inst, const char *filename)
return -1;
}
+ printf(" Saving trace to %s\n", filename);
out_fd = creat(filename, mode);
if (out_fd < 0) {
err_msg("Failed to create output file %s\n", filename);
goto out_close_in;
}
- do {
- retval = read(in_fd, buffer, sizeof(buffer));
- if (retval <= 0)
- goto out_close;
-
- retval = write(out_fd, buffer, retval);
- 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;
- } while (retval > 0);
+ }
+ if (n_read == 0)
+ break;
+
+ 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:
@@ -188,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);
@@ -271,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) {
@@ -293,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);
}
/*
@@ -326,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)
@@ -338,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);
@@ -355,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;
@@ -368,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);
@@ -389,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:
@@ -404,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)
@@ -418,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);
@@ -457,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)
@@ -469,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);
@@ -492,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)
@@ -504,7 +517,7 @@ static int trace_event_enable_trigger(struct trace_instance *instance,
return 1;
}
- snprintf(trigger, 1024, "%s\n", tevent->trigger);
+ snprintf(trigger, ARRAY_SIZE(trigger), "%s\n", tevent->trigger);
debug_msg("Enabling %s:%s trigger %s\n", tevent->system,
tevent->event ? : "*", tevent->trigger);
diff --git a/tools/tracing/rtla/src/trace.h b/tools/tracing/rtla/src/trace.h
index 3cd40dd3f06c..95b911a2228b 100644
--- a/tools/tracing/rtla/src/trace.h
+++ b/tools/tracing/rtla/src/trace.h
@@ -30,9 +30,6 @@ struct trace_seq *get_trace_seq(void);
int enable_tracer_by_name(struct tracefs_instance *inst, const char *tracer_name);
void disable_tracer(struct tracefs_instance *inst);
-int enable_osnoise(struct trace_instance *trace);
-int enable_timerlat(struct trace_instance *trace);
-
struct tracefs_instance *create_instance(char *instance_name);
void destroy_instance(struct tracefs_instance *inst);
@@ -48,6 +45,6 @@ void trace_events_destroy(struct trace_instance *instance,
int trace_events_enable(struct trace_instance *instance,
struct trace_events *events);
-int trace_event_add_filter(struct trace_events *event, char *filter);
-int trace_event_add_trigger(struct trace_events *event, char *trigger);
+void trace_event_add_filter(struct trace_events *event, char *filter);
+void trace_event_add_trigger(struct trace_events *event, char *trigger);
int trace_set_buffer_size(struct trace_instance *trace, int size);
diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c
index 4995d35cf3ec..9cec5b3e02c8 100644
--- a/tools/tracing/rtla/src/utils.c
+++ b/tools/tracing/rtla/src/utils.c
@@ -17,8 +17,9 @@
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
+#include <limits.h>
-#include "utils.h"
+#include "common.h"
#define MAX_MSG_LENGTH 1024
int config_debug;
@@ -57,6 +58,21 @@ void debug_msg(const char *fmt, ...)
}
/*
+ * fatal - print an error message and EOL to stderr and exit with ERROR
+ */
+void fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ exit(ERROR);
+}
+
+/*
* get_llong_from_str - get a long long int from a string
*/
long long get_llong_from_str(char *start)
@@ -97,20 +113,17 @@ void get_duration(time_t start_time, char *output, int output_size)
* Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set
* filling cpu_set_t argument.
*
- * Returns 1 on success, 0 otherwise.
+ * Returns 0 on success, 1 otherwise.
*/
int parse_cpu_set(char *cpu_list, cpu_set_t *set)
{
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)
@@ -149,6 +162,24 @@ err:
}
/*
+ * 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
*/
long parse_seconds_duration(char *val)
@@ -183,6 +214,21 @@ long parse_seconds_duration(char *val)
}
/*
+ * 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
*/
long parse_ns_duration(char *val)
@@ -193,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;
}
@@ -227,6 +273,8 @@ long parse_ns_duration(char *val)
# define __NR_sched_setattr 355
# elif __s390x__
# define __NR_sched_setattr 345
+# elif __loongarch__
+# define __NR_sched_setattr 274
# endif
#endif
@@ -276,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;
}
@@ -297,8 +345,8 @@ static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_e
if (retval <= 0)
return 0;
- retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
- if (retval)
+ buffer[MAX_PATH-1] = '\0';
+ if (!str_has_prefix(buffer, comm_prefix))
return 0;
/* comm already have \n */
@@ -320,6 +368,7 @@ int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
struct dirent *proc_entry;
DIR *procfs;
int retval;
+ int pid;
if (strlen(comm_prefix) >= MAX_PATH) {
err_msg("Command prefix is too long: %d < strlen(%s)\n",
@@ -339,20 +388,25 @@ int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
if (!retval)
continue;
+ if (strtoi(proc_entry->d_name, &pid)) {
+ err_msg("'%s' is not a valid pid", proc_entry->d_name);
+ retval = 1;
+ goto out;
+ }
/* procfs_is_workload_pid confirmed it is a pid */
- retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
+ retval = __set_sched_attr(pid, attr);
if (retval) {
err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
- goto out_err;
+ 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)
@@ -535,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);
@@ -543,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;
@@ -620,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;
@@ -725,6 +774,7 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
if (fd < 0)
return 0;
+ memset(path, 0, sizeof(path));
retval = read(fd, path, MAX_PATH);
close(fd);
@@ -732,6 +782,7 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
if (retval <= 0)
return 0;
+ path[MAX_PATH-1] = '\0';
start = path;
start = strstr(start, ":");
@@ -767,39 +818,42 @@ static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
}
/*
- * set_comm_cgroup - Set cgroup to pid_t pid
+ * open_cgroup_procs - Open the cgroup.procs file for the given cgroup
*
- * If cgroup argument is not NULL, the threads will move to the given cgroup.
- * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
+ * If cgroup argument is not NULL, the cgroup.procs file for that cgroup
+ * will be opened. Otherwise, the cgroup of the calling, i.e., rtla, thread
+ * will be used.
*
* Supports cgroup v2.
*
- * Returns 1 on success, 0 otherwise.
+ * Returns the file descriptor on success, -1 otherwise.
*/
-int set_pid_cgroup(pid_t pid, const char *cgroup)
+static int open_cgroup_procs(const char *cgroup)
{
char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
char cgroup_procs[MAX_PATH];
- char pid_str[24];
int retval;
int cg_fd;
+ size_t cg_path_len;
retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
if (!retval) {
err_msg("Did not find cgroupv2 mount point\n");
- return 0;
+ 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 0;
+ 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);
@@ -808,6 +862,29 @@ int set_pid_cgroup(pid_t pid, const char *cgroup)
cg_fd = open(cgroup_procs, O_RDWR);
if (cg_fd < 0)
+ return -1;
+
+ return cg_fd;
+}
+
+/*
+ * set_pid_cgroup - Set cgroup to pid_t pid
+ *
+ * If cgroup argument is not NULL, the threads will move to the given cgroup.
+ * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
+ *
+ * Supports cgroup v2.
+ *
+ * Returns 1 on success, 0 otherwise.
+ */
+int set_pid_cgroup(pid_t pid, const char *cgroup)
+{
+ char pid_str[24];
+ int retval;
+ int cg_fd;
+
+ cg_fd = open_cgroup_procs(cgroup);
+ if (cg_fd < 0)
return 0;
snprintf(pid_str, sizeof(pid_str), "%d\n", pid);
@@ -836,8 +913,6 @@ int set_pid_cgroup(pid_t pid, const char *cgroup)
*/
int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
{
- char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
- char cgroup_procs[MAX_PATH];
struct dirent *proc_entry;
DIR *procfs;
int retval;
@@ -849,29 +924,7 @@ int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
return 0;
}
- retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
- if (!retval) {
- err_msg("Did not find cgroupv2 mount point\n");
- return 0;
- }
-
- if (!cgroup) {
- retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
- sizeof(cgroup_path) - strlen(cgroup_path));
- if (!retval) {
- err_msg("Did not find self cgroup\n");
- return 0;
- }
- } else {
- snprintf(&cgroup_path[strlen(cgroup_path)],
- sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
- }
-
- snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
-
- debug_msg("Using cgroup path at: %s\n", cgroup_procs);
-
- cg_fd = open(cgroup_procs, O_RDWR);
+ cg_fd = open_cgroup_procs(cgroup);
if (cg_fd < 0)
return 0;
@@ -957,3 +1010,86 @@ int auto_house_keeping(cpu_set_t *monitored_cpus)
return 1;
}
+
+/**
+ * parse_optional_arg - Parse optional argument value
+ *
+ * Parse optional argument value, which can be in the form of:
+ * -sarg, -s/--long=arg, -s/--long arg
+ *
+ * Returns arg value if found, NULL otherwise.
+ */
+char *parse_optional_arg(int argc, char **argv)
+{
+ if (optarg) {
+ if (optarg[0] == '=') {
+ /* skip the = */
+ return &optarg[1];
+ } else {
+ return optarg;
+ }
+ /* parse argument of form -s [arg] and --long [arg]*/
+ } else if (optind < argc && argv[optind][0] != '-') {
+ /* consume optind */
+ return argv[optind++];
+ } else {
+ return NULL;
+ }
+}
+
+/*
+ * strtoi - convert string to integer with error checking
+ *
+ * Returns 0 on success, -1 if conversion fails or result is out of int range.
+ */
+int strtoi(const char *s, int *res)
+{
+ char *end_ptr;
+ long lres;
+
+ if (!*s)
+ return -1;
+
+ errno = 0;
+ lres = strtol(s, &end_ptr, 0);
+ if (errno || *end_ptr || lres > INT_MAX || lres < INT_MIN)
+ return -1;
+
+ *res = (int) lres;
+ return 0;
+}
+
+static inline void fatal_alloc(void)
+{
+ fatal("Error allocating memory\n");
+}
+
+void *calloc_fatal(size_t n, size_t size)
+{
+ void *p = calloc(n, size);
+
+ if (!p)
+ fatal_alloc();
+
+ return p;
+}
+
+void *reallocarray_fatal(void *p, size_t n, size_t size)
+{
+ p = reallocarray(p, n, size);
+
+ if (!p)
+ fatal_alloc();
+
+ return p;
+}
+
+char *strdup_fatal(const char *s)
+{
+ char *p = strdup(s);
+
+ if (!p)
+ fatal_alloc();
+
+ return p;
+}
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index 101d4799a009..e794ede64b2c 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -1,8 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdint.h>
+#include <string.h>
#include <time.h>
#include <sched.h>
+#include <stdbool.h>
+#include <stdlib.h>
/*
* '18446744073709551615\0'
@@ -12,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)) ; })
@@ -19,11 +44,12 @@
extern int config_debug;
void debug_msg(const char *fmt, ...);
void err_msg(const char *fmt, ...);
+void fatal(const char *fmt, ...);
long parse_seconds_duration(char *val);
void get_duration(time_t start_time, char *output, int output_size);
-int parse_cpu_list(char *cpu_list, char **monitored_cpus);
+char *parse_optional_arg(int argc, char **argv);
long long get_llong_from_str(char *start);
static inline void
@@ -59,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);
@@ -80,6 +116,13 @@ static inline int set_deepest_cpu_idle_state(unsigned int cpu, unsigned int stat
static inline int have_libcpupower_support(void) { return 0; }
#endif /* HAVE_LIBCPUPOWER_SUPPORT */
int auto_house_keeping(cpu_set_t *monitored_cpus);
+__attribute__((__warn_unused_result__)) int strtoi(const char *s, int *res);
#define ns_to_usf(x) (((double)x/1000))
#define ns_to_per(total, part) ((part * 100) / (double)total)
+
+enum result {
+ PASSED = EXIT_SUCCESS,
+ ERROR = EXIT_FAILURE,
+ FAILED, /* test hit the stop tracing condition */
+};
diff --git a/tools/tracing/rtla/tests/bpf/bpf_action_map.c b/tools/tracing/rtla/tests/bpf/bpf_action_map.c
new file mode 100644
index 000000000000..1686e0b858e6
--- /dev/null
+++ b/tools/tracing/rtla/tests/bpf/bpf_action_map.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_tracing.h>
+
+char LICENSE[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, unsigned int);
+ __type(value, unsigned long long);
+} rtla_test_map SEC(".maps");
+
+struct trace_event_raw_timerlat_sample;
+
+SEC("tp/timerlat_action")
+int action_handler(struct trace_event_raw_timerlat_sample *tp_args)
+{
+ unsigned int key = 0;
+ unsigned long long value = 42;
+
+ bpf_map_update_elem(&rtla_test_map, &key, &value, BPF_ANY);
+
+ return 0;
+}
diff --git a/tools/tracing/rtla/tests/engine.sh b/tools/tracing/rtla/tests/engine.sh
index 64d0446dc28e..ed261e07c6d9 100644
--- a/tools/tracing/rtla/tests/engine.sh
+++ b/tools/tracing/rtla/tests/engine.sh
@@ -8,27 +8,110 @@ test_begin() {
[ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT"
}
+reset_osnoise() {
+ # Reset osnoise options to default and remove any dangling instances created
+ # by improperly exited rtla runs.
+ pushd /sys/kernel/tracing >/dev/null || return 1
+
+ # Remove dangling instances created by previous rtla run
+ echo 0 > tracing_thresh
+ cd instances
+ for i in osnoise_top osnoise_hist timerlat_top timerlat_hist
+ do
+ [ ! -d "$i" ] && continue
+ rmdir "$i"
+ done
+
+ # Reset options to default
+ # Note: those are copied from the default values of osnoise_data
+ # in kernel/trace/trace_osnoise.c
+ cd ../osnoise
+ echo all > cpus
+ echo DEFAULTS > options
+ echo 1000000 > period_us
+ echo 0 > print_stack
+ echo 1000000 > runtime_us
+ echo 0 > stop_tracing_total_us
+ echo 0 > stop_tracing_us
+ echo 1000 > timerlat_period_us
+
+ popd >/dev/null
+}
+
check() {
+ test_name=$0
+ tested_command=$1
+ expected_exitcode=${3:-0}
+ expected_output=$4
+ unexpected_output=$5
# Simple check: run rtla with given arguments and test exit code.
# If TEST_COUNT is set, run the test. Otherwise, just count.
ctr=$(($ctr + 1))
if [ -n "$TEST_COUNT" ]
then
+ # Reset osnoise options before running test.
+ [ "$NO_RESET_OSNOISE" == 1 ] || reset_osnoise
# Run rtla; in case of failure, include its output as comment
# in the test results.
- result=$(stdbuf -oL $TIMEOUT "$RTLA" $2 2>&1); exitcode=$?
- if [ $exitcode -eq 0 ]
+ result=$(eval stdbuf -oL $TIMEOUT "$RTLA" $2 2>&1); exitcode=$?
+ failbuf=''
+ fail=0
+
+ # Test if the results matches if requested
+ if [ -n "$expected_output" ] && ! grep -qE "$expected_output" <<< "$result"
+ then
+ fail=1
+ failbuf+=$(printf "# Output match failed: \"%s\"" "$expected_output")
+ failbuf+=$'\n'
+ fi
+
+ if [ -n "$unexpected_output" ] && grep -qE "$unexpected_output" <<< "$result"
+ then
+ fail=1
+ failbuf+=$(printf "# Output non-match failed: \"%s\"" "$unexpected_output")
+ failbuf+=$'\n'
+ fi
+
+ if [ $exitcode -eq $expected_exitcode ] && [ $fail -eq 0 ]
then
echo "ok $ctr - $1"
else
- echo "not ok $ctr - $1"
# Add rtla output and exit code as comments in case of failure
+ echo "not ok $ctr - $1"
+ echo -n "$failbuf"
echo "$result" | col -b | while read line; do echo "# $line"; done
printf "#\n# exit code %s\n" $exitcode
fi
fi
}
+check_with_osnoise_options() {
+ # Do the same as "check", but with pre-set osnoise options.
+ # Note: rtla should reset the osnoise options, this is used to test
+ # if it indeed does so.
+ # Save original arguments
+ arg1=$1
+ arg2=$2
+ arg3=$3
+
+ # Apply osnoise options (if not dry run)
+ if [ -n "$TEST_COUNT" ]
+ then
+ [ "$NO_RESET_OSNOISE" == 1 ] || reset_osnoise
+ shift
+ shift
+ while shift
+ do
+ [ "$1" == "" ] && continue
+ option=$(echo $1 | cut -d '=' -f 1)
+ value=$(echo $1 | cut -d '=' -f 2)
+ echo "$value" > "/sys/kernel/tracing/osnoise/$option" || return 1
+ done
+ fi
+
+ NO_RESET_OSNOISE=1 check "$arg1" "$arg2" "$arg3"
+}
+
set_timeout() {
TIMEOUT="timeout -v -k 15s $1"
}
@@ -37,6 +120,14 @@ unset_timeout() {
unset TIMEOUT
}
+set_no_reset_osnoise() {
+ NO_RESET_OSNOISE=1
+}
+
+unset_no_reset_osnoise() {
+ unset NO_RESET_OSNOISE
+}
+
test_end() {
# If running without TEST_COUNT, tests are not actually run, just
# counted. In that case, re-run the test with the correct count.
diff --git a/tools/tracing/rtla/tests/hwnoise.t b/tools/tracing/rtla/tests/hwnoise.t
index bbed17580537..23ce250a6852 100644
--- a/tools/tracing/rtla/tests/hwnoise.t
+++ b/tools/tracing/rtla/tests/hwnoise.t
@@ -6,16 +6,17 @@ test_begin
set_timeout 2m
check "verify help page" \
- "hwnoise --help"
+ "hwnoise --help" 0 "summary of hardware-related noise"
check "detect noise higher than one microsecond" \
- "hwnoise -c 0 -T 1 -d 5s -q"
+ "hwnoise -c 0 -T 1 -d 5s -q" 0
check "set the automatic trace mode" \
- "hwnoise -a 5 -d 30s"
+ "hwnoise -a 5 -d 10s" 2 "osnoise hit stop tracing"
check "set scheduling param to the osnoise tracer threads" \
- "hwnoise -P F:1 -c 0 -r 900000 -d 1M -q"
+ "hwnoise -P F:1 -c 0 -r 900000 -d 10s -q"
check "stop the trace if a single sample is higher than 1 us" \
- "hwnoise -s 1 -T 1 -t -d 30s"
+ "hwnoise -s 1 -T 1 -t -d 10s" 2 "Saving trace to osnoise_trace.txt"
check "enable a trace event trigger" \
- "hwnoise -t -e osnoise:irq_noise trigger=\"hist:key=desc,duration:sort=desc,duration:vals=hitcount\" -d 1m"
+ "hwnoise -t -e osnoise:irq_noise --trigger=\"hist:key=desc,duration:sort=desc,duration:vals=hitcount\" -d 10s" \
+ 0 "Saving event osnoise:irq_noise hist to osnoise_irq_noise_hist.txt"
test_end
diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t
index 86596e547893..396334608920 100644
--- a/tools/tracing/rtla/tests/osnoise.t
+++ b/tools/tracing/rtla/tests/osnoise.t
@@ -6,14 +6,45 @@ test_begin
set_timeout 2m
check "verify help page" \
- "osnoise --help"
+ "osnoise --help" 0 "osnoise version"
check "verify the --priority/-P param" \
- "osnoise top -P F:1 -c 0 -r 900000 -d 1M -q"
+ "osnoise top -P F:1 -c 0 -r 900000 -d 10s -q -S 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh osnoise/ SCHED_FIFO 1\"" \
+ 2 "Priorities are set correctly"
check "verify the --stop/-s param" \
- "osnoise top -s 30 -T 1 -t"
+ "osnoise top -s 30 -T 1" 2 "osnoise hit stop tracing"
check "verify the --trace param" \
- "osnoise hist -s 30 -T 1 -t"
+ "osnoise hist -s 30 -T 1 -t" 2 "Saving trace to osnoise_trace.txt"
check "verify the --entries/-E param" \
- "osnoise hist -P F:1 -c 0 -r 900000 -d 1M -b 10 -E 25"
+ "osnoise hist -P F:1 -c 0 -r 900000 -d 10s -b 10 -E 25"
+
+# Test setting default period by putting an absurdly high period
+# and stopping on threshold.
+# If default period is not set, this will time out.
+check_with_osnoise_options "apply default period" \
+ "osnoise hist -s 1" 2 period_us=600000000
+
+# Actions tests
+check "trace output through -t with custom filename" \
+ "osnoise hist -S 2 -t custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
+check "trace output through --on-threshold trace" \
+ "osnoise hist -S 2 --on-threshold trace" 2 "^ Saving trace to osnoise_trace.txt$"
+check "trace output through --on-threshold trace with custom filename" \
+ "osnoise hist -S 2 --on-threshold trace,file=custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
+check "exec command" \
+ "osnoise hist -S 2 --on-threshold shell,command='echo TestOutput'" 2 "^TestOutput$"
+check "multiple actions" \
+ "osnoise hist -S 2 --on-threshold shell,command='echo -n 1' --on-threshold shell,command='echo 2'" 2 "^12$"
+check "hist stop at failed action" \
+ "osnoise hist -S 2 --on-threshold shell,command='echo -n 1; false' --on-threshold shell,command='echo -n 2'" 2 "^1# RTLA osnoise histogram$"
+check "top stop at failed action" \
+ "osnoise top -S 2 --on-threshold shell,command='echo -n abc; false' --on-threshold shell,command='echo -n defgh'" 2 "^abc" "defgh"
+check "hist with continue" \
+ "osnoise hist -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
+check "top with continue" \
+ "osnoise top -q -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
+check "hist with trace output at end" \
+ "osnoise hist -d 1s --on-end trace" 0 "^ Saving trace to osnoise_trace.txt$"
+check "top with trace output at end" \
+ "osnoise top -d 1s --on-end trace" 0 "^ Saving trace to osnoise_trace.txt$"
test_end
diff --git a/tools/tracing/rtla/tests/scripts/check-priority.sh b/tools/tracing/rtla/tests/scripts/check-priority.sh
new file mode 100755
index 000000000000..79b702a34a96
--- /dev/null
+++ b/tools/tracing/rtla/tests/scripts/check-priority.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+pids="$(pgrep ^$1)" || exit 1
+for pid in $pids
+do
+ chrt -p $pid | cut -d ':' -f 2 | head -n1 | grep "^ $2\$" >/dev/null
+ chrt -p $pid | cut -d ':' -f 2 | tail -n1 | grep "^ $3\$" >/dev/null
+done && echo "Priorities are set correctly"
diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t
index e86f40e5749e..fd4935fd7b49 100644
--- a/tools/tracing/rtla/tests/timerlat.t
+++ b/tools/tracing/rtla/tests/timerlat.t
@@ -4,24 +4,84 @@ source tests/engine.sh
test_begin
set_timeout 2m
+timerlat_sample_event='/sys/kernel/tracing/events/osnoise/timerlat_sample'
+if ldd $RTLA | grep libbpf >/dev/null && [ -d "$timerlat_sample_event" ]
+then
+ # rtla build with BPF and system supports BPF mode
+ no_bpf_options='0 1'
+else
+ no_bpf_options='1'
+fi
+
+# Do every test with and without BPF
+for option in $no_bpf_options
+do
+export RTLA_NO_BPF=$option
+
+# Basic tests
check "verify help page" \
- "timerlat --help"
+ "timerlat --help" 0 "timerlat version"
check "verify -s/--stack" \
- "timerlat top -s 3 -T 10 -t"
+ "timerlat top -s 3 -T 10 -t" 2 "Blocking thread stack trace"
check "verify -P/--priority" \
- "timerlat top -P F:1 -c 0 -d 1M -q"
+ "timerlat top -P F:1 -c 0 -d 10s -q -T 1 --on-threshold shell,command=\"tests/scripts/check-priority.sh timerlatu/ SCHED_FIFO 1\"" \
+ 2 "Priorities are set correctly"
check "test in nanoseconds" \
- "timerlat top -i 2 -c 0 -n -d 30s"
+ "timerlat top -i 2 -c 0 -n -d 10s" 2 "ns"
check "set the automatic trace mode" \
- "timerlat top -a 5 --dump-tasks"
+ "timerlat top -a 5" 2 "analyzing it"
+check "dump tasks" \
+ "timerlat top -a 5 --dump-tasks" 2 "Printing CPU tasks"
check "print the auto-analysis if hits the stop tracing condition" \
- "timerlat top --aa-only 5"
+ "timerlat top --aa-only 5" 2
check "disable auto-analysis" \
- "timerlat top -s 3 -T 10 -t --no-aa"
+ "timerlat top -s 3 -T 10 -t --no-aa" 2
check "verify -c/--cpus" \
- "timerlat hist -c 0 -d 30s"
+ "timerlat hist -c 0 -d 10s"
check "hist test in nanoseconds" \
- "timerlat hist -i 2 -c 0 -n -d 30s"
+ "timerlat hist -i 2 -c 0 -n -d 10s" 2 "ns"
+
+# Actions tests
+check "trace output through -t" \
+ "timerlat hist -T 2 -t" 2 "^ Saving trace to timerlat_trace.txt$"
+check "trace output through -t with custom filename" \
+ "timerlat hist -T 2 -t custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
+check "trace output through --on-threshold trace" \
+ "timerlat hist -T 2 --on-threshold trace" 2 "^ Saving trace to timerlat_trace.txt$"
+check "trace output through --on-threshold trace with custom filename" \
+ "timerlat hist -T 2 --on-threshold trace,file=custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
+check "exec command" \
+ "timerlat hist -T 2 --on-threshold shell,command='echo TestOutput'" 2 "^TestOutput$"
+check "multiple actions" \
+ "timerlat hist -T 2 --on-threshold shell,command='echo -n 1' --on-threshold shell,command='echo 2'" 2 "^12$"
+check "hist stop at failed action" \
+ "timerlat hist -T 2 --on-threshold shell,command='echo -n 1; false' --on-threshold shell,command='echo -n 2'" 2 "^1# RTLA timerlat histogram$"
+check "top stop at failed action" \
+ "timerlat top -T 2 --on-threshold shell,command='echo -n abc; false' --on-threshold shell,command='echo -n defgh'" 2 "^abc" "defgh"
+check "hist with continue" \
+ "timerlat hist -T 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
+check "top with continue" \
+ "timerlat top -q -T 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
+check "hist with trace output at end" \
+ "timerlat hist -d 1s --on-end trace" 0 "^ Saving trace to timerlat_trace.txt$"
+check "top with trace output at end" \
+ "timerlat top -d 1s --on-end trace" 0 "^ Saving trace to timerlat_trace.txt$"
+
+# BPF action program tests
+if [ "$option" -eq 0 ]
+then
+ # Test BPF action program properly in BPF mode
+ [ -z "$BPFTOOL" ] && BPFTOOL=bpftool
+ check "hist with BPF action program (BPF mode)" \
+ "timerlat hist -T 2 --bpf-action tests/bpf/bpf_action_map.o --on-threshold shell,command='$BPFTOOL map dump name rtla_test_map'" \
+ 2 '"value": 42'
+else
+ # Test BPF action program failure in non-BPF mode
+ check "hist with BPF action program (non-BPF mode)" \
+ "timerlat hist -T 2 --bpf-action tests/bpf/bpf_action_map.o" \
+ 1 "BPF actions are not supported in tracefs-only mode"
+fi
+done
test_end
diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build
new file mode 100644
index 000000000000..5f1e531ea8c9
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/Build
@@ -0,0 +1,2 @@
+unit_tests-y += unit_tests.o
+unit_tests-y +=../../src/utils.o
diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit
new file mode 100644
index 000000000000..2088c9cc3571
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/Makefile.unit
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+UNIT_TESTS := $(OUTPUT)unit_tests
+UNIT_TESTS_IN := $(UNIT_TESTS)-in.o
+
+$(UNIT_TESTS): $(UNIT_TESTS_IN)
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ -lcheck
+
+$(UNIT_TESTS_IN):
+ make $(build)=unit_tests
+
+unit-tests: FORCE
+ $(Q)if [ "$(feature-libcheck)" = "1" ]; then \
+ $(MAKE) $(UNIT_TESTS) && $(UNIT_TESTS); \
+ else \
+ echo "libcheck is missing, skipping unit tests. Please install check-devel/check"; \
+ fi
diff --git a/tools/tracing/rtla/tests/unit/unit_tests.c b/tools/tracing/rtla/tests/unit/unit_tests.c
new file mode 100644
index 000000000000..f3c6d89e3300
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/unit_tests.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <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;
+}