1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2023 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
4 */
5
6#define _GNU_SOURCE
7#include <sched.h>
8#include <fcntl.h>
9#include <stdlib.h>
10#include <unistd.h>
11#include <stdio.h>
12#include <errno.h>
13#include <string.h>
14#include <tracefs.h>
15#include <pthread.h>
16#include <sys/wait.h>
17#include <sys/prctl.h>
18
19#include "utils.h"
20#include "timerlat_u.h"
21
22/*
23 * This is the user-space main for the tool timerlatu/ threads.
24 *
25 * It is as simple as this:
26 *  - set affinity
27 *  - set priority
28 *  - open tracer fd
29 *  - spin
30 *  - close
31 */
32static int timerlat_u_main(int cpu, struct timerlat_u_params *params)
33{
34	struct sched_param sp = { .sched_priority = 95 };
35	char buffer[1024];
36	int timerlat_fd;
37	cpu_set_t set;
38	int retval;
39
40	/*
41	 * This all is only setting up the tool.
42	 */
43	CPU_ZERO(&set);
44	CPU_SET(cpu, &set);
45
46	retval = sched_setaffinity(gettid(), sizeof(set), &set);
47	if (retval == -1) {
48		debug_msg("Error setting user thread affinity %d, is the CPU online?\n", cpu);
49		exit(1);
50	}
51
52	if (!params->sched_param) {
53		retval = sched_setscheduler(0, SCHED_FIFO, &sp);
54		if (retval < 0) {
55			err_msg("Error setting timerlat u default priority: %s\n", strerror(errno));
56			exit(1);
57		}
58	} else {
59		retval = __set_sched_attr(getpid(), params->sched_param);
60		if (retval) {
61			/* __set_sched_attr prints an error message, so */
62			exit(0);
63		}
64	}
65
66	if (params->cgroup_name) {
67		retval = set_pid_cgroup(gettid(), params->cgroup_name);
68		if (!retval) {
69			err_msg("Error setting timerlat u cgroup pid\n");
70			pthread_exit(&retval);
71		}
72	}
73
74	/*
75	 * This is the tool's loop. If you want to use as base for your own tool...
76	 * go ahead.
77	 */
78	snprintf(buffer, sizeof(buffer), "osnoise/per_cpu/cpu%d/timerlat_fd", cpu);
79
80	timerlat_fd = tracefs_instance_file_open(NULL, buffer, O_RDONLY);
81	if (timerlat_fd < 0) {
82		err_msg("Error opening %s:%s\n", buffer, strerror(errno));
83		exit(1);
84	}
85
86	debug_msg("User-space timerlat pid %d on cpu %d\n", gettid(), cpu);
87
88	/* add should continue with a signal handler */
89	while (true) {
90		retval = read(timerlat_fd, buffer, 1024);
91		if (retval < 0)
92			break;
93	}
94
95	close(timerlat_fd);
96
97	debug_msg("Leaving timerlat pid %d on cpu %d\n", gettid(), cpu);
98	exit(0);
99}
100
101/*
102 * timerlat_u_send_kill - send a kill signal for all processes
103 *
104 * Return the number of processes that received the kill.
105 */
106static int timerlat_u_send_kill(pid_t *procs, int nr_cpus)
107{
108	int killed = 0;
109	int i, retval;
110
111	for (i = 0; i < nr_cpus; i++) {
112		if (!procs[i])
113			continue;
114		retval = kill(procs[i], SIGKILL);
115		if (!retval)
116			killed++;
117		else
118			err_msg("Error killing child process %d\n", procs[i]);
119	}
120
121	return killed;
122}
123
124/**
125 * timerlat_u_dispatcher - dispatch one timerlatu/ process per monitored CPU
126 *
127 * This is a thread main that will fork one new process for each monitored
128 * CPU. It will wait for:
129 *
130 *  - rtla to tell to kill the child processes
131 *  - some child process to die, and the cleanup all the processes
132 *
133 * whichever comes first.
134 *
135 */
136void *timerlat_u_dispatcher(void *data)
137{
138	int nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
139	struct timerlat_u_params *params = data;
140	char proc_name[128];
141	int procs_count = 0;
142	int retval = 1;
143	pid_t *procs;
144	int wstatus;
145	pid_t pid;
146	int i;
147
148	debug_msg("Dispatching timerlat u procs\n");
149
150	procs = calloc(nr_cpus, sizeof(pid_t));
151	if (!procs)
152		pthread_exit(&retval);
153
154	for (i = 0; i < nr_cpus; i++) {
155		if (params->set && !CPU_ISSET(i, params->set))
156			continue;
157
158		pid = fork();
159
160		/* child */
161		if (!pid) {
162
163			/*
164			 * rename the process
165			 */
166			snprintf(proc_name, sizeof(proc_name), "timerlatu/%d", i);
167			pthread_setname_np(pthread_self(), proc_name);
168			prctl(PR_SET_NAME, (unsigned long)proc_name, 0, 0, 0);
169
170			timerlat_u_main(i, params);
171			/* timerlat_u_main should exit()! Anyways... */
172			pthread_exit(&retval);
173		}
174
175		/* parent */
176		if (pid == -1) {
177			timerlat_u_send_kill(procs, nr_cpus);
178			debug_msg("Failed to create child processes");
179			pthread_exit(&retval);
180		}
181
182		procs_count++;
183		procs[i] = pid;
184	}
185
186	while (params->should_run) {
187		/* check if processes died */
188		pid = waitpid(-1, &wstatus, WNOHANG);
189		if (pid != 0) {
190			for (i = 0; i < nr_cpus; i++) {
191				if (procs[i] == pid) {
192					procs[i] = 0;
193					procs_count--;
194				}
195			}
196
197			if (!procs_count)
198				break;
199		}
200
201		sleep(1);
202	}
203
204	timerlat_u_send_kill(procs, nr_cpus);
205
206	while (procs_count) {
207		pid = waitpid(-1, &wstatus, 0);
208		if (pid == -1) {
209			err_msg("Failed to monitor child processes");
210			pthread_exit(&retval);
211		}
212		for (i = 0; i < nr_cpus; i++) {
213			if (procs[i] == pid) {
214				procs[i] = 0;
215				procs_count--;
216			}
217		}
218	}
219
220	params->stopped_running = 1;
221
222	free(procs);
223	retval = 0;
224	pthread_exit(&retval);
225
226}
227