root/tools/tracing/rtla/src/timerlat.c
// 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 "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;

        /*
         * Try to enable BPF, unless disabled explicitly.
         * If BPF enablement fails, fall back to tracefs mode.
         */
        if (getenv("RTLA_NO_BPF") && strncmp(getenv("RTLA_NO_BPF"), "1", 2) == 0) {
                debug_msg("RTLA_NO_BPF set, disabling BPF\n");
                params->mode = TRACING_MODE_TRACEFS;
        } else if (!tep_find_event_by_name(tool->trace.tep, "osnoise", "timerlat_sample")) {
                debug_msg("osnoise:timerlat_sample missing, disabling BPF\n");
                params->mode = TRACING_MODE_TRACEFS;
        } else {
                retval = timerlat_bpf_init(params);
                if (retval) {
                        debug_msg("Could not enable BPF\n");
                        params->mode = TRACING_MODE_TRACEFS;
                }
        }

        /* Check if BPF action program is requested but BPF is not available */
        if (params->bpf_action_program) {
                if (params->mode == TRACING_MODE_TRACEFS) {
                        err_msg("BPF actions are not supported in tracefs-only mode\n");
                        goto out_err;
                }

                if (timerlat_load_bpf_action_program(params->bpf_action_program))
                        goto out_err;
        }

        retval = osnoise_set_timerlat_period_us(tool->context,
                                                params->timerlat_period_us ?
                                                params->timerlat_period_us :
                                                DEFAULT_TIMERLAT_PERIOD);
        if (retval) {
                err_msg("Failed to set timerlat period\n");
                goto out_err;
        }


        retval = osnoise_set_print_stack(tool->context, params->print_stack);
        if (retval) {
                err_msg("Failed to set print stack\n");
                goto out_err;
        }

        /*
         * If the user did not specify a type of thread, try user-threads first.
         * Fall back to kernel threads otherwise.
         */
        if (!params->common.kernel_workload && !params->common.user_data) {
                retval = tracefs_file_exists(NULL, "osnoise/per_cpu/cpu0/timerlat_fd");
                if (retval) {
                        debug_msg("User-space interface detected, setting user-threads\n");
                        params->common.user_workload = 1;
                        params->common.user_data = 1;
                } else {
                        debug_msg("User-space interface not detected, setting kernel-threads\n");
                        params->common.kernel_workload = 1;
                }
        }

        return common_apply_config(tool, &params->common);

out_err:
        return -1;
}

int timerlat_enable(struct osnoise_tool *tool)
{
        struct timerlat_params *params = to_timerlat_params(tool->params);
        int retval, nr_cpus, 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;
                }

                nr_cpus = sysconf(_SC_NPROCESSORS_CONF);

                for_each_monitored_cpu(i, nr_cpus, &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);
                if (retval) {
                        err_msg("Failed to enable the auto analysis instance\n");
                        return retval;
                }

                retval = enable_tracer_by_name(tool->aa->trace.inst, "timerlat");
                if (retval) {
                        err_msg("Failed to enable aa tracer\n");
                        return retval;
                }
        }

        if (params->common.warmup > 0) {
                debug_msg("Warming up for %d seconds\n", params->common.warmup);
                sleep(params->common.warmup);
                if (stop_tracing)
                        return -1;
        }

        /*
         * Start the tracers here, after having set all instances.
         *
         * Let the trace instance start first for the case of hitting a stop
         * tracing while enabling other instances. The trace instance is the
         * one with most valuable information.
         */
        if (tool->record)
                trace_instance_start(&tool->record->trace);
        if (!params->no_aa)
                trace_instance_start(&tool->aa->trace);
        if (params->mode == TRACING_MODE_TRACEFS) {
                trace_instance_start(&tool->trace);
        } else {
                retval = timerlat_bpf_attach();
                if (retval) {
                        err_msg("Error attaching BPF program\n");
                        return retval;
                }
        }

        /*
         * In tracefs and mixed mode, timerlat tracer handles stopping
         * on threshold
         */
        if (params->mode != TRACING_MODE_BPF) {
                retval = osn_set_stop(tool);
                if (retval)
                        return retval;
        }

        return 0;
}

void timerlat_analyze(struct osnoise_tool *tool, bool stopped)
{
        struct timerlat_params *params = to_timerlat_params(tool->params);

        if (stopped) {
                if (!params->no_aa)
                        timerlat_auto_analysis(params->common.stop_us,
                                               params->common.stop_total_us);
        } else if (params->common.aa_only) {
                char *max_lat;

                /*
                 * If the trace did not stop with --aa-only, at least print
                 * the max known latency.
                 */
                max_lat = tracefs_instance_file_read(trace_inst->inst, "tracing_max_latency", NULL);
                if (max_lat) {
                        printf("  Max latency was %s\n", max_lat);
                        free(max_lat);
                }
        }
}

void timerlat_free(struct osnoise_tool *tool)
{
        struct timerlat_params *params = to_timerlat_params(tool->params);
        int nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
        int i;

        timerlat_aa_destroy();
        if (dma_latency_fd >= 0)
                close(dma_latency_fd);
        if (params->deepest_idle_state >= -1) {
                for_each_monitored_cpu(i, nr_cpus, &params->common) {
                        restore_cpu_idle_disable_state(i);
                }
        }

        osnoise_destroy_tool(tool->aa);

        if (params->mode != TRACING_MODE_TRACEFS)
                timerlat_bpf_destroy();
        free_cpu_idle_disable_states();
}

static void timerlat_usage(int err)
{
        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(0);
        } else if (strncmp(argv[1], "-", 1) == 0) {
                /* 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(1);
        exit(1);
}