| // 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, ¶ms->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, ¶ms->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, ¶ms->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); | |
| } |