226 lines
4.6 KiB
C
226 lines
4.6 KiB
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) {
|
|
err_msg("Error setting timerlat u default priority: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
} 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) {
|
|
err_msg("Error opening %s:%s\n", buffer, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
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);
|
|
|
|
}
|