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