root/tools/tracing/rtla/src/timerlat_u.c
// 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 "utils.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[1024];
        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, 1024);
                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 nr_cpus)
{
        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)
{
        int nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
        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, nr_cpus);
                        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, nr_cpus);

        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);

}