Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
/rtla
/rtla-static
/unit_tests
fixdep
feature
FEATURE-DUMP
*.skel.h
/libsubcmd/
+2
View File
@@ -0,0 +1,2 @@
rtla-y += src/
unit_tests-y += tests/unit/
+178
View File
@@ -0,0 +1,178 @@
# SPDX-License-Identifier: GPL-2.0-only
ifeq ($(srctree),)
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
endif
include $(srctree)/tools/scripts/Makefile.include
# O is an alias for OUTPUT
OUTPUT := $(O)
ifeq ($(OUTPUT),)
OUTPUT := $(CURDIR)
else
# subdir is used by the ../Makefile in $(call descend,)
ifneq ($(subdir),)
OUTPUT := $(OUTPUT)/$(subdir)
endif
endif
ifneq ($(patsubst %/,,$(lastword $(OUTPUT))),)
OUTPUT := $(OUTPUT)/
endif
RTLA := $(OUTPUT)rtla
RTLA_IN := $(RTLA)-in.o
LIBSUBCMD_DIR = $(srctree)/tools/lib/subcmd/
ifneq ($(OUTPUT),)
LIBSUBCMD_OUTPUT = $(abspath $(OUTPUT))/libsubcmd
else
LIBSUBCMD_OUTPUT = $(CURDIR)/libsubcmd
endif
LIBSUBCMD = $(LIBSUBCMD_OUTPUT)/libsubcmd.a
LIBSUBCMD_INCLUDES = $(LIBSUBCMD_OUTPUT)/include
LIBSUBCMD_MAKEFLAGS = O=$(LIBSUBCMD_OUTPUT) DESTDIR=$(LIBSUBCMD_OUTPUT) prefix= subdir=
TOOLS_INCLUDES = -I$(srctree)/tools/include
ifneq ($(OUTPUT),)
LIB_OUTPUT = $(abspath $(OUTPUT))/lib
else
LIB_OUTPUT = $(CURDIR)/lib
endif
LIB_CTYPE = $(LIB_OUTPUT)/ctype.o
LIB_CTYPE_SRC = $(srctree)/tools/lib/ctype.c
LIB_STRING = $(LIB_OUTPUT)/string.o
LIB_STRING_SRC = $(srctree)/tools/lib/string.c
LIB_STR_ERROR_R = $(LIB_OUTPUT)/str_error_r.o
LIB_STR_ERROR_R_SRC = $(srctree)/tools/lib/str_error_r.c
VERSION := $(shell sh -c "make -sC ../../.. kernelversion | grep -v make")
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
config := 1
ifdef MAKECMDGOALS
ifeq ($(filter-out $(NON_CONFIG_TARGETS),$(MAKECMDGOALS)),)
config := 0
endif
endif
ifeq ($(config),1)
include $(srctree)/tools/build/Makefile.feature
include Makefile.config
endif
CFLAGS += $(INCLUDES) $(LIB_INCLUDES) $(TOOLS_INCLUDES) -I$(LIBSUBCMD_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) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R)
$(eval LDFLAGS += -static)
$(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
rtla.%: fixdep FORCE
make -f $(srctree)/tools/build/Makefile.build dir=. $@
$(RTLA_IN): fixdep FORCE src/timerlat.skel.h $(LIBSUBCMD_INCLUDES)
make $(build)=rtla
$(LIBSUBCMD_OUTPUT):
$(Q)$(MKDIR) -p $@
$(LIBSUBCMD_INCLUDES): FORCE | $(LIBSUBCMD_OUTPUT)
$(Q)$(MAKE) -C $(LIBSUBCMD_DIR) $(LIBSUBCMD_MAKEFLAGS) \
install_headers
$(LIBSUBCMD): FORCE | $(LIBSUBCMD_OUTPUT)
$(Q)$(MAKE) -C $(LIBSUBCMD_DIR) $(LIBSUBCMD_MAKEFLAGS) \
$@
$(LIB_OUTPUT):
$(Q)$(MKDIR) -p $@
$(LIB_STR_ERROR_R): $(LIB_STR_ERROR_R_SRC) | $(LIB_OUTPUT)
$(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
$(LIB_STRING): $(LIB_STRING_SRC) | $(LIB_OUTPUT)
$(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
$(LIB_CTYPE): $(LIB_CTYPE_SRC) | $(LIB_OUTPUT)
$(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
libsubcmd-clean:
$(call QUIET_CLEAN, libsubcmd)
$(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT)
lib-clean:
$(call QUIET_CLEAN, lib)
$(Q)$(RM) -r -- $(LIB_OUTPUT)
clean: doc_clean fixdep-clean libsubcmd-clean lib-clean
$(call QUIET_CLEAN, rtla)
$(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
$(Q)rm -f rtla rtla-static fixdep FEATURE-DUMP rtla-*
$(Q)rm -rf feature
$(Q)rm -f src/timerlat.bpf.o src/timerlat.skel.h 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
@@ -0,0 +1,116 @@
# 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)"))
$(eval EXTLIBS += $(shell sh -c "$(PKG_CONFIG) --libs-only-l lib$(1)"))
endef
$(call feature_check,libtraceevent)
ifeq ($(feature-libtraceevent), 1)
$(call detected,CONFIG_LIBTRACEEVENT)
TEST = $(shell sh -c "$(PKG_CONFIG) --atleast-version $(LIBTRACEEVENT_MIN_VERSION) libtraceevent > /dev/null 2>&1 && echo y || echo n")
ifeq ($(TEST),n)
$(info libtraceevent version is too low, it must be at least $(LIBTRACEEVENT_MIN_VERSION))
STOP_ERROR := 1
endif
$(call lib_setup,traceevent)
else
STOP_ERROR := 1
$(info libtraceevent is missing. Please install libtraceevent-dev/libtraceevent-devel)
endif
$(call feature_check,libtracefs)
ifeq ($(feature-libtracefs), 1)
$(call detected,CONFIG_LIBTRACEFS)
TEST = $(shell sh -c "$(PKG_CONFIG) --atleast-version $(LIBTRACEFS_MIN_VERSION) libtracefs > /dev/null 2>&1 && echo y || echo n")
ifeq ($(TEST),n)
$(info libtracefs version is too low, it must be at least $(LIBTRACEFS_MIN_VERSION))
STOP_ERROR := 1
endif
$(call lib_setup,tracefs)
else
STOP_ERROR := 1
$(info libtracefs is missing. Please install libtracefs-dev/libtracefs-devel)
endif
$(call feature_check,libcpupower)
ifeq ($(feature-libcpupower), 1)
$(call detected,CONFIG_LIBCPUPOWER)
CFLAGS += -DHAVE_LIBCPUPOWER_SUPPORT
EXTLIBS += -lcpupower
else
$(info libcpupower is missing, building without --deepest-idle-state support.)
$(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
@@ -0,0 +1,93 @@
# SPDX-License-Identifier: GPL-2.0-only
define allow-override
$(if $(or $(findstring environment,$(origin $(1))),\
$(findstring command line,$(origin $(1)))),,\
$(eval $(1) = $(2)))
endef
# Allow setting CC and AR, or setting CROSS_COMPILE as a prefix.
$(call allow-override,CC,$(CROSS_COMPILE)gcc)
$(call allow-override,AR,$(CROSS_COMPILE)ar)
$(call allow-override,STRIP,$(CROSS_COMPILE)strip)
$(call allow-override,PKG_CONFIG,pkg-config)
$(call allow-override,LD_SO_CONF_PATH,/etc/ld.so.conf.d/)
$(call allow-override,LDCONFIG,ldconfig)
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
ifeq ($(CC),clang)
FOPTS := $(filter-out -flto=auto -ffat-lto-objects, $(FOPTS))
WOPTS := $(filter-out -Wno-maybe-uninitialized, $(WOPTS))
endif
CFLAGS := -g -DVERSION=\"$(VERSION)\" $(FOPTS) $(WOPTS) $(CFLAGS)
LDFLAGS := -ggdb $(LDFLAGS)
RM := rm -rf
LN := ln -s
INSTALL := install
MKDIR := mkdir
STRIP := strip
BINDIR := /usr/bin
CTAGS := ctags
ETAGS := ctags -e
.PHONY: install
install: doc_install
@$(MKDIR) -p $(DESTDIR)$(BINDIR)
$(call QUIET_INSTALL,rtla)$(INSTALL) $(RTLA) -m 755 $(DESTDIR)$(BINDIR)
@$(STRIP) $(DESTDIR)$(BINDIR)/rtla
@test ! -f $(DESTDIR)$(BINDIR)/osnoise || $(RM) $(DESTDIR)$(BINDIR)/osnoise
@$(LN) rtla $(DESTDIR)$(BINDIR)/osnoise
@test ! -f $(DESTDIR)$(BINDIR)/hwnoise || $(RM) $(DESTDIR)$(BINDIR)/hwnoise
@$(LN) -s rtla $(DESTDIR)$(BINDIR)/hwnoise
@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)
doc_clean:
$(MAKE) -C $(DOCSRC) clean
doc_install:
$(MAKE) -C $(DOCSRC) install
# This section is necessary to make the rtla tarball
NAME := rtla
DIRS := src
FILES := Makefile README.txt
CEXT := bz2
TARBALL := $(NAME)-$(VERSION).tar.$(CEXT)
TAROPTS := -cvjf $(TARBALL)
SRCTREE := $(or $(BUILD_SRC),$(CURDIR))
tarball: clean
$(RM) $(NAME)-$(VERSION) && $(MKDIR) $(NAME)-$(VERSION)
echo $(VERSION) > $(NAME)-$(VERSION)/VERSION
cp -r $(DIRS) $(FILES) $(NAME)-$(VERSION)
$(MKDIR) $(NAME)-$(VERSION)/Documentation/
cp -rp $(SRCTREE)/../../../Documentation/tools/$(NAME)/* $(NAME)-$(VERSION)/Documentation/
cp Makefile.standalone $(NAME)-$(VERSION)/Makefile
cp Makefile.$(NAME) $(NAME)-$(VERSION)/
tar $(TAROPTS) --exclude='*~' $(NAME)-$(VERSION)
$(RM) $(NAME)-$(VERSION)
.PHONY: tarball
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-only
VERSION := $(shell cat VERSION)
CFLAGS += $$($(PKG_CONFIG) --cflags libtracefs)
EXTLIBS += $$($(PKG_CONFIG) --libs libtracefs)
rtla:
include Makefile.rtla
SRC := $(wildcard src/*.c)
HDR := $(wildcard src/*.h)
OBJ := $(SRC:.c=.o)
DOCSRC := Documentation/
rtla: $(OBJ)
$(CC) -o rtla $(LDFLAGS) $(OBJ) $(LIBS) $(EXTLIBS)
$(info This is a deprecated method to compile RTLA, please compile from Linux kernel source)
.PHONY: clean tarball
clean: doc_clean
@test ! -f rtla || rm rtla
@test ! -f rtla-static || rm rtla-static
@test ! -f src/rtla.o || rm src/rtla.o
@test ! -f $(TARBALL) || rm -f $(TARBALL)
@rm -rf *~ $(OBJ) *.tar.$(CEXT)
+75
View File
@@ -0,0 +1,75 @@
RTLA: Real-Time Linux Analysis tools
The rtla meta-tool includes a set of commands that aims to analyze
the real-time properties of Linux. Instead of testing Linux as a black box,
rtla leverages kernel tracing capabilities to provide precise information
about the properties and root causes of unexpected results.
Installing RTLA
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.
For development, we suggest the following steps for compiling rtla:
$ git clone git://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git
$ cd libtraceevent/
$ make
$ sudo make install
$ cd ..
$ git clone git://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git
$ cd libtracefs/
$ make
$ sudo make install
$ cd ..
$ cd $libcpupower_src
$ make
$ sudo make install
$ cd $rtla_src
$ make
$ sudo make install
Running tests
RTLA has two test suites: a runtime test suite and a unit test suite.
The runtime test suite is available as "make check" (root required) and has
the following dependencies, in addition to RTLA build dependencies:
- Perl
- Test::Harness (libtest-harness-perl on Debian/Ubuntu, perl-Test-Harness on Fedora/RHEL)
- bash
- coreutils
- ldd
- util-linux
- procps(-ng)
- bpftool (if rtla is built against libbpf)
as well as the following required system configuration:
- CONFIG_OSNOISE_TRACER=y
- CONFIG_TIMERLAT_TRACER=y
- tracefs mounted and readable at /sys/kernel/tracing
The unit test suite is available as "make unit-tests" and has the following
dependencies:
- libcheck
Unlike the runtime test suite, root is not required to run unit tests, nor is
a tracefs/osnoise/timerlat-capable kernel required.
For further information, please refer to the rtla man page.
@@ -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;
}
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2024 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
#
# This is a sample code about how to use timerlat's timer by any workload
# so rtla can measure and provide auto-analysis for the overall latency (IOW
# the response time) for a task.
#
# Before running it, you need to dispatch timerlat with -U option in a terminal.
# Then # run this script pinned to a CPU on another terminal. For example:
#
# timerlat_load.py 1 -p 95
#
# The "Timerlat IRQ" is the IRQ latency, The thread latency is the latency
# for the python process to get the CPU. The Ret from user Timer Latency is
# the overall latency. In other words, it is the response time for that
# activation.
#
# This is just an example, the load is reading 20MB of data from /dev/full
# It is in python because it is easy to read :-)
import argparse
import sys
import os
parser = argparse.ArgumentParser(description='user-space timerlat thread in Python')
parser.add_argument("cpu", type=int, help='CPU to run timerlat thread')
parser.add_argument("-p", "--prio", type=int, help='FIFO priority')
args = parser.parse_args()
try:
affinity_mask = {args.cpu}
os.sched_setaffinity(0, affinity_mask)
except Exception as e:
print(f"Error setting affinity: {e}")
sys.exit(1)
if args.prio:
try:
param = os.sched_param(args.prio)
os.sched_setscheduler(0, os.SCHED_FIFO, param)
except Exception as e:
print(f"Error setting priority: {e}")
sys.exit(1)
try:
timerlat_path = f"/sys/kernel/tracing/osnoise/per_cpu/cpu{args.cpu}/timerlat_fd"
timerlat_fd = open(timerlat_path, 'r')
except PermissionError:
print("Permission denied. Please check your access rights.")
sys.exit(1)
except OSError:
print("Error opening timerlat fd, did you run timerlat -U?")
sys.exit(1)
try:
data_fd = open("/dev/full", 'r')
except Exception as e:
print(f"Error opening data fd: {e}")
sys.exit(1)
while True:
try:
timerlat_fd.read(1)
data_fd.read(20 * 1024 * 1024)
except KeyboardInterrupt:
print("Leaving")
break
except IOError as e:
print(f"I/O error occurred: {e}")
break
except Exception as e:
print(f"Unexpected error: {e}")
break
timerlat_fd.close()
data_fd.close()
+14
View File
@@ -0,0 +1,14 @@
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
rtla-y += timerlat.o
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 += cli.o
@@ -0,0 +1,286 @@
// 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;
self->continue_flag = false;
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;
}
@@ -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);
+539
View File
@@ -0,0 +1,539 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/compiler.h>
#define RTLA_ALLOW_CLI_P_H
#include "cli_p.h"
static const char * const osnoise_top_usage[] = {
"rtla osnoise [top] [<options>] [-h|--help]",
NULL,
};
static const char * const osnoise_hist_usage[] = {
"rtla osnoise hist [<options>] [-h|--help]",
NULL,
};
static const char * const timerlat_top_usage[] = {
"rtla timerlat [top] [<options>] [-h|--help]",
NULL,
};
static const char * const timerlat_hist_usage[] = {
"rtla timerlat hist [<options>] [-h|--help]",
NULL,
};
static const char * const hwnoise_usage[] = {
"rtla hwnoise [<options>] [-h|--help]",
NULL,
};
static const int common_parse_options_flags = PARSE_OPT_OPTARG_ALLOW_NEXT;
bool in_unit_test;
/*
* osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
*/
struct common_params *osnoise_top_parse_args(int argc, char **argv)
{
struct osnoise_params *params;
struct osnoise_cb_data cb_data;
const char * const *usage;
params = calloc_fatal(1, sizeof(*params));
cb_data.params = params;
cb_data.trace_output = NULL;
if (strcmp(argv[0], "hwnoise") == 0) {
params->mode = MODE_HWNOISE;
/*
* Reduce CPU usage for 75% to avoid killing the system.
*/
params->runtime = 750000;
params->period = 1000000;
usage = hwnoise_usage;
} else {
usage = osnoise_top_usage;
}
const struct option osnoise_top_options[] = {
OPT_GROUP("Tracing Options:"),
OSNOISE_OPT_PERIOD,
OSNOISE_OPT_RUNTIME,
RTLA_OPT_STOP('s', "stop", "single sample"),
RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"),
OSNOISE_OPT_THRESHOLD,
RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb),
OPT_GROUP("Event Configuration:"),
RTLA_OPT_EVENT,
RTLA_OPT_FILTER,
RTLA_OPT_TRIGGER,
OPT_GROUP("CPU Configuration:"),
RTLA_OPT_CPUS,
RTLA_OPT_HOUSEKEEPING,
OPT_GROUP("Thread Configuration:"),
RTLA_OPT_PRIORITY,
RTLA_OPT_CGROUP,
OPT_GROUP("Output:"),
RTLA_OPT_QUIET,
OPT_GROUP("System Tuning:"),
RTLA_OPT_TRACE_BUFFER_SIZE,
RTLA_OPT_WARM_UP,
OPT_GROUP("Auto Analysis and Actions:"),
RTLA_OPT_AUTO(opt_osnoise_auto_cb),
RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb),
RTLA_OPT_ON_END(opt_osnoise_on_end_cb),
OPT_GROUP("General:"),
RTLA_OPT_DURATION,
RTLA_OPT_DEBUG,
OPT_END(),
};
actions_init(&params->common.threshold_actions);
actions_init(&params->common.end_actions);
argc = parse_options(argc, (const char **)argv,
osnoise_top_options,
usage,
common_parse_options_flags);
if (argc < 0)
return NULL;
if (cb_data.trace_output)
actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
if (geteuid() && !in_unit_test)
fatal("osnoise needs root permission");
return &params->common;
}
/*
* osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters
*/
struct common_params *osnoise_hist_parse_args(int argc, char **argv)
{
struct osnoise_params *params;
struct osnoise_cb_data cb_data;
params = calloc_fatal(1, sizeof(*params));
cb_data.params = params;
cb_data.trace_output = NULL;
const struct option osnoise_hist_options[] = {
OPT_GROUP("Tracing Options:"),
OSNOISE_OPT_PERIOD,
OSNOISE_OPT_RUNTIME,
RTLA_OPT_STOP('s', "stop", "single sample"),
RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"),
OSNOISE_OPT_THRESHOLD,
RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb),
OPT_GROUP("Event Configuration:"),
RTLA_OPT_EVENT,
RTLA_OPT_FILTER,
RTLA_OPT_TRIGGER,
OPT_GROUP("CPU Configuration:"),
RTLA_OPT_CPUS,
RTLA_OPT_HOUSEKEEPING,
OPT_GROUP("Thread Configuration:"),
RTLA_OPT_PRIORITY,
RTLA_OPT_CGROUP,
OPT_GROUP("Histogram Options:"),
HIST_OPT_BUCKET_SIZE,
HIST_OPT_ENTRIES,
HIST_OPT_NO_HEADER,
HIST_OPT_NO_SUMMARY,
HIST_OPT_NO_INDEX,
HIST_OPT_WITH_ZEROS,
OPT_GROUP("System Tuning:"),
RTLA_OPT_TRACE_BUFFER_SIZE,
RTLA_OPT_WARM_UP,
OPT_GROUP("Auto Analysis and Actions:"),
RTLA_OPT_AUTO(opt_osnoise_auto_cb),
RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb),
RTLA_OPT_ON_END(opt_osnoise_on_end_cb),
OPT_GROUP("General:"),
RTLA_OPT_DURATION,
RTLA_OPT_DEBUG,
OPT_END(),
};
actions_init(&params->common.threshold_actions);
actions_init(&params->common.end_actions);
/* display data in microseconds */
params->common.output_divisor = 1000;
params->common.hist.bucket_size = 1;
params->common.hist.entries = 256;
argc = parse_options(argc, (const char **)argv,
osnoise_hist_options, osnoise_hist_usage,
common_parse_options_flags);
if (argc < 0)
return NULL;
if (cb_data.trace_output)
actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
if (geteuid() && !in_unit_test)
fatal("rtla needs root permission");
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->common;
}
struct common_params *timerlat_top_parse_args(int argc, char **argv)
{
struct timerlat_params *params;
struct timerlat_cb_data cb_data;
params = calloc_fatal(1, sizeof(*params));
cb_data.params = params;
cb_data.trace_output = NULL;
const struct option timerlat_top_options[] = {
OPT_GROUP("Tracing Options:"),
TIMERLAT_OPT_PERIOD,
RTLA_OPT_STOP('i', "irq", "irq latency"),
RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"),
TIMERLAT_OPT_STACK,
RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb),
OPT_GROUP("Event Configuration:"),
RTLA_OPT_EVENT,
RTLA_OPT_FILTER,
RTLA_OPT_TRIGGER,
OPT_GROUP("CPU Configuration:"),
RTLA_OPT_CPUS,
RTLA_OPT_HOUSEKEEPING,
OPT_GROUP("Thread Configuration:"),
RTLA_OPT_PRIORITY,
RTLA_OPT_CGROUP,
RTLA_OPT_USER_THREADS,
RTLA_OPT_KERNEL_THREADS,
RTLA_OPT_USER_LOAD,
TIMERLAT_OPT_ALIGNED,
OPT_GROUP("Output:"),
TIMERLAT_OPT_NANO,
RTLA_OPT_QUIET,
OPT_GROUP("System Tuning:"),
TIMERLAT_OPT_DMA_LATENCY,
TIMERLAT_OPT_DEEPEST_IDLE_STATE,
RTLA_OPT_TRACE_BUFFER_SIZE,
RTLA_OPT_WARM_UP,
OPT_GROUP("Auto Analysis and Actions:"),
RTLA_OPT_AUTO(opt_timerlat_auto_cb),
TIMERLAT_OPT_AA_ONLY,
TIMERLAT_OPT_NO_AA,
TIMERLAT_OPT_DUMPS_TASKS,
RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb),
RTLA_OPT_ON_END(opt_timerlat_on_end_cb),
TIMERLAT_OPT_BPF_ACTION,
TIMERLAT_OPT_STACK_FORMAT,
OPT_GROUP("General:"),
RTLA_OPT_DURATION,
RTLA_OPT_DEBUG,
OPT_END(),
};
actions_init(&params->common.threshold_actions);
actions_init(&params->common.end_actions);
/* disabled by default */
params->dma_latency = -1;
params->deepest_idle_state = -2;
/* display data in microseconds */
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;
argc = parse_options(argc, (const char **)argv,
timerlat_top_options, timerlat_top_usage,
common_parse_options_flags);
if (argc < 0)
return NULL;
if (cb_data.trace_output)
actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
if (geteuid() && !in_unit_test)
fatal("rtla needs root permission");
/*
* Auto analysis only happens if stop tracing, thus:
*/
if (!params->common.stop_us && !params->common.stop_total_us)
params->no_aa = 1;
if (params->no_aa && params->common.aa_only)
fatal("--no-aa and --aa-only are mutually exclusive!");
if (params->common.kernel_workload && params->common.user_workload)
fatal("--kernel-threads and --user-threads are mutually exclusive!");
/*
* 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;
}
struct common_params *timerlat_hist_parse_args(int argc, char **argv)
{
struct timerlat_params *params;
struct timerlat_cb_data cb_data;
params = calloc_fatal(1, sizeof(*params));
cb_data.params = params;
cb_data.trace_output = NULL;
const struct option timerlat_hist_options[] = {
OPT_GROUP("Tracing Options:"),
TIMERLAT_OPT_PERIOD,
RTLA_OPT_STOP('i', "irq", "irq latency"),
RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"),
TIMERLAT_OPT_STACK,
RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb),
OPT_GROUP("Event Configuration:"),
RTLA_OPT_EVENT,
RTLA_OPT_FILTER,
RTLA_OPT_TRIGGER,
OPT_GROUP("CPU Configuration:"),
RTLA_OPT_CPUS,
RTLA_OPT_HOUSEKEEPING,
OPT_GROUP("Thread Configuration:"),
RTLA_OPT_PRIORITY,
RTLA_OPT_CGROUP,
RTLA_OPT_USER_THREADS,
RTLA_OPT_KERNEL_THREADS,
RTLA_OPT_USER_LOAD,
TIMERLAT_OPT_ALIGNED,
OPT_GROUP("Histogram Options:"),
HIST_OPT_BUCKET_SIZE,
HIST_OPT_ENTRIES,
HIST_OPT_NO_IRQ,
HIST_OPT_NO_THREAD,
HIST_OPT_NO_HEADER,
HIST_OPT_NO_SUMMARY,
HIST_OPT_NO_INDEX,
HIST_OPT_WITH_ZEROS,
OPT_GROUP("Output:"),
TIMERLAT_OPT_NANO,
OPT_GROUP("System Tuning:"),
TIMERLAT_OPT_DMA_LATENCY,
TIMERLAT_OPT_DEEPEST_IDLE_STATE,
RTLA_OPT_TRACE_BUFFER_SIZE,
RTLA_OPT_WARM_UP,
OPT_GROUP("Auto Analysis and Actions:"),
RTLA_OPT_AUTO(opt_timerlat_auto_cb),
TIMERLAT_OPT_NO_AA,
TIMERLAT_OPT_DUMPS_TASKS,
RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb),
RTLA_OPT_ON_END(opt_timerlat_on_end_cb),
TIMERLAT_OPT_BPF_ACTION,
TIMERLAT_OPT_STACK_FORMAT,
OPT_GROUP("General:"),
RTLA_OPT_DURATION,
RTLA_OPT_DEBUG,
OPT_END(),
};
actions_init(&params->common.threshold_actions);
actions_init(&params->common.end_actions);
/* disabled by default */
params->dma_latency = -1;
/* disabled by default */
params->deepest_idle_state = -2;
/* display data in microseconds */
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;
argc = parse_options(argc, (const char **)argv,
timerlat_hist_options, timerlat_hist_usage,
common_parse_options_flags);
if (argc < 0)
return NULL;
if (cb_data.trace_output)
actions_add_trace_output(&params->common.threshold_actions, cb_data.trace_output);
if (geteuid() && !in_unit_test)
fatal("rtla needs root permission");
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->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->common.stop_us && !params->common.stop_total_us)
params->no_aa = 1;
if (params->common.kernel_workload && params->common.user_workload)
fatal("--kernel-threads and --user-threads are mutually exclusive!");
/*
* 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;
}
/*
* rtla_usage - print rtla usage
*/
__noreturn static void rtla_usage(int err)
{
int i;
static const char * const msg[] = {
"",
"rtla version " VERSION,
"",
" usage: rtla COMMAND ...",
"",
" commands:",
" osnoise - gives information about the operating system noise (osnoise)",
" hwnoise - gives information about hardware-related noise",
" timerlat - measures the timer irq and thread latency",
"",
NULL,
};
for (i = 0; msg[i]; i++)
fprintf(stderr, "%s\n", msg[i]);
exit(err);
}
/*
* run_tool_command - try to run a rtla tool command
*
* It returns 0 if it fails. The tool's main will generally not
* return as they should call exit().
*/
int run_tool_command(int argc, char **argv, int start_position)
{
if (strcmp(argv[start_position], "osnoise") == 0) {
osnoise_main(argc-start_position, &argv[start_position]);
goto ran;
} else if (strcmp(argv[start_position], "hwnoise") == 0) {
hwnoise_main(argc-start_position, &argv[start_position]);
goto ran;
} else if (strcmp(argv[start_position], "timerlat") == 0) {
timerlat_main(argc-start_position, &argv[start_position]);
goto ran;
}
return 0;
ran:
return 1;
}
/* Set main as weak to allow overriding it for building unit test binary */
#pragma weak main
int main(int argc, char *argv[])
{
int retval;
/* is it an alias? */
retval = run_tool_command(argc, argv, 0);
if (retval)
exit(0);
if (argc < 2)
goto usage;
if (strcmp(argv[1], "-h") == 0)
rtla_usage(129);
else if (strcmp(argv[1], "--help") == 0)
rtla_usage(129);
retval = run_tool_command(argc, argv, 1);
if (retval)
exit(0);
usage:
rtla_usage(129);
}
+9
View File
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#pragma once
struct common_params *osnoise_top_parse_args(int argc, char **argv);
struct common_params *osnoise_hist_parse_args(int argc, char **argv);
struct common_params *timerlat_top_parse_args(int argc, char **argv);
struct common_params *timerlat_hist_parse_args(int argc, char **argv);
extern bool in_unit_test;
+687
View File
@@ -0,0 +1,687 @@
/* SPDX-License-Identifier: GPL-2.0 */
#pragma once
#ifndef RTLA_ALLOW_CLI_P_H
#error "Private header file included outside of cli.c module"
#endif
#include <linux/kernel.h>
#include <subcmd/parse-options.h>
#include "cli.h"
#include "osnoise.h"
#include "timerlat.h"
struct osnoise_cb_data {
struct osnoise_params *params;
char *trace_output;
};
struct timerlat_cb_data {
struct timerlat_params *params;
char *trace_output;
};
/*
* Macros for command line options common to all tools
*
* Note: Some of the options are common to both timerlat and osnoise, but
* have a slightly different meaning. Such options take additional arguments
* that have to be provided by the *_parse_args() function of the corresponding
* tool.
*
* All macros defined here assume the presence of a params variable of
* the corresponding tool type (i.e struct timerlat_params or struct osnoise_params)
* and a cb_data variable of the matching type.
*/
#define RTLA_OPT_STOP(short, long, name) OPT_CALLBACK_FLAG(short, long, \
&params->common.stop_us, \
"us", \
"stop trace if " name " is higher than the argument in us", \
opt_llong_callback, PARSE_OPT_NOAUTONEG)
#define RTLA_OPT_STOP_TOTAL(short, long, name) OPT_CALLBACK_FLAG(short, long, \
&params->common.stop_total_us, \
"us", \
"stop trace if " name " is higher than the argument in us", \
opt_llong_callback, PARSE_OPT_NOAUTONEG)
#define RTLA_OPT_TRACE_OUTPUT(tracer, cb) OPT_CALLBACK_OPTARG('t', "trace", \
(const char **)&cb_data.trace_output, \
tracer "_trace.txt", \
"[file]", \
"save the stopped trace to [file|" tracer "_trace.txt]", \
cb)
#define RTLA_OPT_CPUS OPT_CALLBACK('c', "cpus", &params->common, \
"cpu-list", \
"run the tracer only on the given cpus", \
opt_cpus_cb)
#define RTLA_OPT_CGROUP OPT_CALLBACK_OPTARG('C', "cgroup", &params->common, \
"[cgroup_name]", NULL, \
"set cgroup, no argument means rtla's cgroup will be inherited", \
opt_cgroup_cb)
#define RTLA_OPT_USER_THREADS OPT_CALLBACK_NOOPT('u', "user-threads", params, NULL, \
"use rtla user-space threads instead of kernel-space timerlat threads", \
opt_user_threads_cb)
#define RTLA_OPT_KERNEL_THREADS OPT_BOOLEAN('k', "kernel-threads", \
&params->common.kernel_workload, \
"use timerlat kernel-space threads instead of rtla user-space threads")
#define RTLA_OPT_USER_LOAD OPT_BOOLEAN('U', "user-load", &params->common.user_data, \
"enable timerlat for user-defined user-space workload")
#define RTLA_OPT_DURATION OPT_CALLBACK('d', "duration", &params->common, \
"time[s|m|h|d]", \
"set the duration of the session", \
opt_duration_cb)
#define RTLA_OPT_EVENT OPT_CALLBACK('e', "event", &params->common.events, \
"sys:event", \
"enable the <sys:event> in the trace instance, multiple -e are allowed", \
opt_event_cb)
#define RTLA_OPT_HOUSEKEEPING OPT_CALLBACK('H', "house-keeping", &params->common, \
"cpu-list", \
"run rtla control threads only on the given cpus", \
opt_housekeeping_cb)
#define RTLA_OPT_PRIORITY OPT_CALLBACK('P', "priority", &params->common, \
"o:prio|r:prio|f:prio|d:runtime:period", \
"set scheduling parameters", \
opt_priority_cb)
#define RTLA_OPT_TRIGGER OPT_CALLBACK(0, "trigger", &params->common.events, \
"trigger", \
"enable a trace event trigger to the previous -e event", \
opt_trigger_cb)
#define RTLA_OPT_FILTER OPT_CALLBACK(0, "filter", &params->common.events, \
"filter", \
"enable a trace event filter to the previous -e event", \
opt_filter_cb)
#define RTLA_OPT_QUIET OPT_BOOLEAN('q', "quiet", &params->common.quiet, \
"print only a summary at the end")
#define RTLA_OPT_TRACE_BUFFER_SIZE OPT_CALLBACK(0, "trace-buffer-size", \
&params->common.buffer_size, "kB", \
"set the per-cpu trace buffer size in kB", \
opt_int_callback)
#define RTLA_OPT_WARM_UP OPT_CALLBACK(0, "warm-up", &params->common.warmup, "s", \
"let the workload run for s seconds before collecting data", \
opt_int_callback)
#define RTLA_OPT_AUTO(cb) OPT_CALLBACK('a', "auto", &cb_data, "us", \
"set automatic trace mode, stopping the session if argument in us sample is hit", \
cb)
#define RTLA_OPT_ON_THRESHOLD(threshold, cb) OPT_CALLBACK(0, "on-threshold", \
&params->common.threshold_actions, \
"action", \
"define action to be executed at " threshold " threshold, multiple are allowed", \
cb)
#define RTLA_OPT_ON_END(cb) OPT_CALLBACK(0, "on-end", &params->common.end_actions, \
"action", \
"define action to be executed at measurement end, multiple are allowed", \
cb)
#define RTLA_OPT_DEBUG OPT_BOOLEAN('D', "debug", &config_debug, \
"print debug info")
/*
* Common callback functions for command line options
*/
static int opt_llong_callback(const struct option *opt, const char *arg, int unset)
{
long long *value = opt->value;
if (unset || !arg)
return -1;
*value = get_llong_from_str((char *)arg);
return 0;
}
static int opt_int_callback(const struct option *opt, const char *arg, int unset)
{
int *value = opt->value;
if (unset || !arg)
return -1;
if (strtoi(arg, value))
return -1;
return 0;
}
static int opt_cpus_cb(const struct option *opt, const char *arg, int unset)
{
struct common_params *params = opt->value;
int retval;
if (unset || !arg)
return -1;
retval = parse_cpu_set((char *)arg, &params->monitored_cpus);
if (retval)
fatal("Invalid -c cpu list");
params->cpus = (char *)arg;
return 0;
}
static int opt_cgroup_cb(const struct option *opt, const char *arg, int unset)
{
struct common_params *params = opt->value;
if (unset)
return -1;
params->cgroup = 1;
params->cgroup_name = (char *)arg;
if (params->cgroup_name && params->cgroup_name[0] == '=')
/* Allow -C=<cgroup_name> next to -C[ ]<cgroup_name> */
++params->cgroup_name;
return 0;
}
static int opt_duration_cb(const struct option *opt, const char *arg, int unset)
{
struct common_params *params = opt->value;
if (unset || !arg)
return -1;
params->duration = parse_seconds_duration((char *)arg);
if (!params->duration)
fatal("Invalid -d duration");
return 0;
}
static int opt_event_cb(const struct option *opt, const char *arg, int unset)
{
struct trace_events **events = opt->value;
struct trace_events *tevent;
if (unset || !arg)
return -1;
tevent = trace_event_alloc((char *)arg);
if (!tevent)
fatal("Error alloc trace event");
if (*events)
tevent->next = *events;
*events = tevent;
return 0;
}
static int opt_housekeeping_cb(const struct option *opt, const char *arg, int unset)
{
struct common_params *params = opt->value;
int retval;
if (unset || !arg)
return -1;
params->hk_cpus = 1;
retval = parse_cpu_set((char *)arg, &params->hk_cpu_set);
if (retval)
fatal("Error parsing house keeping CPUs");
return 0;
}
static int opt_priority_cb(const struct option *opt, const char *arg, int unset)
{
struct common_params *params = opt->value;
int retval;
if (unset || !arg)
return -1;
retval = parse_prio((char *)arg, &params->sched_param);
if (retval == -1)
fatal("Invalid -P priority");
params->set_sched = 1;
return 0;
}
static int opt_trigger_cb(const struct option *opt, const char *arg, int unset)
{
struct trace_events **events = opt->value;
if (unset || !arg)
return -1;
if (!*events)
fatal("--trigger requires a previous -e");
trace_event_add_trigger(*events, (char *)arg);
return 0;
}
static int opt_filter_cb(const struct option *opt, const char *arg, int unset)
{
struct trace_events **events = opt->value;
if (unset || !arg)
return -1;
if (!*events)
fatal("--filter requires a previous -e");
trace_event_add_filter(*events, (char *)arg);
return 0;
}
/*
* Macros for command line options specific to osnoise
*/
#define OSNOISE_OPT_PERIOD OPT_CALLBACK('p', "period", &params->period, "us", \
"osnoise period in us", \
opt_osnoise_period_cb)
#define OSNOISE_OPT_RUNTIME OPT_CALLBACK('r', "runtime", &params->runtime, "us", \
"osnoise runtime in us", \
opt_osnoise_runtime_cb)
#define OSNOISE_OPT_THRESHOLD OPT_CALLBACK('T', "threshold", &params->threshold, "us", \
"the minimum delta to be considered a noise", \
opt_llong_callback)
/*
* Callback functions for command line options for osnoise tools
*/
static int opt_osnoise_auto_cb(const struct option *opt, const char *arg, int unset)
{
struct osnoise_cb_data *cb_data = opt->value;
struct osnoise_params *params = cb_data->params;
long long auto_thresh;
if (unset || !arg)
return -1;
auto_thresh = get_llong_from_str((char *)arg);
params->common.stop_us = auto_thresh;
params->threshold = 1;
if (!cb_data->trace_output)
cb_data->trace_output = "osnoise_trace.txt";
return 0;
}
static int opt_osnoise_period_cb(const struct option *opt, const char *arg, int unset)
{
unsigned long long *period = opt->value;
if (unset || !arg)
return -1;
*period = get_llong_from_str((char *)arg);
if (*period > 10000000)
fatal("Period longer than 10 s");
return 0;
}
static int opt_osnoise_runtime_cb(const struct option *opt, const char *arg, int unset)
{
unsigned long long *runtime = opt->value;
if (unset || !arg)
return -1;
*runtime = get_llong_from_str((char *)arg);
if (*runtime < 100)
fatal("Runtime shorter than 100 us");
return 0;
}
static int opt_osnoise_trace_output_cb(const struct option *opt, const char *arg, int unset)
{
const char **trace_output = opt->value;
if (unset)
return -1;
if (!arg) {
*trace_output = "osnoise_trace.txt";
} else {
*trace_output = (char *)arg;
if (*trace_output && (*trace_output)[0] == '=')
/* Allow -t=<trace_output> next to -t[ ]<trace_output> */
++*trace_output;
}
return 0;
}
static int opt_osnoise_on_threshold_cb(const struct option *opt, const char *arg, int unset)
{
struct actions *actions = opt->value;
int retval;
if (unset || !arg)
return -1;
retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt");
if (retval)
fatal("Invalid action %s", arg);
return 0;
}
static int opt_osnoise_on_end_cb(const struct option *opt, const char *arg, int unset)
{
struct actions *actions = opt->value;
int retval;
if (unset || !arg)
return -1;
retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt");
if (retval)
fatal("Invalid action %s", arg);
return 0;
}
/*
* Macros for command line options specific to timerlat
*/
#define TIMERLAT_OPT_PERIOD OPT_CALLBACK('p', "period", &params->timerlat_period_us, "us", \
"timerlat period in us", \
opt_timerlat_period_cb)
#define TIMERLAT_OPT_STACK OPT_CALLBACK('s', "stack", &params->print_stack, "us", \
"save the stack trace at the IRQ if a thread latency is higher than the argument in us", \
opt_llong_callback)
#define TIMERLAT_OPT_NANO OPT_CALLBACK_NOOPT('n', "nano", params, NULL, \
"display data in nanoseconds", \
opt_nano_cb)
#define TIMERLAT_OPT_DMA_LATENCY OPT_CALLBACK(0, "dma-latency", &params->dma_latency, "us", \
"set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency", \
opt_dma_latency_cb)
#define TIMERLAT_OPT_DEEPEST_IDLE_STATE OPT_CALLBACK(0, "deepest-idle-state", \
&params->deepest_idle_state, "n", \
"only go down to idle state n on cpus used by timerlat to reduce exit from idle latency", \
opt_int_callback)
#define TIMERLAT_OPT_AA_ONLY OPT_CALLBACK(0, "aa-only", params, "us", \
"stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)", \
opt_aa_only_cb)
#define TIMERLAT_OPT_NO_AA OPT_BOOLEAN(0, "no-aa", &params->no_aa, \
"disable auto-analysis, reducing rtla timerlat cpu usage")
#define TIMERLAT_OPT_DUMPS_TASKS OPT_BOOLEAN(0, "dump-tasks", &params->dump_tasks, \
"prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)")
#define TIMERLAT_OPT_BPF_ACTION OPT_STRING(0, "bpf-action", &params->bpf_action_program, \
"program", \
"load and execute BPF program when latency threshold is exceeded")
#define TIMERLAT_OPT_STACK_FORMAT OPT_CALLBACK(0, "stack-format", &params->stack_format, "format", \
"set the stack format (truncate, skip, full)", \
opt_stack_format_cb)
#define TIMERLAT_OPT_ALIGNED OPT_CALLBACK('A', "aligned", params, "us", \
"align thread wakeups to a specific offset", \
opt_timerlat_align_cb)
/*
* Callback functions for command line options for timerlat tools
*/
static int opt_timerlat_period_cb(const struct option *opt, const char *arg, int unset)
{
long long *period = opt->value;
if (unset || !arg)
return -1;
*period = get_llong_from_str((char *)arg);
if (*period > 1000000)
fatal("Period longer than 1 s");
return 0;
}
static int opt_timerlat_auto_cb(const struct option *opt, const char *arg, int unset)
{
struct timerlat_cb_data *cb_data = opt->value;
struct timerlat_params *params = cb_data->params;
long long auto_thresh;
if (unset || !arg)
return -1;
auto_thresh = get_llong_from_str((char *)arg);
params->common.stop_total_us = auto_thresh;
params->common.stop_us = auto_thresh;
params->print_stack = auto_thresh;
if (!cb_data->trace_output)
cb_data->trace_output = "timerlat_trace.txt";
return 0;
}
static int opt_dma_latency_cb(const struct option *opt, const char *arg, int unset)
{
int *dma_latency = opt->value;
int retval;
if (unset || !arg)
return -1;
retval = strtoi((char *)arg, dma_latency);
if (retval)
fatal("Invalid -dma-latency %s", arg);
if (*dma_latency < 0 || *dma_latency > 10000)
fatal("--dma-latency needs to be >= 0 and <= 10000");
return 0;
}
static int opt_aa_only_cb(const struct option *opt, const char *arg, int unset)
{
struct timerlat_params *params = opt->value;
long long auto_thresh;
if (unset || !arg)
return -1;
auto_thresh = get_llong_from_str((char *)arg);
params->common.stop_total_us = auto_thresh;
params->common.stop_us = auto_thresh;
params->print_stack = auto_thresh;
params->common.aa_only = 1;
return 0;
}
static int opt_timerlat_trace_output_cb(const struct option *opt, const char *arg, int unset)
{
const char **trace_output = opt->value;
if (unset)
return -1;
if (!arg) {
*trace_output = "timerlat_trace.txt";
} else {
*trace_output = (char *)arg;
if (*trace_output && (*trace_output)[0] == '=')
/* Allow -t=<trace_output> next to -t[ ]<trace_output> */
++*trace_output;
}
return 0;
}
static int opt_timerlat_on_threshold_cb(const struct option *opt, const char *arg, int unset)
{
struct actions *actions = opt->value;
int retval;
if (unset || !arg)
return -1;
retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt");
if (retval)
fatal("Invalid action %s", arg);
return 0;
}
static int opt_timerlat_on_end_cb(const struct option *opt, const char *arg, int unset)
{
struct actions *actions = opt->value;
int retval;
if (unset || !arg)
return -1;
retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt");
if (retval)
fatal("Invalid action %s", arg);
return 0;
}
static int opt_user_threads_cb(const struct option *opt, const char *arg, int unset)
{
struct timerlat_params *params = opt->value;
if (unset)
return -1;
params->common.user_workload = true;
params->common.user_data = true;
return 0;
}
static int opt_nano_cb(const struct option *opt, const char *arg, int unset)
{
struct timerlat_params *params = opt->value;
if (unset)
return -1;
params->common.output_divisor = 1;
return 0;
}
static int opt_stack_format_cb(const struct option *opt, const char *arg, int unset)
{
int *format = opt->value;
if (unset || !arg)
return -1;
*format = parse_stack_format((char *)arg);
if (*format == -1)
fatal("Invalid --stack-format option");
return 0;
}
static int opt_timerlat_align_cb(const struct option *opt, const char *arg, int unset)
{
struct timerlat_params *params = opt->value;
if (unset || !arg)
return -1;
params->timerlat_align = true;
params->timerlat_align_us = get_llong_from_str((char *)arg);
return 0;
}
/*
* Macros for command line options specific to histogram-based tools
*/
#define HIST_OPT_BUCKET_SIZE OPT_CALLBACK('b', "bucket-size", \
&params->common.hist.bucket_size, "N", \
"set the histogram bucket size (default 1)", \
opt_bucket_size_cb)
#define HIST_OPT_ENTRIES OPT_CALLBACK('E', "entries", &params->common.hist.entries, "N", \
"set the number of entries of the histogram (default 256)", \
opt_entries_cb)
#define HIST_OPT_NO_IRQ OPT_BOOLEAN_FLAG(0, "no-irq", &params->common.hist.no_irq, \
"ignore IRQ latencies", PARSE_OPT_NOAUTONEG)
#define HIST_OPT_NO_THREAD OPT_BOOLEAN_FLAG(0, "no-thread", &params->common.hist.no_thread, \
"ignore thread latencies", PARSE_OPT_NOAUTONEG)
#define HIST_OPT_NO_HEADER OPT_BOOLEAN(0, "no-header", &params->common.hist.no_header, \
"do not print header")
#define HIST_OPT_NO_SUMMARY OPT_BOOLEAN(0, "no-summary", &params->common.hist.no_summary, \
"do not print summary")
#define HIST_OPT_NO_INDEX OPT_BOOLEAN(0, "no-index", &params->common.hist.no_index, \
"do not print index")
#define HIST_OPT_WITH_ZEROS OPT_BOOLEAN(0, "with-zeros", &params->common.hist.with_zeros, \
"print zero only entries")
/* Histogram-specific callbacks */
static int opt_bucket_size_cb(const struct option *opt, const char *arg, int unset)
{
int *bucket_size = opt->value;
if (unset || !arg)
return -1;
*bucket_size = get_llong_from_str((char *)arg);
if (*bucket_size == 0 || *bucket_size >= 1000000)
fatal("Bucket size needs to be > 0 and <= 1000000");
return 0;
}
static int opt_entries_cb(const struct option *opt, const char *arg, int unset)
{
int *entries = opt->value;
if (unset || !arg)
return -1;
*entries = get_llong_from_str((char *)arg);
if (*entries < 10 || *entries > 9999999)
fatal("Entries must be > 10 and < 10000000");
return 0;
}
+457
View File
@@ -0,0 +1,457 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#include "common.h"
struct osnoise_tool *trace_tool;
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
*/
if (trace_tool)
tracefs_iterate_stop(trace_tool->trace.inst);
return;
}
stop_tracing = 1;
if (trace_tool) {
trace_instance_stop(&trace_tool->trace);
if (trace_tool->record)
trace_instance_stop(&trace_tool->record->trace);
}
}
/*
* 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);
}
}
/*
* 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;
/*
* Expose the tool to signal handlers so they can stop the trace.
* Otherwise, rtla could loop indefinitely when overloaded.
*/
trace_tool = tool;
retval = ops->apply_config(tool);
if (retval) {
err_msg("Could not apply config\n");
goto out_free;
}
retval = enable_tracer_by_name(tool->trace.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);
}
+192
View File
@@ -0,0 +1,192 @@
/* SPDX-License-Identifier: GPL-2.0 */
#pragma once
#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;
/* -1 as init value because 0 is off */
int orig_opt_timerlat_align;
int opt_timerlat_align;
/* 0 as init value */
unsigned long long orig_timerlat_align_us;
unsigned long long timerlat_align_us;
};
extern volatile int stop_tracing;
struct hist_params {
bool no_irq;
bool no_thread;
bool no_header;
bool no_summary;
bool no_index;
bool 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;
bool pretty_output;
bool quiet;
bool user_workload;
bool kernel_workload;
bool user_data;
bool 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 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);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-2.0
#pragma once
#include "common.h"
enum osnoise_mode {
MODE_OSNOISE = 0,
MODE_HWNOISE
};
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.
*/
#define OSNOISE_OPTION_INIT_VAL (-1)
#define OSNOISE_TIME_INIT_VAL (0)
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_runtime_period(struct osnoise_context *context,
unsigned long long runtime,
unsigned long long period);
void osnoise_restore_runtime_period(struct osnoise_context *context);
void osnoise_restore_stop_us(struct osnoise_context *context);
void osnoise_restore_stop_total_us(struct osnoise_context *context);
int osnoise_set_timerlat_period_us(struct osnoise_context *context,
long long timerlat_period_us);
void osnoise_restore_timerlat_period_us(struct osnoise_context *context);
int osnoise_set_tracing_thresh(struct osnoise_context *context,
long long tracing_thresh);
void osnoise_restore_tracing_thresh(struct osnoise_context *context);
void osnoise_restore_print_stack(struct osnoise_context *context);
int osnoise_set_print_stack(struct osnoise_context *context,
long long print_stack);
int osnoise_set_timerlat_align_us(struct osnoise_context *context,
long long timerlat_align_us);
void osnoise_restore_timerlat_align_us(struct osnoise_context *context);
int osnoise_set_timerlat_align(struct osnoise_context *context, bool onoff);
int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff);
void osnoise_report_missed_events(struct osnoise_tool *tool);
int osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params);
int osnoise_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[]);
@@ -0,0 +1,467 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include "osnoise.h"
#include "cli.h"
struct osnoise_hist_cpu {
int *samples;
int count;
unsigned long long min_sample;
unsigned long long sum_sample;
unsigned long long max_sample;
};
struct osnoise_hist_data {
struct tracefs_hist *trace_hist;
struct osnoise_hist_cpu *hist;
int entries;
int bucket_size;
};
/*
* osnoise_free_histogram - free runtime data
*/
static void
osnoise_free_histogram(struct osnoise_hist_data *data)
{
int cpu;
/* one histogram for IRQ and one for thread, per CPU */
for (cpu = 0; cpu < nr_cpus; cpu++) {
if (data->hist[cpu].samples)
free(data->hist[cpu].samples);
}
/* one set of histograms per CPU */
if (data->hist)
free(data->hist);
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 entries, int bucket_size)
{
struct osnoise_hist_data *data;
int cpu;
data = calloc(1, sizeof(*data));
if (!data)
return NULL;
data->entries = entries;
data->bucket_size = bucket_size;
data->hist = calloc(1, sizeof(*data->hist) * nr_cpus);
if (!data->hist)
goto cleanup;
for (cpu = 0; cpu < nr_cpus; cpu++) {
data->hist[cpu].samples = calloc(1, sizeof(*data->hist->samples) * (entries + 1));
if (!data->hist[cpu].samples)
goto cleanup;
}
/* set the min to max */
for (cpu = 0; cpu < nr_cpus; cpu++)
data->hist[cpu].min_sample = ~0;
return data;
cleanup:
osnoise_free_histogram(data);
return NULL;
}
static void osnoise_hist_update_multiple(struct osnoise_tool *tool, int cpu,
unsigned long long duration, int count)
{
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->common.output_divisor)
duration = duration / params->common.output_divisor;
bucket = duration / data->bucket_size;
total_duration = duration * count;
hist = data->hist[cpu].samples;
data->hist[cpu].count += count;
update_min(&data->hist[cpu].min_sample, &duration);
update_sum(&data->hist[cpu].sum_sample, &total_duration);
update_max(&data->hist[cpu].max_sample, &duration);
if (bucket < entries)
hist[bucket] += count;
else
hist[entries] += count;
}
/*
* osnoise_destroy_trace_hist - disable events used to collect histogram
*/
static void osnoise_destroy_trace_hist(struct osnoise_tool *tool)
{
struct osnoise_hist_data *data = tool->data;
tracefs_hist_pause(tool->trace.inst, data->trace_hist);
tracefs_hist_destroy(tool->trace.inst, data->trace_hist);
}
/*
* osnoise_init_trace_hist - enable events used to collect histogram
*/
static int osnoise_init_trace_hist(struct osnoise_tool *tool)
{
struct osnoise_params *params = to_osnoise_params(tool->params);
struct osnoise_hist_data *data = tool->data;
int bucket_size;
char buff[128];
int retval = 0;
/*
* Set the size of the bucket.
*/
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",
buff, TRACEFS_HIST_KEY_NORMAL);
if (!data->trace_hist)
return 1;
retval = tracefs_hist_add_key(data->trace_hist, "cpu", 0);
if (retval)
goto out_err;
retval = tracefs_hist_start(tool->trace.inst, data->trace_hist);
if (retval)
goto out_err;
return 0;
out_err:
osnoise_destroy_trace_hist(tool);
return 1;
}
/*
* osnoise_read_trace_hist - parse histogram file and file osnoise histogram
*/
static void osnoise_read_trace_hist(struct osnoise_tool *tool)
{
struct osnoise_hist_data *data = tool->data;
long long cpu, counter, duration;
char *content, *position;
tracefs_hist_pause(tool->trace.inst, data->trace_hist);
content = tracefs_event_file_read(tool->trace.inst, "osnoise",
"sample_threshold",
"hist", NULL);
if (!content)
return;
position = content;
while (true) {
position = strstr(position, "duration: ~");
if (!position)
break;
position += strlen("duration: ~");
duration = get_llong_from_str(position);
if (duration == -1)
err_msg("error reading duration from histogram\n");
position = strstr(position, "cpu:");
if (!position)
break;
position += strlen("cpu: ");
cpu = get_llong_from_str(position);
if (cpu == -1)
err_msg("error reading cpu from histogram\n");
position = strstr(position, "hitcount:");
if (!position)
break;
position += strlen("hitcount: ");
counter = get_llong_from_str(position);
if (counter == -1)
err_msg("error reading counter from histogram\n");
osnoise_hist_update_multiple(tool, cpu, duration, counter);
}
free(content);
}
/*
* osnoise_hist_header - print the header of the tracer to the output
*/
static void osnoise_hist_header(struct osnoise_tool *tool)
{
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->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->common.output_divisor == 1 ? "nanoseconds" : "microseconds",
params->common.output_divisor == 1 ? "ns" : "us");
trace_seq_printf(s, "# Duration: %s\n", duration);
if (!params->common.hist.no_index)
trace_seq_printf(s, "Index");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
trace_seq_printf(s, " CPU-%03d", cpu);
}
trace_seq_printf(s, "\n");
trace_seq_do_printf(s);
trace_seq_reset(s);
}
/*
* osnoise_print_summary - print the summary of the hist data to the output
*/
static void
osnoise_print_summary(struct osnoise_params *params,
struct trace_instance *trace,
struct osnoise_hist_data *data)
{
int cpu;
if (params->common.hist.no_summary)
return;
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "count:");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
trace_seq_printf(trace->seq, "%9d ", data->hist[cpu].count);
}
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "min: ");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
trace_seq_printf(trace->seq, "%9llu ", data->hist[cpu].min_sample);
}
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "avg: ");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
if (data->hist[cpu].count)
trace_seq_printf(trace->seq, "%9.2f ",
((double) data->hist[cpu].sum_sample) / data->hist[cpu].count);
else
trace_seq_printf(trace->seq, " - ");
}
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "max: ");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
trace_seq_printf(trace->seq, "%9llu ", data->hist[cpu].max_sample);
}
trace_seq_printf(trace->seq, "\n");
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
}
/*
* osnoise_print_stats - print data for all CPUs
*/
static void
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;
int bucket, cpu;
int total;
osnoise_hist_header(tool);
for (bucket = 0; bucket < data->entries; bucket++) {
total = 0;
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "%-6d",
bucket * data->bucket_size);
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
total += data->hist[cpu].samples[bucket];
trace_seq_printf(trace->seq, "%9d ", data->hist[cpu].samples[bucket]);
}
if (total == 0 && !params->common.hist.with_zeros) {
trace_seq_reset(trace->seq);
continue;
}
/* There are samples above the threshold */
has_samples = 1;
trace_seq_printf(trace->seq, "\n");
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
}
/*
* If no samples were recorded, skip calculations, print zeroed statistics
* and return.
*/
if (!has_samples) {
trace_seq_reset(trace->seq);
trace_seq_printf(trace->seq, "over: 0\ncount: 0\nmin: 0\navg: 0\nmax: 0\n");
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
return;
}
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "over: ");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].count)
continue;
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].samples[data->entries]);
}
trace_seq_printf(trace->seq, "\n");
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
osnoise_print_summary(params, trace, data);
osnoise_report_missed_events(tool);
}
/*
* osnoise_hist_apply_config - apply the hist configs to the initialized tool
*/
static int
osnoise_hist_apply_config(struct osnoise_tool *tool)
{
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 common_params *params)
{
struct osnoise_tool *tool;
tool = osnoise_init_tool("osnoise_hist");
if (!tool)
return NULL;
tool->data = osnoise_alloc_histogram(params->hist.entries,
params->hist.bucket_size);
if (!tool->data)
goto out_err;
return tool;
out_err:
osnoise_destroy_tool(tool);
return NULL;
}
static int osnoise_hist_enable(struct osnoise_tool *tool)
{
int retval;
retval = osnoise_init_trace_hist(tool);
if (retval)
return retval;
return osnoise_enable(tool);
}
static int osnoise_hist_main_loop(struct osnoise_tool *tool)
{
int retval;
retval = hist_main_loop(tool);
osnoise_read_trace_hist(tool);
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,
};
@@ -0,0 +1,311 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include "osnoise.h"
#include "cli.h"
struct osnoise_top_cpu {
unsigned long long sum_runtime;
unsigned long long sum_noise;
unsigned long long max_noise;
unsigned long long max_sample;
unsigned long long hw_count;
unsigned long long nmi_count;
unsigned long long irq_count;
unsigned long long softirq_count;
unsigned long long thread_count;
int sum_cycles;
};
struct osnoise_top_data {
struct osnoise_top_cpu *cpu_data;
};
/*
* osnoise_free_top - free runtime 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(void)
{
struct osnoise_top_data *data;
data = calloc(1, sizeof(*data));
if (!data)
return NULL;
/* one set of histograms per CPU */
data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
if (!data->cpu_data)
goto cleanup;
return data;
cleanup:
osnoise_free_top(data);
return NULL;
}
/*
* osnoise_top_handler - this is the handler for osnoise tracer events
*/
static int
osnoise_top_handler(struct trace_seq *s, struct tep_record *record,
struct tep_event *event, void *context)
{
struct trace_instance *trace = context;
struct osnoise_tool *tool;
unsigned long long val;
struct osnoise_top_cpu *cpu_data;
struct osnoise_top_data *data;
int cpu = record->cpu;
tool = container_of(trace, struct osnoise_tool, trace);
data = tool->data;
cpu_data = &data->cpu_data[cpu];
cpu_data->sum_cycles++;
tep_get_field_val(s, event, "runtime", record, &val, 1);
update_sum(&cpu_data->sum_runtime, &val);
tep_get_field_val(s, event, "noise", record, &val, 1);
update_max(&cpu_data->max_noise, &val);
update_sum(&cpu_data->sum_noise, &val);
tep_get_field_val(s, event, "max_sample", record, &val, 1);
update_max(&cpu_data->max_sample, &val);
tep_get_field_val(s, event, "hw_count", record, &val, 1);
update_sum(&cpu_data->hw_count, &val);
tep_get_field_val(s, event, "nmi_count", record, &val, 1);
update_sum(&cpu_data->nmi_count, &val);
tep_get_field_val(s, event, "irq_count", record, &val, 1);
update_sum(&cpu_data->irq_count, &val);
tep_get_field_val(s, event, "softirq_count", record, &val, 1);
update_sum(&cpu_data->softirq_count, &val);
tep_get_field_val(s, event, "thread_count", record, &val, 1);
update_sum(&cpu_data->thread_count, &val);
return 0;
}
/*
* osnoise_top_header - print the header of the tool output
*/
static void osnoise_top_header(struct osnoise_tool *top)
{
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 (pretty)
trace_seq_printf(s, "\033[2;37;40m");
trace_seq_printf(s, " ");
if (params->mode == MODE_OSNOISE) {
trace_seq_printf(s, "Operating System Noise");
trace_seq_printf(s, " ");
} else if (params->mode == MODE_HWNOISE) {
trace_seq_printf(s, "Hardware-related Noise");
}
trace_seq_printf(s, " ");
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 (pretty)
trace_seq_printf(s, "\033[2;30;47m");
trace_seq_printf(s, "CPU Period Runtime ");
trace_seq_printf(s, " Noise ");
trace_seq_printf(s, " %% CPU Aval ");
trace_seq_printf(s, " Max Noise Max Single ");
trace_seq_printf(s, " HW NMI");
if (params->mode == MODE_HWNOISE)
goto eol;
trace_seq_printf(s, " IRQ Softirq Thread");
eol:
if (pretty)
trace_seq_printf(s, "\033[0;0;0m");
trace_seq_printf(s, "\n");
}
/*
* clear_terminal - clears the output terminal
*/
static void clear_terminal(struct trace_seq *seq)
{
if (!config_debug)
trace_seq_printf(seq, "\033c");
}
/*
* osnoise_top_print - prints the output of a given CPU
*/
static void osnoise_top_print(struct osnoise_tool *tool, int cpu)
{
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;
int percentage;
int decimal;
data = tool->data;
cpu_data = &data->cpu_data[cpu];
if (!cpu_data->sum_runtime)
return;
percentage = ((cpu_data->sum_runtime - cpu_data->sum_noise) * 10000000)
/ cpu_data->sum_runtime;
decimal = percentage % 100000;
percentage = percentage / 100000;
trace_seq_printf(s, "%3d #%-6d %12llu ", cpu, cpu_data->sum_cycles, cpu_data->sum_runtime);
trace_seq_printf(s, "%12llu ", cpu_data->sum_noise);
trace_seq_printf(s, " %3d.%05d", percentage, decimal);
trace_seq_printf(s, "%12llu %12llu", cpu_data->max_noise, cpu_data->max_sample);
trace_seq_printf(s, "%12llu ", cpu_data->hw_count);
trace_seq_printf(s, "%12llu ", cpu_data->nmi_count);
if (params->mode == MODE_HWNOISE) {
trace_seq_printf(s, "\n");
return;
}
trace_seq_printf(s, "%12llu ", cpu_data->irq_count);
trace_seq_printf(s, "%12llu ", cpu_data->softirq_count);
trace_seq_printf(s, "%12llu\n", cpu_data->thread_count);
}
/*
* osnoise_print_stats - print data for all cpus
*/
static void
osnoise_print_stats(struct osnoise_tool *top)
{
struct osnoise_params *params = to_osnoise_params(top->params);
struct trace_instance *trace = &top->trace;
int i;
if (!params->common.quiet)
clear_terminal(trace->seq);
osnoise_top_header(top);
for_each_monitored_cpu(i, &params->common) {
osnoise_top_print(top, i);
}
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
osnoise_report_missed_events(top);
}
/*
* osnoise_top_apply_config - apply the top configs to the initialized tool
*/
static int
osnoise_top_apply_config(struct osnoise_tool *tool)
{
struct osnoise_params *params = to_osnoise_params(tool->params);
int retval;
retval = osnoise_apply_config(tool, params);
if (retval)
goto out_err;
if (params->mode == MODE_HWNOISE) {
retval = osnoise_set_irq_disable(tool->context, 1);
if (retval) {
err_msg("Failed to set OSNOISE_IRQ_DISABLE option\n");
goto out_err;
}
}
if (isatty(STDOUT_FILENO) && !params->common.quiet)
params->common.pretty_output = 1;
return 0;
out_err:
return -1;
}
/*
* osnoise_init_top - initialize a osnoise top tool with parameters
*/
struct osnoise_tool *osnoise_init_top(struct common_params *params)
{
struct osnoise_tool *tool;
tool = osnoise_init_tool("osnoise_top");
if (!tool)
return NULL;
tool->data = osnoise_alloc_top();
if (!tool->data) {
osnoise_destroy_tool(tool);
return NULL;
}
tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise",
osnoise_top_handler, NULL);
return tool;
}
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,
};
@@ -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;
}
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sched.h>
#include <linux/compiler.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;
}
retval = osnoise_set_timerlat_align(tool->context, params->timerlat_align);
if (retval && params->timerlat_align) {
/*
* We might be running on a kernel that does not support timerlat align.
* Unless user requested it explicitly, ignore the error.
*/
err_msg("Failed to enable timerlat align\n");
goto out_err;
}
if (params->timerlat_align) {
retval = osnoise_set_timerlat_align_us(tool->context, params->timerlat_align_us);
if (retval) {
err_msg("Failed to set timerlat align us\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(tool->trace.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();
}
__noreturn static void timerlat_usage(int err)
{
int i;
static const char * const msg[] = {
"",
"timerlat version " VERSION,
"",
" usage: [rtla] timerlat [MODE] ...",
"",
" modes:",
" top - prints the summary from timerlat tracer",
" hist - prints a histogram of timer latencies",
"",
"if no MODE is given, the top mode is called, passing the arguments",
NULL,
};
for (i = 0; msg[i]; i++)
fprintf(stderr, "%s\n", msg[i]);
exit(err);
}
int timerlat_main(int argc, char *argv[])
{
if (argc == 0)
goto usage;
/*
* if timerlat was called without any argument, run the
* default cmdline.
*/
if (argc == 1) {
run_tool(&timerlat_top_ops, argc, argv);
exit(0);
}
if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
timerlat_usage(129);
} else if (str_has_prefix(argv[1], "-")) {
/* the user skipped the tool, call the default one */
run_tool(&timerlat_top_ops, argc, argv);
exit(0);
} else if (strcmp(argv[1], "top") == 0) {
run_tool(&timerlat_top_ops, argc-1, &argv[1]);
exit(0);
} else if (strcmp(argv[1], "hist") == 0) {
run_tool(&timerlat_hist_ops, argc-1, &argv[1]);
exit(0);
}
usage:
timerlat_usage(129);
}
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0
#pragma once
#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;
bool no_aa;
bool dump_tasks;
int deepest_idle_state;
enum timerlat_tracing_mode mode;
const char *bpf_action_program;
enum stack_format stack_format;
bool timerlat_align;
unsigned long long timerlat_align_us;
};
#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);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
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);
@@ -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 */
@@ -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__ */
@@ -0,0 +1,789 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <sched.h>
#include <pthread.h>
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
#include "cli.h"
#include "common.h"
struct timerlat_hist_cpu {
int *irq;
int *thread;
int *user;
unsigned long long irq_count;
unsigned long long thread_count;
unsigned long long user_count;
unsigned long long min_irq;
unsigned long long sum_irq;
unsigned long long max_irq;
unsigned long long min_thread;
unsigned long long sum_thread;
unsigned long long max_thread;
unsigned long long min_user;
unsigned long long sum_user;
unsigned long long max_user;
};
struct timerlat_hist_data {
struct timerlat_hist_cpu *hist;
int entries;
int bucket_size;
};
/*
* timerlat_free_histogram - free runtime data
*/
static void
timerlat_free_histogram(struct timerlat_hist_data *data)
{
int cpu;
/* one histogram for IRQ and one for thread, per CPU */
for (cpu = 0; cpu < nr_cpus; cpu++) {
if (data->hist[cpu].irq)
free(data->hist[cpu].irq);
if (data->hist[cpu].thread)
free(data->hist[cpu].thread);
if (data->hist[cpu].user)
free(data->hist[cpu].user);
}
/* one set of histograms per CPU */
if (data->hist)
free(data->hist);
}
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 entries, int bucket_size)
{
struct timerlat_hist_data *data;
int cpu;
data = calloc(1, sizeof(*data));
if (!data)
return NULL;
data->entries = entries;
data->bucket_size = bucket_size;
/* one set of histograms per CPU */
data->hist = calloc(1, sizeof(*data->hist) * nr_cpus);
if (!data->hist)
goto cleanup;
/* one histogram for IRQ and one for thread, per cpu */
for (cpu = 0; cpu < nr_cpus; cpu++) {
data->hist[cpu].irq = calloc(1, sizeof(*data->hist->irq) * (entries + 1));
if (!data->hist[cpu].irq)
goto cleanup;
data->hist[cpu].thread = calloc(1, sizeof(*data->hist->thread) * (entries + 1));
if (!data->hist[cpu].thread)
goto cleanup;
data->hist[cpu].user = calloc(1, sizeof(*data->hist->user) * (entries + 1));
if (!data->hist[cpu].user)
goto cleanup;
}
/* set the min to max */
for (cpu = 0; cpu < nr_cpus; cpu++) {
data->hist[cpu].min_irq = ~0;
data->hist[cpu].min_thread = ~0;
data->hist[cpu].min_user = ~0;
}
return data;
cleanup:
timerlat_free_histogram(data);
return NULL;
}
/*
* timerlat_hist_update - record a new timerlat occurent on cpu, updating data
*/
static void
timerlat_hist_update(struct osnoise_tool *tool, int cpu,
unsigned long long context,
unsigned long long latency)
{
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->common.output_divisor)
latency = latency / params->common.output_divisor;
bucket = latency / data->bucket_size;
if (!context) {
hist = data->hist[cpu].irq;
data->hist[cpu].irq_count++;
update_min(&data->hist[cpu].min_irq, &latency);
update_sum(&data->hist[cpu].sum_irq, &latency);
update_max(&data->hist[cpu].max_irq, &latency);
} else if (context == 1) {
hist = data->hist[cpu].thread;
data->hist[cpu].thread_count++;
update_min(&data->hist[cpu].min_thread, &latency);
update_sum(&data->hist[cpu].sum_thread, &latency);
update_max(&data->hist[cpu].max_thread, &latency);
} else { /* user */
hist = data->hist[cpu].user;
data->hist[cpu].user_count++;
update_min(&data->hist[cpu].min_user, &latency);
update_sum(&data->hist[cpu].sum_user, &latency);
update_max(&data->hist[cpu].max_user, &latency);
}
if (bucket < entries)
hist[bucket]++;
else
hist[entries]++;
}
/*
* timerlat_hist_handler - this is the handler for timerlat tracer events
*/
static int
timerlat_hist_handler(struct trace_seq *s, struct tep_record *record,
struct tep_event *event, void *data)
{
struct trace_instance *trace = data;
unsigned long long context, latency;
struct osnoise_tool *tool;
int cpu = record->cpu;
tool = container_of(trace, struct osnoise_tool, trace);
tep_get_field_val(s, event, "context", record, &context, 1);
tep_get_field_val(s, event, "timer_latency", record, &latency, 1);
timerlat_hist_update(tool, cpu, context, latency);
return 0;
}
/*
* 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_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->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->common.output_divisor == 1 ? "nanoseconds" : "microseconds",
params->common.output_divisor == 1 ? "ns" : "us");
trace_seq_printf(s, "# Duration: %s\n", duration);
if (!params->common.hist.no_index)
trace_seq_printf(s, "Index");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
if (!params->common.hist.no_irq)
trace_seq_printf(s, " IRQ-%03d", cpu);
if (!params->common.hist.no_thread)
trace_seq_printf(s, " Thr-%03d", cpu);
if (params->common.user_data)
trace_seq_printf(s, " Usr-%03d", cpu);
}
trace_seq_printf(s, "\n");
trace_seq_do_printf(s);
trace_seq_reset(s);
}
/*
* format_summary_value - format a line of summary value (min, max or avg)
* of hist data
*/
static void format_summary_value(struct trace_seq *seq,
int count,
unsigned long long val,
bool avg)
{
if (count)
trace_seq_printf(seq, "%9llu ", avg ? val / count : val);
else
trace_seq_printf(seq, "%9c ", '-');
}
/*
* timerlat_print_summary - print the summary of the hist data to the output
*/
static void
timerlat_print_summary(struct timerlat_params *params,
struct trace_instance *trace,
struct timerlat_hist_data *data)
{
int cpu;
if (params->common.hist.no_summary)
return;
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "count:");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
if (!params->common.hist.no_irq)
trace_seq_printf(trace->seq, "%9llu ",
data->hist[cpu].irq_count);
if (!params->common.hist.no_thread)
trace_seq_printf(trace->seq, "%9llu ",
data->hist[cpu].thread_count);
if (params->common.user_data)
trace_seq_printf(trace->seq, "%9llu ",
data->hist[cpu].user_count);
}
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "min: ");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
data->hist[cpu].irq_count,
data->hist[cpu].min_irq,
false);
if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
data->hist[cpu].thread_count,
data->hist[cpu].min_thread,
false);
if (params->common.user_data)
format_summary_value(trace->seq,
data->hist[cpu].user_count,
data->hist[cpu].min_user,
false);
}
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "avg: ");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
data->hist[cpu].irq_count,
data->hist[cpu].sum_irq,
true);
if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
data->hist[cpu].thread_count,
data->hist[cpu].sum_thread,
true);
if (params->common.user_data)
format_summary_value(trace->seq,
data->hist[cpu].user_count,
data->hist[cpu].sum_user,
true);
}
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "max: ");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
data->hist[cpu].irq_count,
data->hist[cpu].max_irq,
false);
if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
data->hist[cpu].thread_count,
data->hist[cpu].max_thread,
false);
if (params->common.user_data)
format_summary_value(trace->seq,
data->hist[cpu].user_count,
data->hist[cpu].max_user,
false);
}
trace_seq_printf(trace->seq, "\n");
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
}
static void
timerlat_print_stats_all(struct timerlat_params *params,
struct trace_instance *trace,
struct timerlat_hist_data *data)
{
struct timerlat_hist_cpu *cpu_data;
struct timerlat_hist_cpu sum;
int cpu;
if (params->common.hist.no_summary)
return;
memset(&sum, 0, sizeof(sum));
sum.min_irq = ~0;
sum.min_thread = ~0;
sum.min_user = ~0;
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
cpu_data = &data->hist[cpu];
sum.irq_count += cpu_data->irq_count;
update_min(&sum.min_irq, &cpu_data->min_irq);
update_sum(&sum.sum_irq, &cpu_data->sum_irq);
update_max(&sum.max_irq, &cpu_data->max_irq);
sum.thread_count += cpu_data->thread_count;
update_min(&sum.min_thread, &cpu_data->min_thread);
update_sum(&sum.sum_thread, &cpu_data->sum_thread);
update_max(&sum.max_thread, &cpu_data->max_thread);
sum.user_count += cpu_data->user_count;
update_min(&sum.min_user, &cpu_data->min_user);
update_sum(&sum.sum_user, &cpu_data->sum_user);
update_max(&sum.max_user, &cpu_data->max_user);
}
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "ALL: ");
if (!params->common.hist.no_irq)
trace_seq_printf(trace->seq, " IRQ");
if (!params->common.hist.no_thread)
trace_seq_printf(trace->seq, " Thr");
if (params->common.user_data)
trace_seq_printf(trace->seq, " Usr");
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "count:");
if (!params->common.hist.no_irq)
trace_seq_printf(trace->seq, "%9llu ",
sum.irq_count);
if (!params->common.hist.no_thread)
trace_seq_printf(trace->seq, "%9llu ",
sum.thread_count);
if (params->common.user_data)
trace_seq_printf(trace->seq, "%9llu ",
sum.user_count);
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "min: ");
if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
sum.irq_count,
sum.min_irq,
false);
if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
sum.thread_count,
sum.min_thread,
false);
if (params->common.user_data)
format_summary_value(trace->seq,
sum.user_count,
sum.min_user,
false);
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "avg: ");
if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
sum.irq_count,
sum.sum_irq,
true);
if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
sum.thread_count,
sum.sum_thread,
true);
if (params->common.user_data)
format_summary_value(trace->seq,
sum.user_count,
sum.sum_user,
true);
trace_seq_printf(trace->seq, "\n");
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "max: ");
if (!params->common.hist.no_irq)
format_summary_value(trace->seq,
sum.irq_count,
sum.max_irq,
false);
if (!params->common.hist.no_thread)
format_summary_value(trace->seq,
sum.thread_count,
sum.max_thread,
false);
if (params->common.user_data)
format_summary_value(trace->seq,
sum.user_count,
sum.max_user,
false);
trace_seq_printf(trace->seq, "\n");
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
}
/*
* timerlat_print_stats - print data for each CPUs
*/
static void
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;
int total;
timerlat_hist_header(tool);
for (bucket = 0; bucket < data->entries; bucket++) {
total = 0;
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "%-6d",
bucket * data->bucket_size);
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
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->common.hist.no_thread) {
total += data->hist[cpu].thread[bucket];
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].thread[bucket]);
}
if (params->common.user_data) {
total += data->hist[cpu].user[bucket];
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].user[bucket]);
}
}
if (total == 0 && !params->common.hist.with_zeros) {
trace_seq_reset(trace->seq);
continue;
}
trace_seq_printf(trace->seq, "\n");
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
}
if (!params->common.hist.no_index)
trace_seq_printf(trace->seq, "over: ");
for_each_monitored_cpu(cpu, &params->common) {
if (!data->hist[cpu].irq_count && !data->hist[cpu].thread_count)
continue;
if (!params->common.hist.no_irq)
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].irq[data->entries]);
if (!params->common.hist.no_thread)
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].thread[data->entries]);
if (params->common.user_data)
trace_seq_printf(trace->seq, "%9d ",
data->hist[cpu].user[data->entries]);
}
trace_seq_printf(trace->seq, "\n");
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
timerlat_print_summary(params, trace, data);
timerlat_print_stats_all(params, trace, data);
osnoise_report_missed_events(tool);
}
/*
* timerlat_hist_apply_config - apply the hist configs to the initialized tool
*/
static int
timerlat_hist_apply_config(struct osnoise_tool *tool)
{
struct timerlat_params *params = to_timerlat_params(tool->params);
int retval;
retval = timerlat_apply_config(tool, params);
if (retval)
goto out_err;
return 0;
out_err:
return -1;
}
/*
* timerlat_init_hist - initialize a timerlat hist tool with parameters
*/
static struct osnoise_tool
*timerlat_init_hist(struct common_params *params)
{
struct osnoise_tool *tool;
tool = osnoise_init_tool("timerlat_hist");
if (!tool)
return NULL;
tool->data = timerlat_alloc_histogram(params->hist.entries,
params->hist.bucket_size);
if (!tool->data)
goto out_err;
tep_register_event_handler(tool->trace.tep, -1, "ftrace", "timerlat",
timerlat_hist_handler, tool);
return tool;
out_err:
osnoise_destroy_tool(tool);
return NULL;
}
static int timerlat_hist_bpf_main_loop(struct osnoise_tool *tool)
{
int retval;
while (!stop_tracing) {
timerlat_bpf_wait(-1);
if (!stop_tracing) {
/* Threshold overflow, perform actions on threshold */
retval = common_threshold_handler(tool);
if (retval)
return retval;
if (!should_continue_tracing(tool->params))
break;
if (timerlat_bpf_restart_tracing()) {
err_msg("Error restarting BPF trace\n");
return -1;
}
}
}
timerlat_bpf_detach();
retval = timerlat_hist_bpf_pull_data(tool);
if (retval)
err_msg("Error pulling BPF data\n");
return retval;
}
static int timerlat_hist_main(struct osnoise_tool *tool)
{
struct timerlat_params *params = to_timerlat_params(tool->params);
int retval;
if (params->mode == TRACING_MODE_TRACEFS)
retval = hist_main_loop(tool);
else
retval = timerlat_hist_bpf_main_loop(tool);
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,
};
@@ -0,0 +1,592 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <sched.h>
#include <pthread.h>
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
#include "cli.h"
#include "common.h"
struct timerlat_top_cpu {
unsigned long long irq_count;
unsigned long long thread_count;
unsigned long long user_count;
unsigned long long cur_irq;
unsigned long long min_irq;
unsigned long long sum_irq;
unsigned long long max_irq;
unsigned long long cur_thread;
unsigned long long min_thread;
unsigned long long sum_thread;
unsigned long long max_thread;
unsigned long long cur_user;
unsigned long long min_user;
unsigned long long sum_user;
unsigned long long max_user;
};
struct timerlat_top_data {
struct timerlat_top_cpu *cpu_data;
};
/*
* timerlat_free_top - free runtime 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(void)
{
struct timerlat_top_data *data;
int cpu;
data = calloc(1, sizeof(*data));
if (!data)
return NULL;
/* one set of histograms per CPU */
data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
if (!data->cpu_data)
goto cleanup;
/* set the min to max */
for (cpu = 0; cpu < nr_cpus; cpu++) {
data->cpu_data[cpu].min_irq = ~0;
data->cpu_data[cpu].min_thread = ~0;
data->cpu_data[cpu].min_user = ~0;
}
return data;
cleanup:
timerlat_free_top(data);
return NULL;
}
static void
timerlat_top_reset_sum(struct timerlat_top_cpu *summary)
{
memset(summary, 0, sizeof(*summary));
summary->min_irq = ~0;
summary->min_thread = ~0;
summary->min_user = ~0;
}
static void
timerlat_top_update_sum(struct osnoise_tool *tool, int cpu, struct timerlat_top_cpu *sum)
{
struct timerlat_top_data *data = tool->data;
struct timerlat_top_cpu *cpu_data = &data->cpu_data[cpu];
sum->irq_count += cpu_data->irq_count;
update_min(&sum->min_irq, &cpu_data->min_irq);
update_sum(&sum->sum_irq, &cpu_data->sum_irq);
update_max(&sum->max_irq, &cpu_data->max_irq);
sum->thread_count += cpu_data->thread_count;
update_min(&sum->min_thread, &cpu_data->min_thread);
update_sum(&sum->sum_thread, &cpu_data->sum_thread);
update_max(&sum->max_thread, &cpu_data->max_thread);
sum->user_count += cpu_data->user_count;
update_min(&sum->min_user, &cpu_data->min_user);
update_sum(&sum->sum_user, &cpu_data->sum_user);
update_max(&sum->max_user, &cpu_data->max_user);
}
/*
* timerlat_hist_update - record a new timerlat occurent on cpu, updating data
*/
static void
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;
update_min(&cpu_data->min_irq, &latency);
update_sum(&cpu_data->sum_irq, &latency);
update_max(&cpu_data->max_irq, &latency);
} else if (thread == 1) {
cpu_data->thread_count++;
cpu_data->cur_thread = latency;
update_min(&cpu_data->min_thread, &latency);
update_sum(&cpu_data->sum_thread, &latency);
update_max(&cpu_data->max_thread, &latency);
} else {
cpu_data->user_count++;
cpu_data->cur_user = latency;
update_min(&cpu_data->min_user, &latency);
update_sum(&cpu_data->sum_user, &latency);
update_max(&cpu_data->max_user, &latency);
}
}
/*
* timerlat_top_handler - this is the handler for timerlat tracer events
*/
static int
timerlat_top_handler(struct trace_seq *s, struct tep_record *record,
struct tep_event *event, void *context)
{
struct trace_instance *trace = context;
unsigned long long latency, thread;
struct osnoise_tool *top;
int cpu = record->cpu;
top = container_of(trace, struct osnoise_tool, trace);
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);
timerlat_top_update(top, cpu, thread, latency);
}
return 0;
}
/*
* 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_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 (pretty)
trace_seq_printf(s, "\033[2;37;40m");
trace_seq_printf(s, " Timer Latency ");
if (params->common.user_data)
trace_seq_printf(s, " ");
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->common.output_divisor == 1 ? "ns" : "us",
params->common.output_divisor == 1 ? "ns" : "us");
if (params->common.user_data) {
trace_seq_printf(s, " | Ret user Timer Latency (%s)",
params->common.output_divisor == 1 ? "ns" : "us");
}
trace_seq_printf(s, "\n");
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->common.user_data)
trace_seq_printf(s, " | cur min avg max");
if (pretty)
trace_seq_printf(s, "\033[0;0;0m");
trace_seq_printf(s, "\n");
}
static const char *no_value = " -";
/*
* timerlat_top_print - prints the output of a given CPU
*/
static void timerlat_top_print(struct osnoise_tool *top, int cpu)
{
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];
struct trace_seq *s = top->trace.seq;
/*
* Skip if no data is available: is this cpu offline?
*/
if (!cpu_data->irq_count && !cpu_data->thread_count)
return;
/*
* Unless trace is being lost, IRQ counter is always the max.
*/
trace_seq_printf(s, "%3d #%-9llu |", cpu, cpu_data->irq_count);
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);
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);
trace_seq_printf(s, "%9llu ", cpu_data->min_thread);
trace_seq_printf(s, "%9llu ",
cpu_data->sum_thread / cpu_data->thread_count);
trace_seq_printf(s, "%9llu", cpu_data->max_thread);
}
if (!params->common.user_data) {
trace_seq_printf(s, "\n");
return;
}
trace_seq_printf(s, " |");
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);
trace_seq_printf(s, "%9llu ", cpu_data->min_user);
trace_seq_printf(s, "%9llu ",
cpu_data->sum_user / cpu_data->user_count);
trace_seq_printf(s, "%9llu\n", cpu_data->max_user);
}
}
/*
* timerlat_top_print_sum - prints the summary output
*/
static void
timerlat_top_print_sum(struct osnoise_tool *top, struct timerlat_top_cpu *summary)
{
const char *split = "----------------------------------------";
struct timerlat_params *params = to_timerlat_params(top->params);
unsigned long long count = summary->irq_count;
struct trace_seq *s = top->trace.seq;
int e = 0;
/*
* Skip if no data is available: is this cpu offline?
*/
if (!summary->irq_count && !summary->thread_count)
return;
while (count > 999999) {
e++;
count /= 10;
}
trace_seq_printf(s, "%.*s|%.*s|%.*s", 15, split, 40, split, 39, split);
if (params->common.user_data)
trace_seq_printf(s, "-|%.*s", 39, split);
trace_seq_printf(s, "\n");
trace_seq_printf(s, "ALL #%-6llu e%d |", count, e);
if (!summary->irq_count) {
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);
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);
trace_seq_printf(s, "%9llu ",
summary->sum_thread / summary->thread_count);
trace_seq_printf(s, "%9llu", summary->max_thread);
}
if (!params->common.user_data) {
trace_seq_printf(s, "\n");
return;
}
trace_seq_printf(s, " |");
if (!summary->user_count) {
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);
trace_seq_printf(s, "%9llu ",
summary->sum_user / summary->user_count);
trace_seq_printf(s, "%9llu\n", summary->max_user);
}
}
/*
* clear_terminal - clears the output terminal
*/
static void clear_terminal(struct trace_seq *seq)
{
if (!config_debug)
trace_seq_printf(seq, "\033c");
}
/*
* timerlat_print_stats - print data for all cpus
*/
static void
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;
int i;
if (params->common.aa_only)
return;
if (!params->common.quiet)
clear_terminal(trace->seq);
timerlat_top_reset_sum(&summary);
timerlat_top_header(params, top);
for_each_monitored_cpu(i, &params->common) {
timerlat_top_print(top, i);
timerlat_top_update_sum(top, i, &summary);
}
timerlat_top_print_sum(top, &summary);
trace_seq_do_printf(trace->seq);
trace_seq_reset(trace->seq);
osnoise_report_missed_events(top);
}
/*
* timerlat_top_apply_config - apply the top configs to the initialized tool
*/
static int
timerlat_top_apply_config(struct osnoise_tool *top)
{
struct timerlat_params *params = to_timerlat_params(top->params);
int retval;
retval = timerlat_apply_config(top, params);
if (retval)
goto out_err;
if (isatty(STDOUT_FILENO) && !params->common.quiet)
params->common.pretty_output = 1;
return 0;
out_err:
return -1;
}
/*
* timerlat_init_top - initialize a timerlat top tool with parameters
*/
static struct osnoise_tool
*timerlat_init_top(struct common_params *params)
{
struct osnoise_tool *top;
top = osnoise_init_tool("timerlat_top");
if (!top)
return NULL;
top->data = timerlat_alloc_top();
if (!top->data)
goto out_err;
tep_register_event_handler(top->trace.tep, -1, "ftrace", "timerlat",
timerlat_top_handler, top);
return top;
out_err:
osnoise_destroy_tool(top);
return NULL;
}
/*
* timerlat_top_bpf_main_loop - main loop to process events (BPF variant)
*/
static int
timerlat_top_bpf_main_loop(struct osnoise_tool *tool)
{
const struct common_params *params = tool->params;
int retval, wait_retval;
if (params->aa_only) {
/* Auto-analysis only, just wait for stop tracing */
timerlat_bpf_wait(-1);
return 0;
}
/* Pull and display data in a loop */
while (!stop_tracing) {
wait_retval = timerlat_bpf_wait(params->quiet ? -1 :
params->sleep_time);
retval = timerlat_top_bpf_pull_data(tool);
if (retval) {
err_msg("Error pulling BPF data\n");
return retval;
}
if (!params->quiet)
timerlat_print_stats(tool);
if (wait_retval > 0) {
/* Stopping requested by tracer */
retval = common_threshold_handler(tool);
if (retval)
return retval;
if (!should_continue_tracing(tool->params))
break;
if (timerlat_bpf_restart_tracing()) {
err_msg("Error restarting BPF trace\n");
return -1;
}
}
/* 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;
}
static int timerlat_top_main_loop(struct osnoise_tool *tool)
{
struct timerlat_params *params = to_timerlat_params(tool->params);
int retval;
if (params->mode == TRACING_MODE_TRACEFS) {
retval = top_main_loop(tool);
} else {
retval = timerlat_top_bpf_main_loop(tool);
timerlat_bpf_detach();
}
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,
};
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <sched.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <tracefs.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include "common.h"
#include "timerlat_u.h"
/*
* This is the user-space main for the tool timerlatu/ threads.
*
* It is as simple as this:
* - set affinity
* - set priority
* - open tracer fd
* - spin
* - close
*/
static int timerlat_u_main(int cpu, struct timerlat_u_params *params)
{
struct sched_param sp = { .sched_priority = 95 };
char buffer[MAX_PATH];
int timerlat_fd;
cpu_set_t set;
int retval;
/*
* This all is only setting up the tool.
*/
CPU_ZERO(&set);
CPU_SET(cpu, &set);
retval = sched_setaffinity(gettid(), sizeof(set), &set);
if (retval == -1) {
debug_msg("Error setting user thread affinity %d, is the CPU online?\n", cpu);
exit(1);
}
if (!params->sched_param) {
retval = sched_setscheduler(0, SCHED_FIFO, &sp);
if (retval < 0)
fatal("Error setting timerlat u default priority: %s", strerror(errno));
} else {
retval = __set_sched_attr(getpid(), params->sched_param);
if (retval) {
/* __set_sched_attr prints an error message, so */
exit(0);
}
}
if (params->cgroup_name) {
retval = set_pid_cgroup(gettid(), params->cgroup_name);
if (!retval) {
err_msg("Error setting timerlat u cgroup pid\n");
pthread_exit(&retval);
}
}
/*
* This is the tool's loop. If you want to use as base for your own tool...
* go ahead.
*/
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)
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, ARRAY_SIZE(buffer));
if (retval < 0)
break;
}
close(timerlat_fd);
debug_msg("Leaving timerlat pid %d on cpu %d\n", gettid(), cpu);
exit(0);
}
/*
* timerlat_u_send_kill - send a kill signal for all processes
*
* Return the number of processes that received the kill.
*/
static int timerlat_u_send_kill(pid_t *procs)
{
int killed = 0;
int i, retval;
for (i = 0; i < nr_cpus; i++) {
if (!procs[i])
continue;
retval = kill(procs[i], SIGKILL);
if (!retval)
killed++;
else
err_msg("Error killing child process %d\n", procs[i]);
}
return killed;
}
/**
* timerlat_u_dispatcher - dispatch one timerlatu/ process per monitored CPU
*
* This is a thread main that will fork one new process for each monitored
* CPU. It will wait for:
*
* - rtla to tell to kill the child processes
* - some child process to die, and the cleanup all the processes
*
* whichever comes first.
*
*/
void *timerlat_u_dispatcher(void *data)
{
struct timerlat_u_params *params = data;
char proc_name[128];
int procs_count = 0;
int retval = 1;
pid_t *procs;
int wstatus;
pid_t pid;
int i;
debug_msg("Dispatching timerlat u procs\n");
procs = calloc(nr_cpus, sizeof(pid_t));
if (!procs)
pthread_exit(&retval);
for (i = 0; i < nr_cpus; i++) {
if (params->set && !CPU_ISSET(i, params->set))
continue;
pid = fork();
/* child */
if (!pid) {
/*
* rename the process
*/
snprintf(proc_name, sizeof(proc_name), "timerlatu/%d", i);
pthread_setname_np(pthread_self(), proc_name);
prctl(PR_SET_NAME, (unsigned long)proc_name, 0, 0, 0);
timerlat_u_main(i, params);
/* timerlat_u_main should exit()! Anyways... */
pthread_exit(&retval);
}
/* parent */
if (pid == -1) {
timerlat_u_send_kill(procs);
debug_msg("Failed to create child processes");
pthread_exit(&retval);
}
procs_count++;
procs[i] = pid;
}
while (params->should_run) {
/* check if processes died */
pid = waitpid(-1, &wstatus, WNOHANG);
if (pid != 0) {
for (i = 0; i < nr_cpus; i++) {
if (procs[i] == pid) {
procs[i] = 0;
procs_count--;
}
}
if (!procs_count)
break;
}
sleep(1);
}
timerlat_u_send_kill(procs);
while (procs_count) {
pid = waitpid(-1, &wstatus, 0);
if (pid == -1) {
err_msg("Failed to monitor child processes");
pthread_exit(&retval);
}
for (i = 0; i < nr_cpus; i++) {
if (procs[i] == pid) {
procs[i] = 0;
procs_count--;
}
}
}
params->stopped_running = 1;
free(procs);
retval = 0;
pthread_exit(&retval);
}
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-2.0
#pragma once
/*
* Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
struct timerlat_u_params {
/* timerlat -> timerlat_u: user-space threads can keep running */
int should_run;
/* timerlat_u -> timerlat: all timerlat_u threads left, no reason to continue */
int stopped_running;
/* threads config */
cpu_set_t *set;
char *cgroup_name;
struct sched_attr *sched_param;
};
void *timerlat_u_dispatcher(void *data);
+597
View File
@@ -0,0 +1,597 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sys/sendfile.h>
#include <tracefs.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "trace.h"
#include "utils.h"
/*
* enable_tracer_by_name - enable a tracer on the given instance
*/
int enable_tracer_by_name(struct tracefs_instance *inst, const char *tracer_name)
{
enum tracefs_tracers tracer;
int retval;
tracer = TRACEFS_TRACER_CUSTOM;
debug_msg("Enabling %s tracer\n", tracer_name);
retval = tracefs_tracer_set(inst, tracer, tracer_name);
if (retval < 0) {
if (errno == ENODEV)
err_msg("Tracer %s not found!\n", tracer_name);
err_msg("Failed to enable the %s tracer\n", tracer_name);
return -1;
}
return 0;
}
/*
* disable_tracer - set nop tracer to the insta
*/
void disable_tracer(struct tracefs_instance *inst)
{
enum tracefs_tracers t = TRACEFS_TRACER_NOP;
int retval;
retval = tracefs_tracer_set(inst, t);
if (retval < 0)
err_msg("Oops, error disabling tracer\n");
}
/*
* create_instance - create a trace instance with *instance_name
*/
struct tracefs_instance *create_instance(char *instance_name)
{
return tracefs_instance_create(instance_name);
}
/*
* destroy_instance - remove a trace instance and free the data
*/
void destroy_instance(struct tracefs_instance *inst)
{
tracefs_instance_destroy(inst);
tracefs_instance_free(inst);
}
/*
* save_trace_to_file - save the trace output of the instance to the file
*/
int save_trace_to_file(struct tracefs_instance *inst, const char *filename)
{
const char *file = "trace";
mode_t mode = 0644;
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) {
err_msg("Failed to open trace file\n");
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;
}
for (;;) {
n_read = read(in_fd, buffer, sizeof(buffer));
if (n_read < 0) {
if (errno == EINTR)
continue;
err_msg("Error reading trace file: %s\n", strerror(errno));
goto out_close;
}
if (n_read == 0)
break;
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:
close(out_fd);
out_close_in:
close(in_fd);
return retval;
}
/*
* collect_registered_events - call the existing callback function for the event
*
* If an event has a registered callback function, call it.
* Otherwise, ignore the event.
*/
int
collect_registered_events(struct tep_event *event, struct tep_record *record,
int cpu, void *context)
{
struct trace_instance *trace = context;
struct trace_seq *s = trace->seq;
trace->processed_events++;
if (!event->handler)
return 0;
event->handler(s, record, event, context);
return 0;
}
/*
* collect_missed_events - record number of missed events
*
* If rtla cannot keep up with events generated by tracer, events are going
* to fall out of the ring buffer.
* Collect how many events were missed so it can be reported to the user.
*/
static int
collect_missed_events(struct tep_event *event, struct tep_record *record,
int cpu, void *context)
{
struct trace_instance *trace = context;
if (trace->missed_events == UINT64_MAX)
return 0;
if (record->missed_events > 0)
trace->missed_events += record->missed_events;
else
/* Events missed but no data on how many */
trace->missed_events = UINT64_MAX;
return 0;
}
/*
* trace_instance_destroy - destroy and free a rtla trace instance
*/
void trace_instance_destroy(struct trace_instance *trace)
{
if (trace->inst) {
disable_tracer(trace->inst);
destroy_instance(trace->inst);
trace->inst = NULL;
}
if (trace->seq) {
free(trace->seq);
trace->seq = NULL;
}
if (trace->tep) {
tep_free(trace->tep);
trace->tep = NULL;
}
}
/*
* trace_instance_init - create an rtla trace instance
*
* It is more than the tracefs instance, as it contains other
* things required for the tracing, such as the local events and
* a seq file.
*
* Note that the trace instance is returned disabled. This allows
* the tool to apply some other configs, like setting priority
* to the kernel threads, before starting generating trace entries.
*/
int trace_instance_init(struct trace_instance *trace, char *tool_name)
{
trace->seq = calloc_fatal(1, sizeof(*trace->seq));
trace_seq_init(trace->seq);
trace->inst = create_instance(tool_name);
if (!trace->inst)
goto out_err;
trace->tep = tracefs_local_events(NULL);
if (!trace->tep)
goto out_err;
/*
* Let the main enable the record after setting some other
* things such as the priority of the tracer's threads.
*/
tracefs_trace_off(trace->inst);
/*
* Collect the number of events missed due to tracefs buffer
* overflow.
*/
trace->missed_events = 0;
tracefs_follow_missed_events(trace->inst,
collect_missed_events,
trace);
trace->processed_events = 0;
return 0;
out_err:
trace_instance_destroy(trace);
return 1;
}
/*
* trace_instance_start - start tracing a given rtla instance
*/
int trace_instance_start(struct trace_instance *trace)
{
return tracefs_trace_on(trace->inst);
}
/*
* trace_instance_stop - stop tracing a given rtla instance
*/
int trace_instance_stop(struct trace_instance *trace)
{
return tracefs_trace_off(trace->inst);
}
/*
* trace_events_free - free a list of trace events
*/
static void trace_events_free(struct trace_events *events)
{
struct trace_events *tevent = events;
struct trace_events *free_event;
while (tevent) {
free_event = tevent;
tevent = tevent->next;
if (free_event->filter)
free(free_event->filter);
if (free_event->trigger)
free(free_event->trigger);
free(free_event->system);
free(free_event);
}
}
/*
* trace_event_alloc - alloc and parse a single trace event
*/
struct trace_events *trace_event_alloc(const char *event_string)
{
struct trace_events *tevent;
tevent = calloc_fatal(1, sizeof(*tevent));
tevent->system = strdup_fatal(event_string);
tevent->event = strstr(tevent->system, ":");
if (tevent->event) {
*tevent->event = '\0';
tevent->event = &tevent->event[1];
}
return tevent;
}
/*
* trace_event_add_filter - record an event filter
*/
void trace_event_add_filter(struct trace_events *event, char *filter)
{
if (event->filter)
free(event->filter);
event->filter = strdup_fatal(filter);
}
/*
* trace_event_add_trigger - record an event trigger action
*/
void trace_event_add_trigger(struct trace_events *event, char *trigger)
{
if (event->trigger)
free(event->trigger);
event->trigger = strdup_fatal(trigger);
}
/*
* trace_event_disable_filter - disable an event filter
*/
static void trace_event_disable_filter(struct trace_instance *instance,
struct trace_events *tevent)
{
char filter[MAX_PATH];
int retval;
if (!tevent->filter)
return;
if (!tevent->filter_enabled)
return;
debug_msg("Disabling %s:%s filter %s\n", tevent->system,
tevent->event ? : "*", tevent->filter);
snprintf(filter, ARRAY_SIZE(filter), "!%s\n", tevent->filter);
retval = tracefs_event_file_write(instance->inst, tevent->system,
tevent->event, "filter", filter);
if (retval < 0)
err_msg("Error disabling %s:%s filter %s\n", tevent->system,
tevent->event ? : "*", tevent->filter);
}
/*
* trace_event_save_hist - save the content of an event hist
*
* If the trigger is a hist: one, save the content of the hist file.
*/
static void trace_event_save_hist(struct trace_instance *instance,
struct trace_events *tevent)
{
size_t index, hist_len;
mode_t mode = 0644;
char path[MAX_PATH];
char *hist;
int out_fd;
if (!tevent)
return;
/* trigger enables hist */
if (!tevent->trigger)
return;
/* is this a hist: trigger? */
if (!str_has_prefix(tevent->trigger, "hist:"))
return;
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);
out_fd = creat(path, mode);
if (out_fd < 0) {
err_msg(" Failed to create %s output file\n", path);
return;
}
hist = tracefs_event_file_read(instance->inst, tevent->system, tevent->event, "hist", 0);
if (!hist) {
err_msg(" Failed to read %s:%s hist file\n", tevent->system, tevent->event);
goto out_close;
}
index = 0;
hist_len = strlen(hist);
do {
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:
close(out_fd);
}
/*
* trace_event_disable_trigger - disable an event trigger
*/
static void trace_event_disable_trigger(struct trace_instance *instance,
struct trace_events *tevent)
{
char trigger[MAX_PATH];
int retval;
if (!tevent->trigger)
return;
if (!tevent->trigger_enabled)
return;
debug_msg("Disabling %s:%s trigger %s\n", tevent->system,
tevent->event ? : "*", tevent->trigger);
trace_event_save_hist(instance, tevent);
snprintf(trigger, ARRAY_SIZE(trigger), "!%s\n", tevent->trigger);
retval = tracefs_event_file_write(instance->inst, tevent->system,
tevent->event, "trigger", trigger);
if (retval < 0)
err_msg("Error disabling %s:%s trigger %s\n", tevent->system,
tevent->event ? : "*", tevent->trigger);
}
/*
* trace_events_disable - disable all trace events
*/
void trace_events_disable(struct trace_instance *instance,
struct trace_events *events)
{
struct trace_events *tevent = events;
if (!events)
return;
while (tevent) {
debug_msg("Disabling event %s:%s\n", tevent->system, tevent->event ? : "*");
if (tevent->enabled) {
trace_event_disable_filter(instance, tevent);
trace_event_disable_trigger(instance, tevent);
tracefs_event_disable(instance->inst, tevent->system, tevent->event);
}
tevent->enabled = 0;
tevent = tevent->next;
}
}
/*
* trace_event_enable_filter - enable an event filter associated with an event
*/
static int trace_event_enable_filter(struct trace_instance *instance,
struct trace_events *tevent)
{
char filter[MAX_PATH];
int retval;
if (!tevent->filter)
return 0;
if (!tevent->event) {
err_msg("Filter %s applies only for single events, not for all %s:* events\n",
tevent->filter, tevent->system);
return 1;
}
snprintf(filter, ARRAY_SIZE(filter), "%s\n", tevent->filter);
debug_msg("Enabling %s:%s filter %s\n", tevent->system,
tevent->event ? : "*", tevent->filter);
retval = tracefs_event_file_write(instance->inst, tevent->system,
tevent->event, "filter", filter);
if (retval < 0) {
err_msg("Error enabling %s:%s filter %s\n", tevent->system,
tevent->event ? : "*", tevent->filter);
return 1;
}
tevent->filter_enabled = 1;
return 0;
}
/*
* trace_event_enable_trigger - enable an event trigger associated with an event
*/
static int trace_event_enable_trigger(struct trace_instance *instance,
struct trace_events *tevent)
{
char trigger[MAX_PATH];
int retval;
if (!tevent->trigger)
return 0;
if (!tevent->event) {
err_msg("Trigger %s applies only for single events, not for all %s:* events\n",
tevent->trigger, tevent->system);
return 1;
}
snprintf(trigger, ARRAY_SIZE(trigger), "%s\n", tevent->trigger);
debug_msg("Enabling %s:%s trigger %s\n", tevent->system,
tevent->event ? : "*", tevent->trigger);
retval = tracefs_event_file_write(instance->inst, tevent->system,
tevent->event, "trigger", trigger);
if (retval < 0) {
err_msg("Error enabling %s:%s trigger %s\n", tevent->system,
tevent->event ? : "*", tevent->trigger);
return 1;
}
tevent->trigger_enabled = 1;
return 0;
}
/*
* trace_events_enable - enable all events
*/
int trace_events_enable(struct trace_instance *instance,
struct trace_events *events)
{
struct trace_events *tevent = events;
int retval;
while (tevent) {
debug_msg("Enabling event %s:%s\n", tevent->system, tevent->event ? : "*");
retval = tracefs_event_enable(instance->inst, tevent->system, tevent->event);
if (retval < 0) {
err_msg("Error enabling event %s:%s\n", tevent->system,
tevent->event ? : "*");
return 1;
}
retval = trace_event_enable_filter(instance, tevent);
if (retval)
return 1;
retval = trace_event_enable_trigger(instance, tevent);
if (retval)
return 1;
tevent->enabled = 1;
tevent = tevent->next;
}
return 0;
}
/*
* trace_events_destroy - disable and free all trace events
*/
void trace_events_destroy(struct trace_instance *instance,
struct trace_events *events)
{
if (!events)
return;
trace_events_disable(instance, events);
trace_events_free(events);
}
/*
* trace_set_buffer_size - set the per-cpu tracing buffer size.
*/
int trace_set_buffer_size(struct trace_instance *trace, int size)
{
int retval;
debug_msg("Setting trace buffer size to %d Kb\n", size);
retval = tracefs_instance_set_buffer_size(trace->inst, size, -1);
if (retval)
err_msg("Error setting trace buffer size\n");
return retval;
}
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0
#include <tracefs.h>
#include <stddef.h>
struct trace_events {
struct trace_events *next;
char *system;
char *event;
char *filter;
char *trigger;
char enabled;
char filter_enabled;
char trigger_enabled;
};
struct trace_instance {
struct tracefs_instance *inst;
struct tep_handle *tep;
struct trace_seq *seq;
unsigned long long missed_events;
unsigned long long processed_events;
};
int trace_instance_init(struct trace_instance *trace, char *tool_name);
int trace_instance_start(struct trace_instance *trace);
int trace_instance_stop(struct trace_instance *trace);
void trace_instance_destroy(struct trace_instance *trace);
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);
struct tracefs_instance *create_instance(char *instance_name);
void destroy_instance(struct tracefs_instance *inst);
int save_trace_to_file(struct tracefs_instance *inst, const char *filename);
int collect_registered_events(struct tep_event *tep, struct tep_record *record,
int cpu, void *context);
struct trace_events *trace_event_alloc(const char *event_string);
void trace_events_disable(struct trace_instance *instance,
struct trace_events *events);
void trace_events_destroy(struct trace_instance *instance,
struct trace_events *events);
int trace_events_enable(struct trace_instance *instance,
struct trace_events *events);
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);
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sched.h>
#include <stdbool.h>
#include <stdlib.h>
#include <linux/container_of.h>
/*
* '18446744073709551615\0'
*/
#define BUFF_U64_STR_SIZE 24
#define MAX_PATH 1024
#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;
}
extern bool 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);
long long get_llong_from_str(char *start);
static inline void
update_min(unsigned long long *a, unsigned long long *b)
{
if (*a > *b)
*a = *b;
}
static inline void
update_max(unsigned long long *a, unsigned long long *b)
{
if (*a < *b)
*a = *b;
}
static inline void
update_sum(unsigned long long *a, unsigned long long *b)
{
*a += *b;
}
#ifndef SCHED_ATTR_SIZE_VER0
struct sched_attr {
uint32_t size;
uint32_t sched_policy;
uint64_t sched_flags;
int32_t sched_nice;
uint32_t sched_priority;
uint64_t sched_runtime;
uint64_t sched_deadline;
uint64_t sched_period;
};
#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);
void free_cpu_idle_disable_states(void);
int set_deepest_cpu_idle_state(unsigned int cpu, unsigned int state);
static inline int have_libcpupower_support(void) { return 1; }
#else
static inline int save_cpu_idle_disable_state(unsigned int cpu) { return -1; }
static inline int restore_cpu_idle_disable_state(unsigned int cpu) { return -1; }
static inline void free_cpu_idle_disable_states(void) { }
static inline int set_deepest_cpu_idle_state(unsigned int cpu, unsigned int state) { return -1; }
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 */
};
@@ -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;
}
@@ -0,0 +1,166 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
test_begin() {
# Count tests to allow the test harness to double-check if all were
# included correctly.
ctr=0
# Set test directory to the directory of the script
scriptfile=$(realpath "$0")
testdir=$(dirname "$scriptfile")
[ -z "$RTLA" ] && RTLA="./rtla"
[ -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
# Create a temporary directory to contain rtla output
tmpdir=$(mktemp -d)
pushd $tmpdir >/dev/null
# Run rtla; in case of failure, include its output as comment
# in the test results.
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
# 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
# Remove temporary directory
popd >/dev/null
rm -r $tmpdir
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"
}
check_top_hist() {
# Test one command with both "top" and "hist" tools, replacing "TOOL" in
# command with either "top" or "hist" respectively, and prefixing the test
# names with "top " and "hist ".
check "top $1" "$(echo "$2" | sed 's/TOOL/top/g')" "${@:3}"
check "hist $1" "$(echo "$2" | sed 's/TOOL/hist/g')" "${@:3}"
}
check_top_q_hist() {
# Same as above, but pass "-q" to top so that strings printed in main
# loop are on their own line for top too, not only for hist.
check "top $1" "$(echo "$2" | sed 's/TOOL/top -q/g')" "${@:3}"
check "hist $1" "$(echo "$2" | sed 's/TOOL/hist/g')" "${@:3}"
}
set_timeout() {
TIMEOUT="timeout -v -k 15s $1"
}
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.
[ -z "$TEST_COUNT" ] && TEST_COUNT=$ctr exec bash $0 || true
}
# Avoid any environmental discrepancies
export LC_ALL=C
unset_timeout
@@ -0,0 +1,22 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source tests/engine.sh
test_begin
set_timeout 2m
check "verify help page" \
"hwnoise --help" 129 "Usage: rtla hwnoise"
check "detect noise higher than one microsecond" \
"hwnoise -c 0 -T 1 -d 5s -q" 0
check "set the automatic trace mode" \
"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 10s -q"
check "stop the trace if a single sample is higher than 1 us" \
"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 10s" \
0 "Saving event osnoise:irq_noise hist to osnoise_irq_noise_hist.txt"
test_end
@@ -0,0 +1,73 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source tests/engine.sh
test_begin
set_timeout 2m
check "verify help page" \
"osnoise --help" 129 "osnoise version"
check_top_hist "verify help page" \
"osnoise TOOL --help" 129 "rtla osnoise"
check_top_q_hist "verify the --stop/-s param" \
"osnoise TOOL -s 30 -T 1" 2 "osnoise hit stop tracing"
check_top_q_hist "verify the --trace param" \
"osnoise TOOL -s 30 -T 1 -t" 2 "Saving trace to osnoise_trace.txt"
# Thread tests
check_top_q_hist "verify the --priority/-P param" \
"osnoise TOOL -P F:1 -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=\"$testdir/scripts/check-priority.sh SCHED_FIFO 1\"" \
2 "Priorities are set correctly"
check_top_q_hist "verify the -C/--cgroup param" \
"osnoise TOOL -C -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=\"$testdir/scripts/check-cgroup-match.sh\"" \
2 "cgroup matches for all workload PIDs"
check_top_q_hist "verify the -c/--cpus param" \
"osnoise TOOL -P F:1 -c 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=$testdir/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$"
check_top_q_hist "verify the -H/--house-keeping param" \
"osnoise TOOL -P F:1 -H 0 -r 900000 -d 10s -S 1 --on-threshold shell,command=$testdir/scripts/check-housekeeping-cpus.sh" 2 "^Affinity of threads: 0$"
# Histogram tests
check "hist with -b/--bucket-size" \
"osnoise hist -b 1 -d 1s"
check "hist with -E/--entries" \
"osnoise hist -E 10 -d 1s"
check "hist with -E/--entries out of range" \
"osnoise hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 10000000$"
check "hist with --no-header" \
"osnoise hist --no-header -d 1s" 0 "" "RTLA osnoise histogram"
check "hist with --with-zeros" \
"osnoise hist --with-zeros -b 100000 -E 21 -d 1s" 0 '^2000000\s+0\s+'
check "hist with --no-index" \
"osnoise hist --no-index --with-zeros -d 1s" 0 "" "^count:"
check "hist with --no-summary" \
"osnoise hist --no-summary -d 1s" 0 "" "^count:"
# 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_top_q_hist "trace output through -t with custom filename" \
"osnoise TOOL -S 2 -t custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
check_top_q_hist "trace output through --on-threshold trace" \
"osnoise TOOL -S 2 --on-threshold trace" 2 "^ Saving trace to osnoise_trace.txt$"
check_top_q_hist "trace output through --on-threshold trace with custom filename" \
"osnoise TOOL -S 2 --on-threshold trace,file=custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
check_top_q_hist "exec command" \
"osnoise TOOL -S 2 --on-threshold shell,command='echo TestOutput'" 2 "^TestOutput$"
check_top_q_hist "multiple actions" \
"osnoise TOOL -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_top_q_hist "with continue" \
"osnoise TOOL -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
check_top_q_hist "with conditional continue" \
"osnoise TOOL -S 2 --on-threshold shell,command='if [ -f a ]; then echo 2; exit 1; else echo -n 1; touch a; fi' --on-threshold continue" 2 "^12$" "^2$"
check_top_hist "with trace output at end" \
"osnoise TOOL -d 1s --on-end trace" 0 "^ Saving trace to osnoise_trace.txt$"
test_end
@@ -0,0 +1,17 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(dirname $0)/lib/get_workload_pids.sh"
rtla_pid=$(echo $(ps -o ppid= $$))
rtla_cgroup=$(</proc/$rtla_pid/cgroup)
echo "RTLA cgroup: $rtla_cgroup"
for pid in $(get_workload_pids)
do
pid_cgroup=$(</proc/$pid/cgroup)
echo "PID $pid cgroup: $pid_cgroup"
if ! [ "$pid_cgroup" = "$rtla_cgroup" ]
then
echo "Mismatch!"
exit 0
fi
done
echo "cgroup matches for all workload PIDs"
@@ -0,0 +1,9 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(dirname $0)/lib/get_workload_pids.sh"
echo -n "Affinity of threads: "
for pid in $(get_workload_pids)
do
echo -n $(taskset -c -p $pid | cut -d ':' -f 2)
done
echo
@@ -0,0 +1,4 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
pid=$(ps -o ppid= $$)
echo "Affinity of threads:$(taskset -c -p $pid | cut -d ':' -f 2)"
@@ -0,0 +1,8 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(dirname $0)/lib/get_workload_pids.sh"
for pid in $(get_workload_pids)
do
chrt -p $pid | cut -d ':' -f 2 | head -n1 | grep "^ $1\$" >/dev/null
chrt -p $pid | cut -d ':' -f 2 | tail -n1 | grep "^ $2\$" >/dev/null
done && echo "Priorities are set correctly"
@@ -0,0 +1,16 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(dirname $0)/lib/get_workload_pids.sh"
kthreadd_pid=$(pgrep ^kthreadd$)
cnt_kernel=0
cnt_user=0
for pid in $(get_workload_pids)
do
if [ "$(echo $(ps -o ppid= $pid))" = "$kthreadd_pid" ]
then
((++cnt_kernel))
else
((++cnt_user))
fi
done
echo "$cnt_kernel kernel threads, $cnt_user user threads"
@@ -0,0 +1,11 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
get_workload_pids() {
local shell_pid=$$
local rtla_pid=$(ps -o ppid= $shell_pid)
# kernel threads
pgrep -P $(pgrep ^kthreadd$) -f '^\[?(osnoise|timerlat)/[0-9]+\]?$'
# user threads
pgrep -P $rtla_pid | grep -v "^$shell_pid$"
}
@@ -0,0 +1,118 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
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" 129 "timerlat version"
check_top_hist "verify help page" \
"timerlat TOOL --help" 129 "rtla timerlat"
check_top_hist "verify -s/--stack" \
"timerlat TOOL -s 3 -T 10 -t" 2 "Blocking thread stack trace"
check_top_hist "test in nanoseconds" \
"timerlat TOOL -i 2 -c 0 -n -d 10s" 2 "ns"
check_top_hist "set the automatic trace mode" \
"timerlat TOOL -a 5" 2 "analyzing it"
check_top_hist "dump tasks" \
"timerlat TOOL -a 5 --dump-tasks" 2 "Printing CPU tasks"
check "verify --aa-only stop on threshold" \
"timerlat top --aa-only 5" 2 "analyzing it" "Timer Latency"
check "verify --aa-only max latency" \
"timerlat top --aa-only 2000000 -d 1s" 0 "^ Max latency was" "Timer Latency"
check_top_hist "disable auto-analysis" \
"timerlat TOOL -s 3 -T 10 -t --no-aa" 2 "" "analyzing it"
# Thread tests
check_top_hist "verify -P/--priority" \
"timerlat TOOL -P F:1 -c 0 -d 10s -T 1 --on-threshold shell,command=\"$testdir/scripts/check-priority.sh SCHED_FIFO 1\"" \
2 "Priorities are set correctly"
check_top_hist "verify -C/--cgroup" \
"timerlat TOOL -k -C -c 0 -d 10s -T 1 --on-threshold shell,command=\"$testdir/scripts/check-cgroup-match.sh\"" \
2 "cgroup matches for all workload PIDs"
check_top_q_hist "verify -c/--cpus" \
"timerlat TOOL -c 0 -d 10s -T 1 --on-threshold shell,command=$testdir/scripts/check-cpus.sh" 2 "^Affinity of threads: 0$"
check_top_q_hist "verify -H/--house-keeping" \
"timerlat TOOL -H 0 -d 10s -T 1 --on-threshold shell,command=$testdir/scripts/check-housekeeping-cpus.sh" 2 "^Affinity of threads: 0$"
check_top_q_hist "verify -k/--kernel-threads" \
"timerlat TOOL -k -c 0 -d 10s -T 1 --on-threshold shell,command=$testdir/scripts/check-user-kernel-threads.sh" 2 "1 kernel threads, 0 user threads"
check_top_q_hist "verify -u/--user-threads" \
"timerlat TOOL -u -c 0 -d 10s -T 1 --on-threshold shell,command=$testdir/scripts/check-user-kernel-threads.sh" 2 "0 kernel threads, 1 user threads"
# Histogram tests
check "hist with -b/--bucket-size" \
"timerlat hist -b 1 -d 1s"
check "hist with -E/--entries" \
"timerlat hist -E 10 -d 1s"
check "hist with -E/--entries out of range" \
"timerlat hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 10000000$"
check "hist with --no-header" \
"timerlat hist --no-header -d 1s" 0 "" "RTLA timerlat histogram"
check "hist with --with-zeros" \
"timerlat hist --with-zeros -b 100000 -E 21 -d 1s" 0 '^2000000\s+0\s+'
check "hist with --no-index" \
"timerlat hist --no-index --with-zeros -d 1s" 0 "" "^count:"
check "hist with --no-summary" \
"timerlat hist --no-summary -d 1s" 0 "" "^ALL:"
check "hist with --no-irq" \
"timerlat hist --no-irq -d 1s" 0 "" "IRQ-"
check "hist with --no-thread" \
"timerlat hist --no-thread -d 1s" 0 "" "Thr-"
# Actions tests
check_top_q_hist "trace output through -t" \
"timerlat TOOL -T 2 -t" 2 "^ Saving trace to timerlat_trace.txt$"
check_top_q_hist "trace output through -t with custom filename" \
"timerlat TOOL -T 2 -t custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
check_top_q_hist "trace output through --on-threshold trace" \
"timerlat TOOL -T 2 --on-threshold trace" 2 "^ Saving trace to timerlat_trace.txt$"
check_top_q_hist "trace output through --on-threshold trace with custom filename" \
"timerlat TOOL -T 2 --on-threshold trace,file=custom_filename.txt" 2 "^ Saving trace to custom_filename.txt$"
check_top_q_hist "exec command" \
"timerlat TOOL -T 2 --on-threshold shell,command='echo TestOutput'" 2 "^TestOutput$"
check_top_q_hist "multiple actions" \
"timerlat TOOL -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_top_q_hist "with continue" \
"timerlat TOOL -T 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$"
check_top_q_hist "with conditional continue" \
"timerlat TOOL -T 2 --on-threshold shell,command='if [ -f a ]; then echo 2; exit 1; else echo -n 1; touch a; fi' --on-threshold continue" 2 "^12$" "^2$"
check_top_hist "with trace output at end" \
"timerlat TOOL -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_top_q_hist "with BPF action program (BPF mode)" \
"timerlat TOOL -T 2 --bpf-action $testdir/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_top_q_hist "with BPF action program (non-BPF mode)" \
"timerlat TOOL -T 2 --bpf-action $testdir/bpf/bpf_action_map.o" \
1 "BPF actions are not supported in tracefs-only mode"
fi
done
test_end
@@ -0,0 +1,8 @@
unit_tests-y += utils.o
unit_tests-y += actions.o
unit_tests-y += unit_tests.o
unit_tests-y += osnoise_top_cli.o
unit_tests-y += osnoise_hist_cli.o
unit_tests-y += timerlat_top_cli.o
unit_tests-y += timerlat_hist_cli.o
unit_tests-y += cli_opt_callback.o
@@ -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) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck
$(UNIT_TESTS_IN): fixdep $(LIBSUBCMD_INCLUDES)
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
@@ -0,0 +1,393 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <check.h>
#include <signal.h>
#include "../../src/actions.h"
static struct actions actions_fixture;
static void actions_fixture_setup(void)
{
actions_init(&actions_fixture);
}
static void actions_fixture_teardown(void)
{
actions_destroy(&actions_fixture);
}
START_TEST(test_actions_init)
{
struct actions actions;
actions_init(&actions);
ck_assert_int_eq(actions.len, 0);
ck_assert_int_eq(actions.size, action_default_size);
ck_assert(!actions.continue_flag);
ck_assert_ptr_eq(actions.trace_output_inst, NULL);
}
END_TEST
START_TEST(test_actions_destroy)
{
struct actions actions;
actions_init(&actions);
actions_destroy(&actions);
}
END_TEST
START_TEST(test_actions_reallocate)
{
struct actions actions;
int i;
actions_init(&actions);
ck_assert_int_eq(actions.len, 0);
ck_assert_int_eq(actions.size, action_default_size);
/* Fill size of actions array */
for (i = 0; i < action_default_size; i++)
actions_add_continue(&actions);
ck_assert_int_eq(actions.len, action_default_size);
ck_assert_int_eq(actions.size, action_default_size);
/* Add one more action to trigger reallocation */
actions_add_continue(&actions);
ck_assert_int_eq(actions.len, action_default_size + 1);
ck_assert_int_eq(actions.size, action_default_size * 2);
actions_destroy(&actions);
}
END_TEST
START_TEST(test_actions_add_trace_output)
{
actions_add_trace_output(&actions_fixture, "trace_output.txt");
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace_output.txt");
ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
}
END_TEST
START_TEST(test_actions_add_signal)
{
actions_add_signal(&actions_fixture, SIGINT, 1234);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SIGNAL);
ck_assert_int_eq(actions_fixture.list[0].signal, SIGINT);
ck_assert_int_eq(actions_fixture.list[0].pid, 1234);
ck_assert(actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_add_shell)
{
actions_add_shell(&actions_fixture, "echo Hello");
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SHELL);
ck_assert_str_eq(actions_fixture.list[0].command, "echo Hello");
ck_assert(actions_fixture.present[ACTION_SHELL]);
}
END_TEST
START_TEST(test_actions_add_continue)
{
actions_add_continue(&actions_fixture);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_CONTINUE);
ck_assert(actions_fixture.present[ACTION_CONTINUE]);
}
END_TEST
START_TEST(test_actions_add_multiple_same_action)
{
actions_add_trace_output(&actions_fixture, "trace1.txt");
actions_add_trace_output(&actions_fixture, "trace2.txt");
ck_assert_int_eq(actions_fixture.len, 2);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace1.txt");
ck_assert_int_eq(actions_fixture.list[1].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions_fixture.list[1].trace_output, "trace2.txt");
ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
}
END_TEST
START_TEST(test_actions_add_multiple_different_action)
{
actions_add_trace_output(&actions_fixture, "trace_output.txt");
actions_add_signal(&actions_fixture, SIGINT, 1234);
ck_assert_int_eq(actions_fixture.len, 2);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace_output.txt");
ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
ck_assert_int_eq(actions_fixture.list[1].type, ACTION_SIGNAL);
ck_assert_int_eq(actions_fixture.list[1].signal, SIGINT);
ck_assert_int_eq(actions_fixture.list[1].pid, 1234);
ck_assert(actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_parse_trace_output)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "trace", "trace.txt"), 0);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace.txt");
ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
}
END_TEST
START_TEST(test_actions_parse_trace_output_arg)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "trace,file=trace2.txt", "trace1.txt"), 0);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions_fixture.list[0].trace_output, "trace2.txt");
ck_assert(actions_fixture.present[ACTION_TRACE_OUTPUT]);
}
END_TEST
START_TEST(test_actions_parse_trace_output_arg_bad)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "trace,foo=bar", "trace_output.txt"), -1);
ck_assert_int_eq(actions_fixture.len, 0);
ck_assert(!actions_fixture.present[ACTION_TRACE_OUTPUT]);
}
END_TEST
START_TEST(test_actions_parse_signal)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "signal,num=1,pid=1234", NULL), 0);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SIGNAL);
ck_assert_int_eq(actions_fixture.list[0].signal, 1);
ck_assert_int_eq(actions_fixture.list[0].pid, 1234);
ck_assert(actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_parse_signal_swapped)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "signal,pid=1234,num=1", NULL), 0);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SIGNAL);
ck_assert_int_eq(actions_fixture.list[0].signal, 1);
ck_assert_int_eq(actions_fixture.list[0].pid, 1234);
ck_assert(actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_parse_signal_parent)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "signal,pid=parent,num=1", NULL), 0);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SIGNAL);
ck_assert_int_eq(actions_fixture.list[0].signal, 1);
ck_assert_int_eq(actions_fixture.list[0].pid, -1);
ck_assert(actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_parse_signal_no_arg)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "signal", NULL), -1);
ck_assert_int_eq(actions_fixture.len, 0);
ck_assert(!actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_parse_signal_no_pid)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "signal,num=1", NULL), -1);
ck_assert_int_eq(actions_fixture.len, 0);
ck_assert(!actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_parse_signal_no_num)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "signal,pid=1234", NULL), -1);
ck_assert_int_eq(actions_fixture.len, 0);
ck_assert(!actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_parse_signal_arg_bad)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "signal,foo=bar", NULL), -1);
ck_assert_int_eq(actions_fixture.len, 0);
ck_assert(!actions_fixture.present[ACTION_SIGNAL]);
}
END_TEST
START_TEST(test_actions_parse_shell)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "shell,command=echo Hello", NULL), 0);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_SHELL);
ck_assert_str_eq(actions_fixture.list[0].command, "echo Hello");
ck_assert(actions_fixture.present[ACTION_SHELL]);
}
END_TEST
START_TEST(test_actions_parse_shell_no_arg)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "shell", NULL), -1);
ck_assert_int_eq(actions_fixture.len, 0);
ck_assert(!actions_fixture.present[ACTION_SHELL]);
}
END_TEST
START_TEST(test_actions_parse_shell_arg_bad)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "shell,foo=bar", NULL), -1);
ck_assert_int_eq(actions_fixture.len, 0);
ck_assert(!actions_fixture.present[ACTION_SHELL]);
}
END_TEST
START_TEST(test_actions_parse_continue)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "continue", NULL), 0);
ck_assert_int_eq(actions_fixture.len, 1);
ck_assert_int_eq(actions_fixture.list[0].type, ACTION_CONTINUE);
ck_assert(actions_fixture.present[ACTION_CONTINUE]);
}
END_TEST
START_TEST(test_actions_parse_continue_arg_bad)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "continue,foo=bar", NULL), -1);
ck_assert_int_eq(actions_fixture.len, 0);
ck_assert(!actions_fixture.present[ACTION_CONTINUE]);
}
END_TEST
START_TEST(test_actions_parse_invalid)
{
ck_assert_int_eq(actions_parse(&actions_fixture, "foobar", NULL), -1);
ck_assert_int_eq(actions_fixture.len, 0);
}
END_TEST
START_TEST(test_actions_perform_continue)
{
actions_add_continue(&actions_fixture);
ck_assert_int_eq(actions_perform(&actions_fixture), 0);
ck_assert(actions_fixture.continue_flag);
}
END_TEST
START_TEST(test_actions_perform_continue_after_successful_shell_command)
{
actions_add_shell(&actions_fixture, "exit 0");
actions_add_continue(&actions_fixture);
ck_assert_int_eq(actions_perform(&actions_fixture), 0 << 8);
ck_assert(actions_fixture.continue_flag);
}
END_TEST
START_TEST(test_actions_perform_continue_after_failed_shell_command)
{
actions_add_shell(&actions_fixture, "exit 1");
actions_add_continue(&actions_fixture);
ck_assert_int_eq(actions_perform(&actions_fixture), 1 << 8);
ck_assert(!actions_fixture.continue_flag);
}
END_TEST
START_TEST(test_actions_perform_continue_unset_flag)
{
actions_fixture.continue_flag = true;
actions_add_shell(&actions_fixture, "exit 1");
actions_add_continue(&actions_fixture);
ck_assert_int_eq(actions_perform(&actions_fixture), 1 << 8);
ck_assert(!actions_fixture.continue_flag);
}
END_TEST
Suite *actions_suite(void)
{
Suite *s = suite_create("actions");
TCase *tc;
tc = tcase_create("alloc");
tcase_add_test(tc, test_actions_init);
tcase_add_test(tc, test_actions_destroy);
tcase_add_test(tc, test_actions_reallocate);
suite_add_tcase(s, tc);
tc = tcase_create("add");
tcase_add_checked_fixture(tc, actions_fixture_setup, actions_fixture_teardown);
tcase_add_test(tc, test_actions_add_trace_output);
tcase_add_test(tc, test_actions_add_signal);
tcase_add_test(tc, test_actions_add_shell);
tcase_add_test(tc, test_actions_add_continue);
tcase_add_test(tc, test_actions_add_multiple_same_action);
tcase_add_test(tc, test_actions_add_multiple_different_action);
suite_add_tcase(s, tc);
tc = tcase_create("parse");
tcase_add_checked_fixture(tc, actions_fixture_setup, actions_fixture_teardown);
tcase_add_test(tc, test_actions_parse_trace_output);
tcase_add_test(tc, test_actions_parse_trace_output_arg);
tcase_add_test(tc, test_actions_parse_trace_output_arg_bad);
tcase_add_test(tc, test_actions_parse_signal);
tcase_add_test(tc, test_actions_parse_signal_swapped);
tcase_add_test(tc, test_actions_parse_signal_parent);
tcase_add_test(tc, test_actions_parse_signal_no_arg);
tcase_add_test(tc, test_actions_parse_signal_no_pid);
tcase_add_test(tc, test_actions_parse_signal_no_num);
tcase_add_test(tc, test_actions_parse_signal_arg_bad);
tcase_add_test(tc, test_actions_parse_shell);
tcase_add_test(tc, test_actions_parse_shell_no_arg);
tcase_add_test(tc, test_actions_parse_shell_arg_bad);
tcase_add_test(tc, test_actions_parse_continue);
tcase_add_test(tc, test_actions_parse_continue_arg_bad);
tcase_add_test(tc, test_actions_parse_invalid);
suite_add_tcase(s, tc);
tc = tcase_create("perform");
tcase_add_checked_fixture(tc, actions_fixture_setup, actions_fixture_teardown);
tcase_add_test(tc, test_actions_perform_continue);
tcase_add_test(tc, test_actions_perform_continue_after_successful_shell_command);
tcase_add_test(tc, test_actions_perform_continue_after_failed_shell_command);
tcase_add_test(tc, test_actions_perform_continue_unset_flag);
suite_add_tcase(s, tc);
return s;
}
@@ -0,0 +1,716 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <stdio.h>
#include <check.h>
#define RTLA_ALLOW_CLI_P_H
#include "../../src/cli_p.h"
#include "cli_params_assert.h"
#define TEST_CALLBACK(value, cb) OPT_CALLBACK('t', "test", value, "test value", "test help", cb)
START_TEST(test_opt_llong_callback_simple)
{
long long test_value = 0;
const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
ck_assert_int_eq(opt_llong_callback(&opt, "1234567890", 0), 0);
ck_assert_int_eq(test_value, 1234567890);
}
END_TEST
START_TEST(test_opt_llong_callback_max)
{
long long test_value = 0;
const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
ck_assert_int_eq(opt_llong_callback(&opt, "9223372036854775807", 0), 0);
ck_assert_int_eq(test_value, 9223372036854775807LL);
}
END_TEST
START_TEST(test_opt_llong_callback_min)
{
long long test_value = 0;
const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
ck_assert_int_eq(opt_llong_callback(&opt, "-9223372036854775808", 0), 0);
ck_assert_int_eq(test_value, ~9223372036854775807LL);
}
END_TEST
START_TEST(test_opt_int_callback_simple)
{
int test_value = 0;
const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
ck_assert_int_eq(opt_int_callback(&opt, "1234567890", 0), 0);
ck_assert_int_eq(test_value, 1234567890);
}
END_TEST
START_TEST(test_opt_int_callback_max)
{
int test_value = 0;
const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
ck_assert_int_eq(opt_int_callback(&opt, "2147483647", 0), 0);
ck_assert_int_eq(test_value, 2147483647);
}
END_TEST
START_TEST(test_opt_int_callback_min)
{
int test_value = 0;
const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
ck_assert_int_eq(opt_int_callback(&opt, "-2147483648", 0), 0);
ck_assert_int_eq(test_value, -2147483648);
}
END_TEST
START_TEST(test_opt_int_callback_non_numeric)
{
int test_value = 0;
const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
ck_assert_int_eq(opt_int_callback(&opt, "abc", 0), -1);
ck_assert_int_eq(test_value, 0);
}
END_TEST
START_TEST(test_opt_int_callback_non_numeric_suffix)
{
int test_value = 0;
const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
ck_assert_int_eq(opt_int_callback(&opt, "1234567890abc", 0), -1);
ck_assert_int_eq(test_value, 0);
}
END_TEST
START_TEST(test_opt_cpus_cb)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_cpus_cb);
nr_cpus = 4;
ck_assert_int_eq(opt_cpus_cb(&opt, "0-3", 0), 0);
ck_assert_str_eq(params.cpus, "0-3");
}
END_TEST
START_TEST(test_opt_cpus_cb_invalid)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_cpus_cb);
nr_cpus = 4;
assert(freopen("/dev/null", "w", stderr));
opt_cpus_cb(&opt, "0-3,5", 0);
}
END_TEST
START_TEST(test_opt_cgroup_cb)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_cgroup_cb);
ck_assert_int_eq(opt_cgroup_cb(&opt, "cgroup", 0), 0);
ck_assert_int_eq(params.cgroup, 1);
ck_assert_str_eq(params.cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_opt_cgroup_cb_equals)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_cgroup_cb);
ck_assert_int_eq(opt_cgroup_cb(&opt, "=cgroup", 0), 0);
ck_assert_int_eq(params.cgroup, 1);
ck_assert_str_eq(params.cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_opt_duration_cb)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_duration_cb);
ck_assert_int_eq(opt_duration_cb(&opt, "1m", 0), 0);
ck_assert_int_eq(params.duration, 60);
}
END_TEST
START_TEST(test_opt_duration_cb_invalid)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_duration_cb);
assert(freopen("/dev/null", "w", stderr));
opt_duration_cb(&opt, "abc", 0);
}
END_TEST
START_TEST(test_opt_event_cb)
{
struct trace_events *events = NULL;
const struct option opt = TEST_CALLBACK(&events, opt_event_cb);
ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_switch", 0), 0);
ck_assert_str_eq(events->system, "sched");
ck_assert_str_eq(events->event, "sched_switch");
ck_assert_ptr_eq(events->next, NULL);
}
END_TEST
START_TEST(test_opt_event_cb_multiple)
{
struct trace_events *events = NULL;
const struct option opt = TEST_CALLBACK(&events, opt_event_cb);
ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_switch", 0), 0);
ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_wakeup", 0), 0);
ck_assert_str_eq(events->system, "sched");
ck_assert_str_eq(events->event, "sched_wakeup");
ck_assert_str_eq(events->next->system, "sched");
ck_assert_str_eq(events->next->event, "sched_switch");
ck_assert_ptr_eq(events->next->next, NULL);
}
END_TEST
START_TEST(test_opt_housekeeping_cb)
{
struct common_params __params = {0};
struct common_params *params = &__params;
const struct option opt = TEST_CALLBACK(params, opt_housekeeping_cb);
nr_cpus = 4;
ck_assert_int_eq(opt_housekeeping_cb(&opt, "0-3", 0), 0);
ck_assert_int_eq(params->hk_cpus, 1);
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 2, 3);
}
END_TEST
START_TEST(test_opt_housekeeping_cb_invalid)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_housekeeping_cb);
nr_cpus = 4;
assert(freopen("/dev/null", "w", stderr));
opt_housekeeping_cb(&opt, "0-3,5", 0);
}
END_TEST
START_TEST(test_opt_priority_cb)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_priority_cb);
ck_assert_int_eq(opt_priority_cb(&opt, "f:95", 0), 0);
ck_assert_int_eq(params.sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params.sched_param.sched_priority, 95);
}
END_TEST
START_TEST(test_opt_priority_cb_invalid)
{
struct common_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_priority_cb);
assert(freopen("/dev/null", "w", stderr));
opt_priority_cb(&opt, "abc", 0);
}
END_TEST
START_TEST(test_opt_trigger_cb)
{
struct trace_events *events = trace_event_alloc("sched:sched_switch");
const struct option opt = TEST_CALLBACK(&events, opt_trigger_cb);
ck_assert_int_eq(opt_trigger_cb(&opt, "stacktrace", 0), 0);
ck_assert_str_eq(events->trigger, "stacktrace");
}
END_TEST
START_TEST(test_opt_trigger_cb_no_event)
{
struct trace_events *events = NULL;
const struct option opt = TEST_CALLBACK(&events, opt_trigger_cb);
assert(freopen("/dev/null", "w", stderr));
opt_trigger_cb(&opt, "stacktrace", 0);
}
END_TEST
START_TEST(test_opt_filter_cb)
{
struct trace_events *events = trace_event_alloc("sched:sched_switch");
const struct option opt = TEST_CALLBACK(&events, opt_filter_cb);
ck_assert_int_eq(opt_filter_cb(&opt, "comm ~ \"rtla\"", 0), 0);
ck_assert_str_eq(events->filter, "comm ~ \"rtla\"");
}
END_TEST
START_TEST(test_opt_filter_cb_no_event)
{
struct trace_events *events = NULL;
const struct option opt = TEST_CALLBACK(&events, opt_filter_cb);
assert(freopen("/dev/null", "w", stderr));
opt_filter_cb(&opt, "comm ~ \"rtla\"", 0);
}
END_TEST
START_TEST(test_opt_osnoise_auto_cb)
{
struct osnoise_params params = {0};
struct osnoise_cb_data cb_data = {&params};
const struct option opt = TEST_CALLBACK(&cb_data, opt_osnoise_auto_cb);
ck_assert_int_eq(opt_osnoise_auto_cb(&opt, "10", 0), 0);
ck_assert_int_eq(params.common.stop_us, 10);
ck_assert_int_eq(params.threshold, 1);
ck_assert_str_eq(cb_data.trace_output, "osnoise_trace.txt");
}
END_TEST
START_TEST(test_opt_osnoise_period_cb)
{
unsigned long long period = 0;
const struct option opt = TEST_CALLBACK(&period, opt_osnoise_period_cb);
ck_assert_int_eq(opt_osnoise_period_cb(&opt, "1000000", 0), 0);
ck_assert_int_eq(period, 1000000);
}
END_TEST
START_TEST(test_opt_osnoise_period_cb_invalid)
{
unsigned long long period = 0;
const struct option opt = TEST_CALLBACK(&period, opt_osnoise_period_cb);
assert(freopen("/dev/null", "w", stderr));
opt_osnoise_period_cb(&opt, "10000001", 0);
}
END_TEST
START_TEST(test_opt_osnoise_runtime_cb)
{
unsigned long long runtime = 0;
const struct option opt = TEST_CALLBACK(&runtime, opt_osnoise_runtime_cb);
ck_assert_int_eq(opt_osnoise_runtime_cb(&opt, "900000", 0), 0);
ck_assert_int_eq(runtime, 900000);
}
END_TEST
START_TEST(test_opt_osnoise_runtime_cb_invalid)
{
unsigned long long runtime = 0;
const struct option opt = TEST_CALLBACK(&runtime, opt_osnoise_runtime_cb);
assert(freopen("/dev/null", "w", stderr));
opt_osnoise_runtime_cb(&opt, "99", 0);
}
END_TEST
START_TEST(test_opt_osnoise_trace_output_cb)
{
const char *trace_output = NULL;
const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, "trace.txt", 0), 0);
ck_assert_str_eq(trace_output, "trace.txt");
}
END_TEST
START_TEST(test_opt_osnoise_trace_output_cb_noarg)
{
const char *trace_output = NULL;
const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, NULL, 0), 0);
ck_assert_str_eq(trace_output, "osnoise_trace.txt");
}
END_TEST
START_TEST(test_opt_osnoise_on_threshold_cb)
{
struct actions actions = {0};
const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_threshold_cb);
ck_assert_int_eq(opt_osnoise_on_threshold_cb(&opt, "trace", 0), 0);
ck_assert_int_eq(actions.len, 1);
ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions.list[0].trace_output, "osnoise_trace.txt");
}
END_TEST
START_TEST(test_opt_osnoise_on_threshold_cb_invalid)
{
struct actions actions = {0};
const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_threshold_cb);
assert(freopen("/dev/null", "w", stderr));
opt_osnoise_on_threshold_cb(&opt, "abc", 0);
}
END_TEST
START_TEST(test_opt_osnoise_on_end_cb)
{
struct actions actions = {0};
const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_end_cb);
ck_assert_int_eq(opt_osnoise_on_end_cb(&opt, "trace", 0), 0);
ck_assert_int_eq(actions.len, 1);
ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions.list[0].trace_output, "osnoise_trace.txt");
}
END_TEST
START_TEST(test_opt_osnoise_on_end_cb_invalid)
{
struct actions actions = {0};
const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_end_cb);
assert(freopen("/dev/null", "w", stderr));
opt_osnoise_on_end_cb(&opt, "abc", 0);
}
END_TEST
START_TEST(test_opt_timerlat_period_cb)
{
long long period = 0;
const struct option opt = TEST_CALLBACK(&period, opt_timerlat_period_cb);
ck_assert_int_eq(opt_timerlat_period_cb(&opt, "1000", 0), 0);
ck_assert_int_eq(period, 1000);
}
END_TEST
START_TEST(test_opt_timerlat_period_cb_invalid)
{
long long period = 0;
const struct option opt = TEST_CALLBACK(&period, opt_timerlat_period_cb);
assert(freopen("/dev/null", "w", stderr));
opt_timerlat_period_cb(&opt, "1000001", 0);
}
END_TEST
START_TEST(test_opt_timerlat_auto_cb)
{
struct timerlat_params params = {0};
struct timerlat_cb_data cb_data = {&params};
const struct option opt = TEST_CALLBACK(&cb_data, opt_timerlat_auto_cb);
ck_assert_int_eq(opt_timerlat_auto_cb(&opt, "10", 0), 0);
ck_assert_int_eq(params.common.stop_us, 10);
ck_assert_int_eq(params.common.stop_total_us, 10);
ck_assert_int_eq(params.print_stack, 10);
ck_assert_str_eq(cb_data.trace_output, "timerlat_trace.txt");
}
END_TEST
START_TEST(test_opt_dma_latency_cb)
{
int dma_latency = 0;
const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
ck_assert_int_eq(opt_dma_latency_cb(&opt, "1000", 0), 0);
ck_assert_int_eq(dma_latency, 1000);
}
END_TEST
START_TEST(test_opt_dma_latency_cb_min)
{
int dma_latency = 0;
const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
assert(freopen("/dev/null", "w", stderr));
opt_dma_latency_cb(&opt, "-1", 0);
}
END_TEST
START_TEST(test_opt_dma_latency_cb_max)
{
int dma_latency = 0;
const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
assert(freopen("/dev/null", "w", stderr));
opt_dma_latency_cb(&opt, "10001", 0);
}
END_TEST
START_TEST(test_opt_aa_only_cb)
{
struct timerlat_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_aa_only_cb);
ck_assert_int_eq(opt_aa_only_cb(&opt, "10", 0), 0);
ck_assert_int_eq(params.common.stop_us, 10);
ck_assert_int_eq(params.common.stop_total_us, 10);
ck_assert_int_eq(params.print_stack, 10);
ck_assert_int_eq(params.common.aa_only, 1);
}
END_TEST
START_TEST(test_opt_timerlat_trace_output_cb)
{
const char *trace_output = NULL;
const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, "trace.txt", 0), 0);
ck_assert_str_eq(trace_output, "trace.txt");
}
END_TEST
START_TEST(test_opt_timerlat_trace_output_cb_noarg)
{
const char *trace_output = NULL;
const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, NULL, 0), 0);
ck_assert_str_eq(trace_output, "timerlat_trace.txt");
}
END_TEST
START_TEST(test_opt_timerlat_on_threshold_cb)
{
struct actions actions = {0};
const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_threshold_cb);
ck_assert_int_eq(opt_timerlat_on_threshold_cb(&opt, "trace", 0), 0);
ck_assert_int_eq(actions.len, 1);
ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions.list[0].trace_output, "timerlat_trace.txt");
}
END_TEST
START_TEST(test_opt_timerlat_on_threshold_cb_invalid)
{
struct actions actions = {0};
const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_threshold_cb);
assert(freopen("/dev/null", "w", stderr));
opt_timerlat_on_threshold_cb(&opt, "abc", 0);
}
END_TEST
START_TEST(test_opt_timerlat_on_end_cb)
{
struct actions actions = {0};
const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_end_cb);
ck_assert_int_eq(opt_timerlat_on_end_cb(&opt, "trace", 0), 0);
ck_assert_int_eq(actions.len, 1);
ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
ck_assert_str_eq(actions.list[0].trace_output, "timerlat_trace.txt");
}
END_TEST
START_TEST(test_opt_timerlat_on_end_cb_invalid)
{
struct actions actions = {0};
const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_end_cb);
assert(freopen("/dev/null", "w", stderr));
opt_timerlat_on_end_cb(&opt, "abc", 0);
}
END_TEST
START_TEST(test_opt_user_threads_cb)
{
struct timerlat_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_user_threads_cb);
ck_assert_int_eq(opt_user_threads_cb(&opt, NULL, 0), 0);
ck_assert_int_eq(params.common.user_workload, 1);
ck_assert_int_eq(params.common.user_data, 1);
}
END_TEST
START_TEST(test_opt_nano_cb)
{
struct timerlat_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_nano_cb);
ck_assert_int_eq(opt_nano_cb(&opt, NULL, 0), 0);
ck_assert_int_eq(params.common.output_divisor, 1);
}
END_TEST
START_TEST(test_opt_timerlat_align_cb)
{
struct timerlat_params params = {0};
const struct option opt = TEST_CALLBACK(&params, opt_timerlat_align_cb);
ck_assert_int_eq(opt_timerlat_align_cb(&opt, "500", 0), 0);
ck_assert(params.timerlat_align);
ck_assert_int_eq(params.timerlat_align_us, 500);
}
END_TEST
START_TEST(test_opt_stack_format_cb)
{
int stack_format = 0;
const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
ck_assert_int_eq(opt_stack_format_cb(&opt, "full", 0), 0);
ck_assert_int_eq(stack_format, STACK_FORMAT_FULL);
}
END_TEST
START_TEST(test_opt_stack_format_cb_invalid)
{
int stack_format = 0;
const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
assert(freopen("/dev/null", "w", stderr));
opt_stack_format_cb(&opt, "abc", 0);
}
END_TEST
START_TEST(test_opt_bucket_size_cb)
{
int bucket_size = 0;
const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
ck_assert_int_eq(opt_bucket_size_cb(&opt, "100", 0), 0);
ck_assert_int_eq(bucket_size, 100);
}
END_TEST
START_TEST(test_opt_bucket_size_min)
{
int bucket_size = 0;
const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
assert(freopen("/dev/null", "w", stderr));
opt_bucket_size_cb(&opt, "0", 0);
}
END_TEST
START_TEST(test_opt_bucket_size_max)
{
int bucket_size = 0;
const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
assert(freopen("/dev/null", "w", stderr));
opt_bucket_size_cb(&opt, "1000001", 0);
}
END_TEST
START_TEST(test_opt_entries_cb)
{
int entries = 0;
const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
ck_assert_int_eq(opt_entries_cb(&opt, "100", 0), 0);
ck_assert_int_eq(entries, 100);
}
END_TEST
START_TEST(test_opt_entries_min)
{
int entries = 0;
const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
assert(freopen("/dev/null", "w", stderr));
opt_entries_cb(&opt, "9", 0);
}
END_TEST
START_TEST(test_opt_entries_max)
{
int entries = 0;
const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
assert(freopen("/dev/null", "w", stderr));
opt_entries_cb(&opt, "10000000", 0);
}
END_TEST
Suite *cli_opt_callback_suite(void)
{
Suite *s = suite_create("cli_opt_callback");
TCase *tc;
tc = tcase_create("common");
tcase_add_test(tc, test_opt_llong_callback_simple);
tcase_add_test(tc, test_opt_llong_callback_max);
tcase_add_test(tc, test_opt_llong_callback_min);
tcase_add_test(tc, test_opt_int_callback_simple);
tcase_add_test(tc, test_opt_int_callback_max);
tcase_add_test(tc, test_opt_int_callback_min);
tcase_add_test(tc, test_opt_int_callback_non_numeric);
tcase_add_test(tc, test_opt_int_callback_non_numeric_suffix);
tcase_add_test(tc, test_opt_cpus_cb);
tcase_add_exit_test(tc, test_opt_cpus_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_cgroup_cb);
tcase_add_test(tc, test_opt_cgroup_cb_equals);
tcase_add_test(tc, test_opt_duration_cb);
tcase_add_exit_test(tc, test_opt_duration_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_event_cb);
tcase_add_test(tc, test_opt_event_cb_multiple);
tcase_add_test(tc, test_opt_housekeeping_cb);
tcase_add_exit_test(tc, test_opt_housekeeping_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_priority_cb);
tcase_add_exit_test(tc, test_opt_priority_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_trigger_cb);
tcase_add_exit_test(tc, test_opt_trigger_cb_no_event, EXIT_FAILURE);
tcase_add_test(tc, test_opt_filter_cb);
tcase_add_exit_test(tc, test_opt_filter_cb_no_event, EXIT_FAILURE);
suite_add_tcase(s, tc);
tc = tcase_create("osnoise");
tcase_add_test(tc, test_opt_osnoise_auto_cb);
tcase_add_test(tc, test_opt_osnoise_period_cb);
tcase_add_exit_test(tc, test_opt_osnoise_period_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_osnoise_runtime_cb);
tcase_add_exit_test(tc, test_opt_osnoise_runtime_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_osnoise_trace_output_cb);
tcase_add_test(tc, test_opt_osnoise_trace_output_cb_noarg);
tcase_add_test(tc, test_opt_osnoise_on_threshold_cb);
tcase_add_exit_test(tc, test_opt_osnoise_on_threshold_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_osnoise_on_end_cb);
tcase_add_exit_test(tc, test_opt_osnoise_on_end_cb_invalid, EXIT_FAILURE);
suite_add_tcase(s, tc);
tc = tcase_create("timerlat");
tcase_add_test(tc, test_opt_timerlat_period_cb);
tcase_add_exit_test(tc, test_opt_timerlat_period_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_timerlat_auto_cb);
tcase_add_test(tc, test_opt_dma_latency_cb);
tcase_add_exit_test(tc, test_opt_dma_latency_cb_min, EXIT_FAILURE);
tcase_add_exit_test(tc, test_opt_dma_latency_cb_max, EXIT_FAILURE);
tcase_add_test(tc, test_opt_aa_only_cb);
tcase_add_test(tc, test_opt_timerlat_trace_output_cb);
tcase_add_test(tc, test_opt_timerlat_trace_output_cb_noarg);
tcase_add_test(tc, test_opt_timerlat_on_threshold_cb);
tcase_add_exit_test(tc, test_opt_timerlat_on_threshold_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_timerlat_on_end_cb);
tcase_add_exit_test(tc, test_opt_timerlat_on_end_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_user_threads_cb);
tcase_add_test(tc, test_opt_nano_cb);
tcase_add_test(tc, test_opt_stack_format_cb);
tcase_add_exit_test(tc, test_opt_stack_format_cb_invalid, EXIT_FAILURE);
tcase_add_test(tc, test_opt_timerlat_align_cb);
suite_add_tcase(s, tc);
tc = tcase_create("histogram");
tcase_add_test(tc, test_opt_bucket_size_cb);
tcase_add_exit_test(tc, test_opt_bucket_size_min, EXIT_FAILURE);
tcase_add_exit_test(tc, test_opt_bucket_size_max, EXIT_FAILURE);
tcase_add_test(tc, test_opt_entries_cb);
tcase_add_exit_test(tc, test_opt_entries_min, EXIT_FAILURE);
tcase_add_exit_test(tc, test_opt_entries_max, EXIT_FAILURE);
suite_add_tcase(s, tc);
return s;
}
@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0 */
#pragma once
#include "../../src/timerlat.h"
/* Tracing Options */
#define CLI_ASSERT_SINGLE_EVENT(_system, _event) do {\
ck_assert_ptr_nonnull(params->events);\
ck_assert_str_eq(params->events->system, _system);\
ck_assert_str_eq(params->events->event, _event);\
ck_assert_ptr_null(params->events->next);\
} while (0)
#define CLI_ASSERT_SINGLE_FILTER(_filter) do {\
ck_assert_ptr_nonnull(params->events);\
ck_assert_str_eq(params->events->filter, _filter);\
ck_assert_ptr_null(params->events->next);\
} while (0)
#define CLI_ASSERT_SINGLE_TRIGGER(_trigger) do {\
ck_assert_ptr_nonnull(params->events);\
ck_assert_str_eq(params->events->trigger, _trigger);\
ck_assert_ptr_null(params->events->next);\
} while (0)
/* CPU Configuration */
#define CLI_ASSERT_CPUSET(_field, ...) do {\
int n;\
int cpus[] = { __VA_ARGS__ };\
for (n = 0; n < sizeof(cpus) / sizeof(int); n++)\
ck_assert(CPU_ISSET(cpus[n], &params->_field));\
ck_assert_int_eq(CPU_COUNT(&params->_field), n);\
} while (0)
/* Auto Analysis and Actions */
#define CLI_OSNOISE_ASSERT_AUTO(_stop) do {\
ck_assert_int_eq(params->stop_us, _stop);\
ck_assert_int_eq(osn_params->threshold, 1);\
ck_assert_int_eq(params->threshold_actions.len, 1);\
ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\
ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "osnoise_trace.txt");\
} while (0)
#define CLI_TIMERLAT_ASSERT_AUTO(_threshold) do {\
ck_assert_int_eq(params->stop_us, _threshold);\
ck_assert_int_eq(params->stop_total_us, _threshold);\
ck_assert_int_eq(tlat_params->print_stack, _threshold);\
ck_assert_int_eq(params->threshold_actions.len, 1);\
ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\
ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "timerlat_trace.txt");\
} while (0)
#define CLI_TIMERLAT_ASSERT_AA_ONLY(_threshold) do {\
ck_assert_int_eq(params->stop_us, _threshold);\
ck_assert_int_eq(params->stop_total_us, _threshold);\
ck_assert_int_eq(tlat_params->print_stack, _threshold);\
ck_assert_int_eq(params->threshold_actions.len, 0);\
ck_assert(params->aa_only);\
} while (0)
#define CLI_ASSERT_SINGLE_ACTION(_actions, _type, _arg, _valtype, _value) do {\
ck_assert_int_eq(params->_actions.len, 1);\
ck_assert_int_eq(params->_actions.list[0].type, _type);\
ck_assert_##_valtype##_eq(params->_actions.list[0]._arg, _value);\
} while (0)
@@ -0,0 +1,557 @@
// 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 "cli_params_assert.h"
#include "../../src/cli.h"
#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
int argc = sizeof(argv) / sizeof(char *) - 1;\
struct common_params *params =\
osnoise_hist_parse_args(argc, argv);\
struct osnoise_params *osn_params __maybe_unused =\
to_osnoise_params(params)
/* Tracing Options */
START_TEST(test_period_short)
{
PARSE_ARGS("osnoise", "hist", "-p", "100000");
ck_assert_int_eq(osn_params->period, 100000);
}
END_TEST
START_TEST(test_period_long)
{
PARSE_ARGS("osnoise", "hist", "--period", "100000");
ck_assert_int_eq(osn_params->period, 100000);
}
END_TEST
START_TEST(test_runtime_short)
{
PARSE_ARGS("osnoise", "hist", "-r", "95000");
ck_assert_int_eq(osn_params->runtime, 95000);
}
END_TEST
START_TEST(test_runtime_long)
{
PARSE_ARGS("osnoise", "hist", "--runtime", "95000");
ck_assert_int_eq(osn_params->runtime, 95000);
}
END_TEST
START_TEST(test_stop_short)
{
PARSE_ARGS("osnoise", "hist", "-s", "20");
ck_assert_int_eq(params->stop_us, 20);
}
END_TEST
START_TEST(test_stop_long)
{
PARSE_ARGS("osnoise", "hist", "--stop", "20");
ck_assert_int_eq(params->stop_us, 20);
}
END_TEST
START_TEST(test_stop_total_short)
{
PARSE_ARGS("osnoise", "hist", "-S", "20");
ck_assert_int_eq(params->stop_total_us, 20);
}
END_TEST
START_TEST(test_stop_total_long)
{
PARSE_ARGS("osnoise", "hist", "--stop-total", "20");
ck_assert_int_eq(params->stop_total_us, 20);
}
END_TEST
START_TEST(test_threshold_short)
{
PARSE_ARGS("osnoise", "hist", "-T", "5");
ck_assert_int_eq(osn_params->threshold, 5);
}
END_TEST
START_TEST(test_threshold_long)
{
PARSE_ARGS("osnoise", "hist", "--threshold", "5");
ck_assert_int_eq(osn_params->threshold, 5);
}
END_TEST
START_TEST(test_trace_short_noarg)
{
PARSE_ARGS("osnoise", "hist", "-t");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
}
END_TEST
START_TEST(test_trace_short_followarg)
{
PARSE_ARGS("osnoise", "hist", "-t", "-d", "20");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
}
END_TEST
START_TEST(test_trace_short_space)
{
PARSE_ARGS("osnoise", "hist", "-t", "tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_short_equals)
{
PARSE_ARGS("osnoise", "hist", "-t=tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_long_noarg)
{
PARSE_ARGS("osnoise", "hist", "--trace");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
}
END_TEST
START_TEST(test_trace_long_followarg)
{
PARSE_ARGS("osnoise", "hist", "--trace", "-d", "20");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
}
END_TEST
START_TEST(test_trace_long_space)
{
PARSE_ARGS("osnoise", "hist", "--trace", "tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_long_equals)
{
PARSE_ARGS("osnoise", "hist", "--trace=tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
/* Event Configuration */
START_TEST(test_event_short)
{
PARSE_ARGS("osnoise", "hist", "-e", "system:event");
CLI_ASSERT_SINGLE_EVENT("system", "event");
}
END_TEST
START_TEST(test_event_long)
{
PARSE_ARGS("osnoise", "hist", "--event", "system:event");
CLI_ASSERT_SINGLE_EVENT("system", "event");
}
END_TEST
START_TEST(test_filter)
{
PARSE_ARGS("osnoise", "hist", "-e", "system:event", "--filter", "filter");
CLI_ASSERT_SINGLE_FILTER("filter");
}
END_TEST
START_TEST(test_trigger)
{
PARSE_ARGS("osnoise", "hist", "-e", "system:event", "--trigger", "trigger");
CLI_ASSERT_SINGLE_TRIGGER("trigger");
}
END_TEST
/* CPU Configuration */
START_TEST(test_cpus_short)
{
nr_cpus = 4;
PARSE_ARGS("osnoise", "hist", "-c", "0-1,3");
ck_assert_str_eq(params->cpus, "0-1,3");
CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
}
END_TEST
START_TEST(test_cpus_long)
{
nr_cpus = 4;
PARSE_ARGS("osnoise", "hist", "--cpus", "0-1,3");
ck_assert_str_eq(params->cpus, "0-1,3");
CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
}
END_TEST
START_TEST(test_housekeeping_short)
{
nr_cpus = 4;
PARSE_ARGS("osnoise", "hist", "-H", "0-1,3");
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
}
END_TEST
START_TEST(test_housekeeping_long)
{
nr_cpus = 4;
PARSE_ARGS("osnoise", "hist", "--house-keeping", "0-1,3");
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
}
END_TEST
/* Thread Configuration */
START_TEST(test_cgroup_short_noarg)
{
PARSE_ARGS("osnoise", "hist", "-C");
ck_assert(params->cgroup);
ck_assert_ptr_null(params->cgroup_name);
}
END_TEST
START_TEST(test_cgroup_short_space)
{
PARSE_ARGS("osnoise", "hist", "-C", "cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_short_equals)
{
PARSE_ARGS("osnoise", "hist", "-C=cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_long_noarg)
{
PARSE_ARGS("osnoise", "hist", "--cgroup");
ck_assert(params->cgroup);
ck_assert_ptr_null(params->cgroup_name);
}
END_TEST
START_TEST(test_cgroup_long_space)
{
PARSE_ARGS("osnoise", "hist", "--cgroup", "cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_long_equals)
{
PARSE_ARGS("osnoise", "hist", "--cgroup=cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_priority_short)
{
PARSE_ARGS("osnoise", "hist", "-P", "f:95");
ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params->sched_param.sched_priority, 95);
}
END_TEST
START_TEST(test_priority_long)
{
PARSE_ARGS("osnoise", "hist", "--priority", "f:95");
ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params->sched_param.sched_priority, 95);
}
END_TEST
/* Histogram Options */
START_TEST(test_bucket_size_short)
{
PARSE_ARGS("osnoise", "hist", "-b", "2");
ck_assert_int_eq(params->hist.bucket_size, 2);
}
END_TEST
START_TEST(test_bucket_size_long)
{
PARSE_ARGS("osnoise", "hist", "--bucket-size", "2");
ck_assert_int_eq(params->hist.bucket_size, 2);
}
END_TEST
START_TEST(test_entries_short)
{
PARSE_ARGS("osnoise", "hist", "-E", "512");
ck_assert_int_eq(params->hist.entries, 512);
}
END_TEST
START_TEST(test_entries_long)
{
PARSE_ARGS("osnoise", "hist", "--entries", "512");
ck_assert_int_eq(params->hist.entries, 512);
}
END_TEST
START_TEST(test_no_header)
{
PARSE_ARGS("osnoise", "hist", "--no-header");
ck_assert(params->hist.no_header);
}
END_TEST
START_TEST(test_no_index)
{
PARSE_ARGS("osnoise", "hist", "--with-zeros", "--no-index");
ck_assert(params->hist.no_index);
}
END_TEST
START_TEST(test_no_summary)
{
PARSE_ARGS("osnoise", "hist", "--no-summary");
ck_assert(params->hist.no_summary);
}
END_TEST
START_TEST(test_with_zeros)
{
PARSE_ARGS("osnoise", "hist", "--with-zeros");
ck_assert(params->hist.with_zeros);
}
END_TEST
/* System Tuning */
START_TEST(test_trace_buffer_size)
{
PARSE_ARGS("osnoise", "hist", "--trace-buffer-size", "200");
ck_assert_int_eq(params->buffer_size, 200);
}
END_TEST
START_TEST(test_warm_up)
{
PARSE_ARGS("osnoise", "hist", "--warm-up", "5");
ck_assert_int_eq(params->warmup, 5);
}
END_TEST
/* Auto Analysis and Actions */
START_TEST(test_auto)
{
PARSE_ARGS("osnoise", "hist", "-a", "20");
CLI_OSNOISE_ASSERT_AUTO(20);
}
END_TEST
START_TEST(test_on_end)
{
PARSE_ARGS("osnoise", "hist", "--on-end", "trace");
CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
}
END_TEST
START_TEST(test_on_threshold)
{
PARSE_ARGS("osnoise", "hist", "--on-threshold", "trace");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
}
END_TEST
/* General */
START_TEST(test_debug_short)
{
PARSE_ARGS("osnoise", "hist", "-D");
ck_assert(config_debug);
}
END_TEST
START_TEST(test_debug_long)
{
PARSE_ARGS("osnoise", "hist", "--debug");
ck_assert(config_debug);
}
END_TEST
START_TEST(test_duration_short)
{
PARSE_ARGS("osnoise", "hist", "-d", "1m");
ck_assert_int_eq(params->duration, 60);
}
END_TEST
START_TEST(test_duration_long)
{
PARSE_ARGS("osnoise", "hist", "--duration", "1m");
ck_assert_int_eq(params->duration, 60);
}
END_TEST
Suite *osnoise_hist_cli_suite(void)
{
Suite *s = suite_create("osnoise_hist_cli");
TCase *tc;
tc = tcase_create("tracing_options");
tcase_add_test(tc, test_period_short);
tcase_add_test(tc, test_period_long);
tcase_add_test(tc, test_runtime_short);
tcase_add_test(tc, test_runtime_long);
tcase_add_test(tc, test_stop_short);
tcase_add_test(tc, test_stop_long);
tcase_add_test(tc, test_stop_total_short);
tcase_add_test(tc, test_stop_total_long);
tcase_add_test(tc, test_threshold_short);
tcase_add_test(tc, test_threshold_long);
tcase_add_test(tc, test_trace_short_noarg);
tcase_add_test(tc, test_trace_short_followarg);
tcase_add_test(tc, test_trace_short_space);
tcase_add_test(tc, test_trace_short_equals);
tcase_add_test(tc, test_trace_long_noarg);
tcase_add_test(tc, test_trace_long_followarg);
tcase_add_test(tc, test_trace_long_space);
tcase_add_test(tc, test_trace_long_equals);
suite_add_tcase(s, tc);
tc = tcase_create("event_configuration");
tcase_add_test(tc, test_event_short);
tcase_add_test(tc, test_event_long);
tcase_add_test(tc, test_filter);
tcase_add_test(tc, test_trigger);
suite_add_tcase(s, tc);
tc = tcase_create("cpu_configuration");
tcase_add_test(tc, test_cpus_short);
tcase_add_test(tc, test_cpus_long);
tcase_add_test(tc, test_housekeeping_short);
tcase_add_test(tc, test_housekeeping_long);
suite_add_tcase(s, tc);
tc = tcase_create("thread_configuration");
tcase_add_test(tc, test_cgroup_short_noarg);
tcase_add_test(tc, test_cgroup_short_space);
tcase_add_test(tc, test_cgroup_short_equals);
tcase_add_test(tc, test_cgroup_long_noarg);
tcase_add_test(tc, test_cgroup_long_space);
tcase_add_test(tc, test_cgroup_long_equals);
tcase_add_test(tc, test_priority_short);
tcase_add_test(tc, test_priority_long);
suite_add_tcase(s, tc);
tc = tcase_create("histogram_options");
tcase_add_test(tc, test_bucket_size_short);
tcase_add_test(tc, test_bucket_size_long);
tcase_add_test(tc, test_entries_short);
tcase_add_test(tc, test_entries_long);
tcase_add_test(tc, test_no_header);
tcase_add_test(tc, test_no_index);
tcase_add_test(tc, test_no_summary);
tcase_add_test(tc, test_with_zeros);
suite_add_tcase(s, tc);
tc = tcase_create("system_tuning");
tcase_add_test(tc, test_trace_buffer_size);
tcase_add_test(tc, test_warm_up);
suite_add_tcase(s, tc);
tc = tcase_create("aa_actions");
tcase_add_test(tc, test_auto);
tcase_add_test(tc, test_on_end);
tcase_add_test(tc, test_on_threshold);
suite_add_tcase(s, tc);
tc = tcase_create("general");
tcase_add_test(tc, test_debug_short);
tcase_add_test(tc, test_debug_long);
tcase_add_test(tc, test_duration_short);
tcase_add_test(tc, test_duration_long);
suite_add_tcase(s, tc);
return s;
}
@@ -0,0 +1,503 @@
// 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 "cli_params_assert.h"
#include "../../src/cli.h"
#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
int argc = sizeof(argv) / sizeof(char *) - 1;\
struct common_params *params =\
osnoise_top_parse_args(argc, argv);\
struct osnoise_params *osn_params __maybe_unused =\
to_osnoise_params(params)
/* Tracing Options */
START_TEST(test_period_short)
{
PARSE_ARGS("osnoise", "top", "-p", "100000");
ck_assert_int_eq(osn_params->period, 100000);
}
END_TEST
START_TEST(test_period_long)
{
PARSE_ARGS("osnoise", "top", "--period", "100000");
ck_assert_int_eq(osn_params->period, 100000);
}
END_TEST
START_TEST(test_runtime_short)
{
PARSE_ARGS("osnoise", "top", "-r", "95000");
ck_assert_int_eq(osn_params->runtime, 95000);
}
END_TEST
START_TEST(test_runtime_long)
{
PARSE_ARGS("osnoise", "top", "--runtime", "95000");
ck_assert_int_eq(osn_params->runtime, 95000);
}
END_TEST
START_TEST(test_stop_short)
{
PARSE_ARGS("osnoise", "top", "-s", "20");
ck_assert_int_eq(params->stop_us, 20);
}
END_TEST
START_TEST(test_stop_long)
{
PARSE_ARGS("osnoise", "top", "--stop", "20");
ck_assert_int_eq(params->stop_us, 20);
}
END_TEST
START_TEST(test_stop_total_short)
{
PARSE_ARGS("osnoise", "top", "-S", "20");
ck_assert_int_eq(params->stop_total_us, 20);
}
END_TEST
START_TEST(test_stop_total_long)
{
PARSE_ARGS("osnoise", "top", "--stop-total", "20");
ck_assert_int_eq(params->stop_total_us, 20);
}
END_TEST
START_TEST(test_threshold_short)
{
PARSE_ARGS("osnoise", "top", "-T", "5");
ck_assert_int_eq(osn_params->threshold, 5);
}
END_TEST
START_TEST(test_threshold_long)
{
PARSE_ARGS("osnoise", "top", "--threshold", "5");
ck_assert_int_eq(osn_params->threshold, 5);
}
END_TEST
START_TEST(test_trace_short_noarg)
{
PARSE_ARGS("osnoise", "top", "-t");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
}
END_TEST
START_TEST(test_trace_short_followarg)
{
PARSE_ARGS("osnoise", "top", "-t", "-d", "20");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
}
END_TEST
START_TEST(test_trace_short_space)
{
PARSE_ARGS("osnoise", "top", "-t", "tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_short_equals)
{
PARSE_ARGS("osnoise", "top", "-t=tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_long_noarg)
{
PARSE_ARGS("osnoise", "top", "--trace");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
}
END_TEST
START_TEST(test_trace_long_followarg)
{
PARSE_ARGS("osnoise", "top", "--trace", "-d", "20");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
}
END_TEST
START_TEST(test_trace_long_space)
{
PARSE_ARGS("osnoise", "top", "--trace", "tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_long_equals)
{
PARSE_ARGS("osnoise", "top", "--trace=tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
/* Event Configuration */
START_TEST(test_event_short)
{
PARSE_ARGS("osnoise", "top", "-e", "system:event");
CLI_ASSERT_SINGLE_EVENT("system", "event");
}
END_TEST
START_TEST(test_event_long)
{
PARSE_ARGS("osnoise", "top", "--event", "system:event");
CLI_ASSERT_SINGLE_EVENT("system", "event");
}
END_TEST
START_TEST(test_filter)
{
PARSE_ARGS("osnoise", "top", "-e", "system:event", "--filter", "filter");
CLI_ASSERT_SINGLE_FILTER("filter");
}
END_TEST
START_TEST(test_trigger)
{
PARSE_ARGS("osnoise", "top", "-e", "system:event", "--trigger", "trigger");
CLI_ASSERT_SINGLE_TRIGGER("trigger");
}
END_TEST
/* CPU Configuration */
START_TEST(test_cpus_short)
{
nr_cpus = 4;
PARSE_ARGS("osnoise", "top", "-c", "0-1,3");
ck_assert_str_eq(params->cpus, "0-1,3");
CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
}
END_TEST
START_TEST(test_cpus_long)
{
nr_cpus = 4;
PARSE_ARGS("osnoise", "top", "--cpus", "0-1,3");
ck_assert_str_eq(params->cpus, "0-1,3");
CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
}
END_TEST
START_TEST(test_housekeeping_short)
{
nr_cpus = 4;
PARSE_ARGS("osnoise", "top", "-H", "0-1,3");
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
}
END_TEST
START_TEST(test_housekeeping_long)
{
nr_cpus = 4;
PARSE_ARGS("osnoise", "top", "--house-keeping", "0-1,3");
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
}
END_TEST
/* Thread Configuration */
START_TEST(test_cgroup_short_noarg)
{
PARSE_ARGS("osnoise", "top", "-C");
ck_assert(params->cgroup);
ck_assert_ptr_null(params->cgroup_name);
}
END_TEST
START_TEST(test_cgroup_short_space)
{
PARSE_ARGS("osnoise", "top", "-C", "cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_short_equals)
{
PARSE_ARGS("osnoise", "top", "-C=cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_long_noarg)
{
PARSE_ARGS("osnoise", "top", "--cgroup");
ck_assert(params->cgroup);
ck_assert_ptr_null(params->cgroup_name);
}
END_TEST
START_TEST(test_cgroup_long_space)
{
PARSE_ARGS("osnoise", "top", "--cgroup", "cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_long_equals)
{
PARSE_ARGS("osnoise", "top", "--cgroup=cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_priority_short)
{
PARSE_ARGS("osnoise", "top", "-P", "f:95");
ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params->sched_param.sched_priority, 95);
}
END_TEST
START_TEST(test_priority_long)
{
PARSE_ARGS("osnoise", "top", "--priority", "f:95");
ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params->sched_param.sched_priority, 95);
}
END_TEST
/* Output */
START_TEST(test_quiet_short)
{
PARSE_ARGS("osnoise", "top", "-q");
ck_assert(params->quiet);
}
END_TEST
START_TEST(test_quiet_long)
{
PARSE_ARGS("osnoise", "top", "--quiet");
ck_assert(params->quiet);
}
END_TEST
/* Auto Analysis and Actions */
START_TEST(test_auto)
{
PARSE_ARGS("osnoise", "top", "-a", "20");
CLI_OSNOISE_ASSERT_AUTO(20);
}
END_TEST
START_TEST(test_on_end)
{
PARSE_ARGS("osnoise", "top", "--on-end", "trace");
CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
}
END_TEST
START_TEST(test_on_threshold)
{
PARSE_ARGS("osnoise", "top", "--on-threshold", "trace");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"osnoise_trace.txt");
}
END_TEST
/* System Tuning */
START_TEST(test_trace_buffer_size)
{
PARSE_ARGS("osnoise", "top", "--trace-buffer-size", "200");
ck_assert_int_eq(params->buffer_size, 200);
}
END_TEST
START_TEST(test_warm_up)
{
PARSE_ARGS("osnoise", "top", "--warm-up", "5");
ck_assert_int_eq(params->warmup, 5);
}
END_TEST
/* General */
START_TEST(test_debug_short)
{
PARSE_ARGS("osnoise", "top", "-D");
ck_assert(config_debug);
}
END_TEST
START_TEST(test_debug_long)
{
PARSE_ARGS("osnoise", "top", "--debug");
ck_assert(config_debug);
}
END_TEST
START_TEST(test_duration_short)
{
PARSE_ARGS("osnoise", "top", "-d", "1m");
ck_assert_int_eq(params->duration, 60);
}
END_TEST
START_TEST(test_duration_long)
{
PARSE_ARGS("osnoise", "top", "--duration", "1m");
ck_assert_int_eq(params->duration, 60);
}
END_TEST
Suite *osnoise_top_cli_suite(void)
{
Suite *s = suite_create("osnoise_top_cli");
TCase *tc;
tc = tcase_create("tracing_options");
tcase_add_test(tc, test_period_short);
tcase_add_test(tc, test_period_long);
tcase_add_test(tc, test_runtime_short);
tcase_add_test(tc, test_runtime_long);
tcase_add_test(tc, test_stop_short);
tcase_add_test(tc, test_stop_long);
tcase_add_test(tc, test_stop_total_short);
tcase_add_test(tc, test_stop_total_long);
tcase_add_test(tc, test_threshold_short);
tcase_add_test(tc, test_threshold_long);
tcase_add_test(tc, test_trace_short_noarg);
tcase_add_test(tc, test_trace_short_followarg);
tcase_add_test(tc, test_trace_short_space);
tcase_add_test(tc, test_trace_short_equals);
tcase_add_test(tc, test_trace_long_noarg);
tcase_add_test(tc, test_trace_long_followarg);
tcase_add_test(tc, test_trace_long_space);
tcase_add_test(tc, test_trace_long_equals);
suite_add_tcase(s, tc);
tc = tcase_create("event_configuration");
tcase_add_test(tc, test_event_short);
tcase_add_test(tc, test_event_long);
tcase_add_test(tc, test_filter);
tcase_add_test(tc, test_trigger);
suite_add_tcase(s, tc);
tc = tcase_create("cpu_configuration");
tcase_add_test(tc, test_cpus_short);
tcase_add_test(tc, test_cpus_long);
tcase_add_test(tc, test_housekeeping_short);
tcase_add_test(tc, test_housekeeping_long);
suite_add_tcase(s, tc);
tc = tcase_create("thread_configuration");
tcase_add_test(tc, test_cgroup_short_noarg);
tcase_add_test(tc, test_cgroup_short_space);
tcase_add_test(tc, test_cgroup_short_equals);
tcase_add_test(tc, test_cgroup_long_noarg);
tcase_add_test(tc, test_cgroup_long_space);
tcase_add_test(tc, test_cgroup_long_equals);
tcase_add_test(tc, test_priority_short);
tcase_add_test(tc, test_priority_long);
suite_add_tcase(s, tc);
tc = tcase_create("output");
tcase_add_test(tc, test_quiet_short);
tcase_add_test(tc, test_quiet_long);
suite_add_tcase(s, tc);
tc = tcase_create("system_tuning");
tcase_add_test(tc, test_trace_buffer_size);
tcase_add_test(tc, test_warm_up);
suite_add_tcase(s, tc);
tc = tcase_create("aa_actions");
tcase_add_test(tc, test_auto);
tcase_add_test(tc, test_on_end);
tcase_add_test(tc, test_on_threshold);
suite_add_tcase(s, tc);
tc = tcase_create("general");
tcase_add_test(tc, test_debug_short);
tcase_add_test(tc, test_debug_long);
tcase_add_test(tc, test_duration_short);
tcase_add_test(tc, test_duration_long);
suite_add_tcase(s, tc);
return s;
}
@@ -0,0 +1,722 @@
// 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 <linux/container_of.h>
#include "cli_params_assert.h"
#include "../../src/cli.h"
#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
int argc = sizeof(argv) / sizeof(char *) - 1;\
struct common_params *params =\
timerlat_hist_parse_args(argc, argv);\
struct timerlat_params *tlat_params __maybe_unused =\
to_timerlat_params(params)
/* Tracing Options */
START_TEST(test_irq_short)
{
PARSE_ARGS("timerlat", "hist", "-i", "20");
ck_assert_int_eq(params->stop_us, 20);
}
END_TEST
START_TEST(test_irq_long)
{
PARSE_ARGS("timerlat", "hist", "--irq", "20");
ck_assert_int_eq(params->stop_us, 20);
}
END_TEST
START_TEST(test_period_short)
{
PARSE_ARGS("timerlat", "hist", "-p", "200");
ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
}
END_TEST
START_TEST(test_period_long)
{
PARSE_ARGS("timerlat", "hist", "--period", "200");
ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
}
END_TEST
START_TEST(test_stack_short)
{
PARSE_ARGS("timerlat", "hist", "-s", "20");
ck_assert_int_eq(tlat_params->print_stack, 20);
}
END_TEST
START_TEST(test_stack_long)
{
PARSE_ARGS("timerlat", "hist", "--stack", "20");
ck_assert_int_eq(tlat_params->print_stack, 20);
}
END_TEST
START_TEST(test_thread_short)
{
PARSE_ARGS("timerlat", "hist", "-T", "20");
ck_assert_int_eq(params->stop_total_us, 20);
}
END_TEST
START_TEST(test_thread_long)
{
PARSE_ARGS("timerlat", "hist", "--thread", "20");
ck_assert_int_eq(params->stop_total_us, 20);
}
END_TEST
START_TEST(test_trace_short_noarg)
{
PARSE_ARGS("timerlat", "hist", "-t");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
}
END_TEST
START_TEST(test_trace_short_followarg)
{
PARSE_ARGS("timerlat", "hist", "-t", "-d", "20");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
}
END_TEST
START_TEST(test_trace_short_space)
{
PARSE_ARGS("timerlat", "hist", "-t", "tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_short_equals)
{
PARSE_ARGS("timerlat", "hist", "-t=tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_long_noarg)
{
PARSE_ARGS("timerlat", "hist", "--trace");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
}
END_TEST
START_TEST(test_trace_long_followarg)
{
PARSE_ARGS("timerlat", "hist", "--trace", "-d", "20");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
}
END_TEST
START_TEST(test_trace_long_space)
{
PARSE_ARGS("timerlat", "hist", "--trace", "tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_long_equals)
{
PARSE_ARGS("timerlat", "hist", "--trace=tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
/* Event Configuration */
START_TEST(test_event_short)
{
PARSE_ARGS("timerlat", "hist", "-e", "system:event");
CLI_ASSERT_SINGLE_EVENT("system", "event");
}
END_TEST
START_TEST(test_event_long)
{
PARSE_ARGS("timerlat", "hist", "--event", "system:event");
CLI_ASSERT_SINGLE_EVENT("system", "event");
}
END_TEST
START_TEST(test_filter)
{
PARSE_ARGS("timerlat", "hist", "-e", "system:event", "--filter", "filter");
CLI_ASSERT_SINGLE_FILTER("filter");
}
END_TEST
START_TEST(test_trigger)
{
PARSE_ARGS("timerlat", "hist", "-e", "system:event", "--trigger", "trigger");
CLI_ASSERT_SINGLE_TRIGGER("trigger");
}
END_TEST
/* CPU Configuration */
START_TEST(test_cpus_short)
{
nr_cpus = 4;
PARSE_ARGS("timerlat", "hist", "-c", "0-1,3");
ck_assert_str_eq(params->cpus, "0-1,3");
CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
}
END_TEST
START_TEST(test_cpus_long)
{
nr_cpus = 4;
PARSE_ARGS("timerlat", "hist", "--cpus", "0-1,3");
ck_assert_str_eq(params->cpus, "0-1,3");
CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
}
END_TEST
START_TEST(test_housekeeping_short)
{
nr_cpus = 4;
PARSE_ARGS("timerlat", "hist", "-H", "0-1,3");
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
}
END_TEST
START_TEST(test_housekeeping_long)
{
nr_cpus = 4;
PARSE_ARGS("timerlat", "hist", "--house-keeping", "0-1,3");
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
}
END_TEST
/* Thread Configuration */
START_TEST(test_cgroup_short_noarg)
{
PARSE_ARGS("timerlat", "hist", "-C");
ck_assert(params->cgroup);
ck_assert_ptr_null(params->cgroup_name);
}
END_TEST
START_TEST(test_cgroup_short_space)
{
PARSE_ARGS("timerlat", "hist", "-C", "cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_short_equals)
{
PARSE_ARGS("timerlat", "hist", "-C=cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_long_noarg)
{
PARSE_ARGS("timerlat", "hist", "--cgroup");
ck_assert(params->cgroup);
ck_assert_ptr_null(params->cgroup_name);
}
END_TEST
START_TEST(test_cgroup_long_space)
{
PARSE_ARGS("timerlat", "hist", "--cgroup", "cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_long_equals)
{
PARSE_ARGS("timerlat", "hist", "--cgroup=cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_kernel_threads_short)
{
PARSE_ARGS("timerlat", "hist", "-k");
ck_assert(params->kernel_workload);
ck_assert(!params->user_workload);
ck_assert(!params->user_data);
}
END_TEST
START_TEST(test_kernel_threads_long)
{
PARSE_ARGS("timerlat", "hist", "--kernel-threads");
ck_assert(params->kernel_workload);
ck_assert(!params->user_workload);
ck_assert(!params->user_data);
}
END_TEST
START_TEST(test_priority_short)
{
PARSE_ARGS("timerlat", "hist", "-P", "f:95");
ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params->sched_param.sched_priority, 95);
}
END_TEST
START_TEST(test_priority_long)
{
PARSE_ARGS("timerlat", "hist", "--priority", "f:95");
ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params->sched_param.sched_priority, 95);
}
END_TEST
START_TEST(test_user_load_short)
{
PARSE_ARGS("timerlat", "hist", "-U");
ck_assert(!params->kernel_workload);
ck_assert(!params->user_workload);
ck_assert(params->user_data);
}
END_TEST
START_TEST(test_user_load_long)
{
PARSE_ARGS("timerlat", "hist", "--user-load");
ck_assert(!params->kernel_workload);
ck_assert(!params->user_workload);
ck_assert(params->user_data);
}
END_TEST
START_TEST(test_user_threads_short)
{
PARSE_ARGS("timerlat", "hist", "-u");
ck_assert(!params->kernel_workload);
ck_assert(params->user_workload);
ck_assert(params->user_data);
}
END_TEST
START_TEST(test_user_threads_long)
{
PARSE_ARGS("timerlat", "hist", "--user-threads");
ck_assert(!params->kernel_workload);
ck_assert(params->user_workload);
ck_assert(params->user_data);
}
END_TEST
START_TEST(test_aligned_short)
{
PARSE_ARGS("timerlat", "hist", "-A", "500");
ck_assert(tlat_params->timerlat_align);
ck_assert_int_eq(tlat_params->timerlat_align_us, 500);
}
END_TEST
START_TEST(test_aligned_long)
{
PARSE_ARGS("timerlat", "hist", "--aligned", "500");
ck_assert(tlat_params->timerlat_align);
ck_assert_int_eq(tlat_params->timerlat_align_us, 500);
}
END_TEST
/* Histogram Options */
START_TEST(test_bucket_size_short)
{
PARSE_ARGS("timerlat", "hist", "-b", "2");
ck_assert_int_eq(params->hist.bucket_size, 2);
}
END_TEST
START_TEST(test_bucket_size_long)
{
PARSE_ARGS("timerlat", "hist", "--bucket-size", "2");
ck_assert_int_eq(params->hist.bucket_size, 2);
}
END_TEST
START_TEST(test_entries_short)
{
PARSE_ARGS("timerlat", "hist", "-E", "512");
ck_assert_int_eq(params->hist.entries, 512);
}
END_TEST
START_TEST(test_entries_long)
{
PARSE_ARGS("timerlat", "hist", "--entries", "512");
ck_assert_int_eq(params->hist.entries, 512);
}
END_TEST
START_TEST(test_no_header)
{
PARSE_ARGS("timerlat", "hist", "--no-header");
ck_assert(params->hist.no_header);
}
END_TEST
START_TEST(test_no_index)
{
PARSE_ARGS("timerlat", "hist", "--with-zeros", "--no-index");
ck_assert(params->hist.no_index);
}
END_TEST
START_TEST(test_no_irq)
{
PARSE_ARGS("timerlat", "hist", "--no-irq");
ck_assert(params->hist.no_irq);
}
END_TEST
START_TEST(test_no_summary)
{
PARSE_ARGS("timerlat", "hist", "--no-summary");
ck_assert(params->hist.no_summary);
}
END_TEST
START_TEST(test_no_thread)
{
PARSE_ARGS("timerlat", "hist", "--no-thread");
ck_assert(params->hist.no_thread);
}
END_TEST
START_TEST(test_with_zeros)
{
PARSE_ARGS("timerlat", "hist", "--with-zeros");
ck_assert(params->hist.with_zeros);
}
END_TEST
/* Output */
START_TEST(test_nano_short)
{
PARSE_ARGS("timerlat", "hist", "-n");
ck_assert_int_eq(params->output_divisor, 1);
}
END_TEST
START_TEST(test_nano_long)
{
PARSE_ARGS("timerlat", "hist", "--nano");
ck_assert_int_eq(params->output_divisor, 1);
}
END_TEST
/* System Tuning */
START_TEST(test_deepest_idle_state)
{
PARSE_ARGS("timerlat", "hist", "--deepest-idle-state", "1");
ck_assert_int_eq(tlat_params->deepest_idle_state, 1);
}
END_TEST
START_TEST(test_dma_latency)
{
PARSE_ARGS("timerlat", "hist", "--dma-latency", "10");
ck_assert_int_eq(tlat_params->dma_latency, 10);
}
END_TEST
START_TEST(test_trace_buffer_size)
{
PARSE_ARGS("timerlat", "hist", "--trace-buffer-size", "200");
ck_assert_int_eq(params->buffer_size, 200);
}
END_TEST
START_TEST(test_warm_up)
{
PARSE_ARGS("timerlat", "hist", "--warm-up", "5");
ck_assert_int_eq(params->warmup, 5);
}
END_TEST
/* Auto Analysis and Actions */
START_TEST(test_auto)
{
PARSE_ARGS("timerlat", "hist", "-a", "20");
CLI_TIMERLAT_ASSERT_AUTO(20);
}
END_TEST
START_TEST(test_bpf_action)
{
PARSE_ARGS("timerlat", "hist", "--bpf-action", "program");
ck_assert_str_eq(tlat_params->bpf_action_program, "program");
}
END_TEST
START_TEST(test_dump_tasks)
{
PARSE_ARGS("timerlat", "hist", "--dump-tasks");
ck_assert(tlat_params->dump_tasks);
}
END_TEST
START_TEST(test_no_aa)
{
PARSE_ARGS("timerlat", "hist", "--no-aa");
ck_assert(tlat_params->no_aa);
}
END_TEST
START_TEST(test_on_end)
{
PARSE_ARGS("timerlat", "hist", "--on-end", "trace");
CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
}
END_TEST
START_TEST(test_on_threshold)
{
PARSE_ARGS("timerlat", "hist", "--on-threshold", "trace");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
}
END_TEST
START_TEST(test_stack_format)
{
PARSE_ARGS("timerlat", "hist", "--stack-format", "truncate");
ck_assert_int_eq(tlat_params->stack_format, STACK_FORMAT_TRUNCATE);
}
END_TEST
/* General */
START_TEST(test_debug_short)
{
PARSE_ARGS("timerlat", "hist", "-D");
ck_assert(config_debug);
}
END_TEST
START_TEST(test_debug_long)
{
PARSE_ARGS("timerlat", "hist", "--debug");
ck_assert(config_debug);
}
END_TEST
START_TEST(test_duration_short)
{
PARSE_ARGS("timerlat", "hist", "-d", "1m");
ck_assert_int_eq(params->duration, 60);
}
END_TEST
START_TEST(test_duration_long)
{
PARSE_ARGS("timerlat", "hist", "--duration", "1m");
ck_assert_int_eq(params->duration, 60);
}
END_TEST
Suite *timerlat_hist_cli_suite(void)
{
Suite *s = suite_create("timerlat_hist_cli");
TCase *tc;
tc = tcase_create("tracing_options");
tcase_add_test(tc, test_irq_short);
tcase_add_test(tc, test_irq_long);
tcase_add_test(tc, test_period_short);
tcase_add_test(tc, test_period_long);
tcase_add_test(tc, test_stack_short);
tcase_add_test(tc, test_stack_long);
tcase_add_test(tc, test_thread_short);
tcase_add_test(tc, test_thread_long);
tcase_add_test(tc, test_trace_short_noarg);
tcase_add_test(tc, test_trace_short_followarg);
tcase_add_test(tc, test_trace_short_space);
tcase_add_test(tc, test_trace_short_equals);
tcase_add_test(tc, test_trace_long_noarg);
tcase_add_test(tc, test_trace_long_followarg);
tcase_add_test(tc, test_trace_long_space);
tcase_add_test(tc, test_trace_long_equals);
suite_add_tcase(s, tc);
tc = tcase_create("event_configuration");
tcase_add_test(tc, test_event_short);
tcase_add_test(tc, test_event_long);
tcase_add_test(tc, test_filter);
tcase_add_test(tc, test_trigger);
suite_add_tcase(s, tc);
tc = tcase_create("cpu_configuration");
tcase_add_test(tc, test_cpus_short);
tcase_add_test(tc, test_cpus_long);
tcase_add_test(tc, test_housekeeping_short);
tcase_add_test(tc, test_housekeeping_long);
suite_add_tcase(s, tc);
tc = tcase_create("thread_configuration");
tcase_add_test(tc, test_cgroup_short_noarg);
tcase_add_test(tc, test_cgroup_short_space);
tcase_add_test(tc, test_cgroup_short_equals);
tcase_add_test(tc, test_cgroup_long_noarg);
tcase_add_test(tc, test_cgroup_long_space);
tcase_add_test(tc, test_cgroup_long_equals);
tcase_add_test(tc, test_kernel_threads_short);
tcase_add_test(tc, test_kernel_threads_long);
tcase_add_test(tc, test_priority_short);
tcase_add_test(tc, test_priority_long);
tcase_add_test(tc, test_user_load_short);
tcase_add_test(tc, test_user_load_long);
tcase_add_test(tc, test_user_threads_short);
tcase_add_test(tc, test_user_threads_long);
tcase_add_test(tc, test_aligned_short);
tcase_add_test(tc, test_aligned_long);
suite_add_tcase(s, tc);
tc = tcase_create("histogram_options");
tcase_add_test(tc, test_bucket_size_short);
tcase_add_test(tc, test_bucket_size_long);
tcase_add_test(tc, test_entries_short);
tcase_add_test(tc, test_entries_long);
tcase_add_test(tc, test_no_header);
tcase_add_test(tc, test_no_index);
tcase_add_test(tc, test_no_irq);
tcase_add_test(tc, test_no_summary);
tcase_add_test(tc, test_no_thread);
tcase_add_test(tc, test_with_zeros);
suite_add_tcase(s, tc);
tc = tcase_create("output");
tcase_add_test(tc, test_nano_short);
tcase_add_test(tc, test_nano_long);
suite_add_tcase(s, tc);
tc = tcase_create("system_tuning");
tcase_add_test(tc, test_deepest_idle_state);
tcase_add_test(tc, test_dma_latency);
tcase_add_test(tc, test_trace_buffer_size);
tcase_add_test(tc, test_warm_up);
suite_add_tcase(s, tc);
tc = tcase_create("aa_actions");
tcase_add_test(tc, test_auto);
tcase_add_test(tc, test_bpf_action);
tcase_add_test(tc, test_dump_tasks);
tcase_add_test(tc, test_no_aa);
tcase_add_test(tc, test_on_end);
tcase_add_test(tc, test_on_threshold);
tcase_add_test(tc, test_stack_format);
suite_add_tcase(s, tc);
tc = tcase_create("general");
tcase_add_test(tc, test_debug_short);
tcase_add_test(tc, test_debug_long);
tcase_add_test(tc, test_duration_short);
tcase_add_test(tc, test_duration_long);
suite_add_tcase(s, tc);
return s;
}
@@ -0,0 +1,654 @@
// 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 <linux/container_of.h>
#include "cli_params_assert.h"
#include "../../src/cli.h"
#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
int argc = sizeof(argv) / sizeof(char *) - 1;\
struct common_params *params =\
timerlat_top_parse_args(argc, argv);\
struct timerlat_params *tlat_params __maybe_unused =\
to_timerlat_params(params)
/* Tracing Options */
START_TEST(test_irq_short)
{
PARSE_ARGS("timerlat", "top", "-i", "20");
ck_assert_int_eq(params->stop_us, 20);
}
END_TEST
START_TEST(test_irq_long)
{
PARSE_ARGS("timerlat", "top", "--irq", "20");
ck_assert_int_eq(params->stop_us, 20);
}
END_TEST
START_TEST(test_period_short)
{
PARSE_ARGS("timerlat", "top", "-p", "200");
ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
}
END_TEST
START_TEST(test_period_long)
{
PARSE_ARGS("timerlat", "top", "--period", "200");
ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
}
END_TEST
START_TEST(test_stack_short)
{
PARSE_ARGS("timerlat", "top", "-s", "20");
ck_assert_int_eq(tlat_params->print_stack, 20);
}
END_TEST
START_TEST(test_stack_long)
{
PARSE_ARGS("timerlat", "top", "--stack", "20");
ck_assert_int_eq(tlat_params->print_stack, 20);
}
END_TEST
START_TEST(test_thread_short)
{
PARSE_ARGS("timerlat", "top", "-T", "20");
ck_assert_int_eq(params->stop_total_us, 20);
}
END_TEST
START_TEST(test_thread_long)
{
PARSE_ARGS("timerlat", "top", "--thread", "20");
ck_assert_int_eq(params->stop_total_us, 20);
}
END_TEST
/* Event Configuration */
START_TEST(test_event_short)
{
PARSE_ARGS("timerlat", "top", "-e", "system:event");
CLI_ASSERT_SINGLE_EVENT("system", "event");
}
END_TEST
START_TEST(test_event_long)
{
PARSE_ARGS("timerlat", "top", "--event", "system:event");
CLI_ASSERT_SINGLE_EVENT("system", "event");
}
END_TEST
START_TEST(test_filter)
{
PARSE_ARGS("timerlat", "top", "-e", "system:event", "--filter", "filter");
CLI_ASSERT_SINGLE_FILTER("filter");
}
END_TEST
START_TEST(test_trigger)
{
PARSE_ARGS("timerlat", "top", "-e", "system:event", "--trigger", "trigger");
CLI_ASSERT_SINGLE_TRIGGER("trigger");
}
END_TEST
START_TEST(test_trace_short_noarg)
{
PARSE_ARGS("timerlat", "top", "-t");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
}
END_TEST
START_TEST(test_trace_short_followarg)
{
PARSE_ARGS("timerlat", "top", "-t", "-d", "20");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
}
END_TEST
START_TEST(test_trace_short_space)
{
PARSE_ARGS("timerlat", "top", "-t", "tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_short_equals)
{
PARSE_ARGS("timerlat", "top", "-t=tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_long_noarg)
{
PARSE_ARGS("timerlat", "top", "--trace");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
}
END_TEST
START_TEST(test_trace_long_followarg)
{
PARSE_ARGS("timerlat", "top", "--trace", "-d", "20");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
}
END_TEST
START_TEST(test_trace_long_space)
{
PARSE_ARGS("timerlat", "top", "--trace", "tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
START_TEST(test_trace_long_equals)
{
PARSE_ARGS("timerlat", "top", "--trace=tracefile");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"tracefile");
}
END_TEST
/* CPU Configuration */
START_TEST(test_cpus_short)
{
nr_cpus = 4;
PARSE_ARGS("timerlat", "top", "-c", "0-1,3");
ck_assert_str_eq(params->cpus, "0-1,3");
CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
}
END_TEST
START_TEST(test_cpus_long)
{
nr_cpus = 4;
PARSE_ARGS("timerlat", "top", "--cpus", "0-1,3");
ck_assert_str_eq(params->cpus, "0-1,3");
CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
}
END_TEST
START_TEST(test_housekeeping_short)
{
nr_cpus = 4;
PARSE_ARGS("timerlat", "top", "-H", "0-1,3");
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
}
END_TEST
START_TEST(test_housekeeping_long)
{
nr_cpus = 4;
PARSE_ARGS("timerlat", "top", "--house-keeping", "0-1,3");
CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
}
END_TEST
/* Thread Configuration */
START_TEST(test_cgroup_short_noarg)
{
PARSE_ARGS("timerlat", "top", "-C");
ck_assert(params->cgroup);
ck_assert_ptr_null(params->cgroup_name);
}
END_TEST
START_TEST(test_cgroup_short_space)
{
PARSE_ARGS("timerlat", "top", "-C", "cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_short_equals)
{
PARSE_ARGS("timerlat", "top", "-C=cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_long_noarg)
{
PARSE_ARGS("timerlat", "top", "--cgroup");
ck_assert(params->cgroup);
ck_assert_ptr_null(params->cgroup_name);
}
END_TEST
START_TEST(test_cgroup_long_space)
{
PARSE_ARGS("timerlat", "top", "--cgroup", "cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_cgroup_long_equals)
{
PARSE_ARGS("timerlat", "top", "--cgroup=cgroup");
ck_assert(params->cgroup);
ck_assert_str_eq(params->cgroup_name, "cgroup");
}
END_TEST
START_TEST(test_kernel_threads_short)
{
PARSE_ARGS("timerlat", "top", "-k");
ck_assert(params->kernel_workload);
ck_assert(!params->user_workload);
ck_assert(!params->user_data);
}
END_TEST
START_TEST(test_kernel_threads_long)
{
PARSE_ARGS("timerlat", "top", "--kernel-threads");
ck_assert(params->kernel_workload);
ck_assert(!params->user_workload);
ck_assert(!params->user_data);
}
END_TEST
START_TEST(test_priority_short)
{
PARSE_ARGS("timerlat", "top", "-P", "f:95");
ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params->sched_param.sched_priority, 95);
}
END_TEST
START_TEST(test_priority_long)
{
PARSE_ARGS("timerlat", "top", "--priority", "f:95");
ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
ck_assert_int_eq(params->sched_param.sched_priority, 95);
}
END_TEST
START_TEST(test_user_load_short)
{
PARSE_ARGS("timerlat", "top", "-U");
ck_assert(!params->kernel_workload);
ck_assert(!params->user_workload);
ck_assert(params->user_data);
}
END_TEST
START_TEST(test_user_load_long)
{
PARSE_ARGS("timerlat", "top", "--user-load");
ck_assert(!params->kernel_workload);
ck_assert(!params->user_workload);
ck_assert(params->user_data);
}
END_TEST
START_TEST(test_user_threads_short)
{
PARSE_ARGS("timerlat", "top", "-u");
ck_assert(!params->kernel_workload);
ck_assert(params->user_workload);
ck_assert(params->user_data);
}
END_TEST
START_TEST(test_user_threads_long)
{
PARSE_ARGS("timerlat", "top", "--user-threads");
ck_assert(!params->kernel_workload);
ck_assert(params->user_workload);
ck_assert(params->user_data);
}
END_TEST
START_TEST(test_aligned_short)
{
PARSE_ARGS("timerlat", "top", "-A", "500");
ck_assert(tlat_params->timerlat_align);
ck_assert_int_eq(tlat_params->timerlat_align_us, 500);
}
END_TEST
START_TEST(test_aligned_long)
{
PARSE_ARGS("timerlat", "top", "--aligned", "500");
ck_assert(tlat_params->timerlat_align);
ck_assert_int_eq(tlat_params->timerlat_align_us, 500);
}
END_TEST
/* Output */
START_TEST(test_nano_short)
{
PARSE_ARGS("timerlat", "top", "-n");
ck_assert_int_eq(params->output_divisor, 1);
}
END_TEST
START_TEST(test_nano_long)
{
PARSE_ARGS("timerlat", "top", "--nano");
ck_assert_int_eq(params->output_divisor, 1);
}
END_TEST
START_TEST(test_quiet_short)
{
PARSE_ARGS("timerlat", "top", "-q");
ck_assert(params->quiet);
}
END_TEST
START_TEST(test_quiet_long)
{
PARSE_ARGS("timerlat", "top", "--quiet");
ck_assert(params->quiet);
}
END_TEST
/* System Tuning */
START_TEST(test_deepest_idle_state)
{
PARSE_ARGS("timerlat", "top", "--deepest-idle-state", "1");
ck_assert_int_eq(tlat_params->deepest_idle_state, 1);
}
END_TEST
START_TEST(test_dma_latency)
{
PARSE_ARGS("timerlat", "top", "--dma-latency", "10");
ck_assert_int_eq(tlat_params->dma_latency, 10);
}
END_TEST
START_TEST(test_trace_buffer_size)
{
PARSE_ARGS("timerlat", "top", "--trace-buffer-size", "200");
ck_assert_int_eq(params->buffer_size, 200);
}
END_TEST
START_TEST(test_warm_up)
{
PARSE_ARGS("timerlat", "top", "--warm-up", "5");
ck_assert_int_eq(params->warmup, 5);
}
END_TEST
/* Auto Analysis and Actions */
START_TEST(test_auto)
{
PARSE_ARGS("timerlat", "top", "-a", "20");
CLI_TIMERLAT_ASSERT_AUTO(20);
}
END_TEST
START_TEST(test_aa_only)
{
PARSE_ARGS("timerlat", "top", "--aa-only", "20");
CLI_TIMERLAT_ASSERT_AA_ONLY(20);
}
END_TEST
START_TEST(test_bpf_action)
{
PARSE_ARGS("timerlat", "top", "--bpf-action", "program");
ck_assert_str_eq(tlat_params->bpf_action_program, "program");
}
END_TEST
START_TEST(test_dump_tasks)
{
PARSE_ARGS("timerlat", "top", "--dump-tasks");
ck_assert(tlat_params->dump_tasks);
}
END_TEST
START_TEST(test_no_aa)
{
PARSE_ARGS("timerlat", "top", "--no-aa");
ck_assert(tlat_params->no_aa);
}
END_TEST
START_TEST(test_on_end)
{
PARSE_ARGS("timerlat", "top", "--on-end", "trace");
CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
}
END_TEST
START_TEST(test_on_threshold)
{
PARSE_ARGS("timerlat", "top", "--on-threshold", "trace");
CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
"timerlat_trace.txt");
}
END_TEST
START_TEST(test_stack_format)
{
PARSE_ARGS("timerlat", "top", "--stack-format", "truncate");
ck_assert_int_eq(tlat_params->stack_format, STACK_FORMAT_TRUNCATE);
}
END_TEST
/* General */
START_TEST(test_debug_short)
{
PARSE_ARGS("timerlat", "top", "-D");
ck_assert(config_debug);
}
END_TEST
START_TEST(test_debug_long)
{
PARSE_ARGS("timerlat", "top", "--debug");
ck_assert(config_debug);
}
END_TEST
START_TEST(test_duration_short)
{
PARSE_ARGS("timerlat", "top", "-d", "1m");
ck_assert_int_eq(params->duration, 60);
}
END_TEST
START_TEST(test_duration_long)
{
PARSE_ARGS("timerlat", "top", "--duration", "1m");
ck_assert_int_eq(params->duration, 60);
}
END_TEST
Suite *timerlat_top_cli_suite(void)
{
Suite *s = suite_create("timerlat_top_cli");
TCase *tc;
tc = tcase_create("tracing_options");
tcase_add_test(tc, test_irq_short);
tcase_add_test(tc, test_irq_long);
tcase_add_test(tc, test_period_short);
tcase_add_test(tc, test_period_long);
tcase_add_test(tc, test_stack_short);
tcase_add_test(tc, test_stack_long);
tcase_add_test(tc, test_thread_short);
tcase_add_test(tc, test_thread_long);
tcase_add_test(tc, test_trace_short_noarg);
tcase_add_test(tc, test_trace_short_followarg);
tcase_add_test(tc, test_trace_short_space);
tcase_add_test(tc, test_trace_short_equals);
tcase_add_test(tc, test_trace_long_noarg);
tcase_add_test(tc, test_trace_long_followarg);
tcase_add_test(tc, test_trace_long_space);
tcase_add_test(tc, test_trace_long_equals);
suite_add_tcase(s, tc);
tc = tcase_create("event_configuration");
tcase_add_test(tc, test_event_short);
tcase_add_test(tc, test_event_long);
tcase_add_test(tc, test_filter);
tcase_add_test(tc, test_trigger);
suite_add_tcase(s, tc);
tc = tcase_create("cpu_configuration");
tcase_add_test(tc, test_cpus_short);
tcase_add_test(tc, test_cpus_long);
tcase_add_test(tc, test_housekeeping_short);
tcase_add_test(tc, test_housekeeping_long);
suite_add_tcase(s, tc);
tc = tcase_create("thread_configuration");
tcase_add_test(tc, test_cgroup_short_noarg);
tcase_add_test(tc, test_cgroup_short_space);
tcase_add_test(tc, test_cgroup_short_equals);
tcase_add_test(tc, test_cgroup_long_noarg);
tcase_add_test(tc, test_cgroup_long_space);
tcase_add_test(tc, test_cgroup_long_equals);
tcase_add_test(tc, test_kernel_threads_short);
tcase_add_test(tc, test_kernel_threads_long);
tcase_add_test(tc, test_priority_short);
tcase_add_test(tc, test_priority_long);
tcase_add_test(tc, test_user_load_short);
tcase_add_test(tc, test_user_load_long);
tcase_add_test(tc, test_user_threads_short);
tcase_add_test(tc, test_user_threads_long);
tcase_add_test(tc, test_aligned_short);
tcase_add_test(tc, test_aligned_long);
suite_add_tcase(s, tc);
tc = tcase_create("output");
tcase_add_test(tc, test_nano_short);
tcase_add_test(tc, test_nano_long);
tcase_add_test(tc, test_quiet_short);
tcase_add_test(tc, test_quiet_long);
suite_add_tcase(s, tc);
tc = tcase_create("system_tuning");
tcase_add_test(tc, test_deepest_idle_state);
tcase_add_test(tc, test_dma_latency);
tcase_add_test(tc, test_trace_buffer_size);
tcase_add_test(tc, test_warm_up);
suite_add_tcase(s, tc);
tc = tcase_create("aa_actions");
tcase_add_test(tc, test_auto);
tcase_add_test(tc, test_aa_only);
tcase_add_test(tc, test_bpf_action);
tcase_add_test(tc, test_dump_tasks);
tcase_add_test(tc, test_no_aa);
tcase_add_test(tc, test_on_end);
tcase_add_test(tc, test_on_threshold);
tcase_add_test(tc, test_stack_format);
suite_add_tcase(s, tc);
tc = tcase_create("general");
tcase_add_test(tc, test_debug_short);
tcase_add_test(tc, test_debug_long);
tcase_add_test(tc, test_duration_short);
tcase_add_test(tc, test_duration_long);
suite_add_tcase(s, tc);
return s;
}
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <check.h>
#include <stdbool.h>
#include "../../src/utils.h"
#include "../../src/cli.h"
Suite *utils_suite(void);
Suite *actions_suite(void);
Suite *osnoise_top_cli_suite(void);
Suite *osnoise_hist_cli_suite(void);
Suite *timerlat_top_cli_suite(void);
Suite *timerlat_hist_cli_suite(void);
Suite *cli_opt_callback_suite(void);
int main(int argc, char *argv[])
{
int num_failed;
SRunner *sr;
in_unit_test = true;
sr = srunner_create(utils_suite());
srunner_add_suite(sr, cli_opt_callback_suite());
srunner_add_suite(sr, actions_suite());
srunner_add_suite(sr, osnoise_top_cli_suite());
srunner_add_suite(sr, osnoise_hist_cli_suite());
srunner_add_suite(sr, timerlat_top_cli_suite());
srunner_add_suite(sr, timerlat_hist_cli_suite());
srunner_run_all(sr, CK_VERBOSE);
num_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (num_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
@@ -0,0 +1,106 @@
// 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"
extern 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;
}