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 */
timerlat_u_main(int cpu,struct timerlat_u_params * params)32 static 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 fatal("Error setting timerlat u default priority: %s", strerror(errno));
56 } else {
57 retval = __set_sched_attr(getpid(), params->sched_param);
58 if (retval) {
59 /* __set_sched_attr prints an error message, so */
60 exit(0);
61 }
62 }
63
64 if (params->cgroup_name) {
65 retval = set_pid_cgroup(gettid(), params->cgroup_name);
66 if (!retval) {
67 err_msg("Error setting timerlat u cgroup pid\n");
68 pthread_exit(&retval);
69 }
70 }
71
72 /*
73 * This is the tool's loop. If you want to use as base for your own tool...
74 * go ahead.
75 */
76 snprintf(buffer, sizeof(buffer), "osnoise/per_cpu/cpu%d/timerlat_fd", cpu);
77
78 timerlat_fd = tracefs_instance_file_open(NULL, buffer, O_RDONLY);
79 if (timerlat_fd < 0)
80 fatal("Error opening %s:%s", buffer, strerror(errno));
81
82 debug_msg("User-space timerlat pid %d on cpu %d\n", gettid(), cpu);
83
84 /* add should continue with a signal handler */
85 while (true) {
86 retval = read(timerlat_fd, buffer, 1024);
87 if (retval < 0)
88 break;
89 }
90
91 close(timerlat_fd);
92
93 debug_msg("Leaving timerlat pid %d on cpu %d\n", gettid(), cpu);
94 exit(0);
95 }
96
97 /*
98 * timerlat_u_send_kill - send a kill signal for all processes
99 *
100 * Return the number of processes that received the kill.
101 */
timerlat_u_send_kill(pid_t * procs,int nr_cpus)102 static int timerlat_u_send_kill(pid_t *procs, int nr_cpus)
103 {
104 int killed = 0;
105 int i, retval;
106
107 for (i = 0; i < nr_cpus; i++) {
108 if (!procs[i])
109 continue;
110 retval = kill(procs[i], SIGKILL);
111 if (!retval)
112 killed++;
113 else
114 err_msg("Error killing child process %d\n", procs[i]);
115 }
116
117 return killed;
118 }
119
120 /**
121 * timerlat_u_dispatcher - dispatch one timerlatu/ process per monitored CPU
122 *
123 * This is a thread main that will fork one new process for each monitored
124 * CPU. It will wait for:
125 *
126 * - rtla to tell to kill the child processes
127 * - some child process to die, and the cleanup all the processes
128 *
129 * whichever comes first.
130 *
131 */
timerlat_u_dispatcher(void * data)132 void *timerlat_u_dispatcher(void *data)
133 {
134 int nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
135 struct timerlat_u_params *params = data;
136 char proc_name[128];
137 int procs_count = 0;
138 int retval = 1;
139 pid_t *procs;
140 int wstatus;
141 pid_t pid;
142 int i;
143
144 debug_msg("Dispatching timerlat u procs\n");
145
146 procs = calloc(nr_cpus, sizeof(pid_t));
147 if (!procs)
148 pthread_exit(&retval);
149
150 for (i = 0; i < nr_cpus; i++) {
151 if (params->set && !CPU_ISSET(i, params->set))
152 continue;
153
154 pid = fork();
155
156 /* child */
157 if (!pid) {
158
159 /*
160 * rename the process
161 */
162 snprintf(proc_name, sizeof(proc_name), "timerlatu/%d", i);
163 pthread_setname_np(pthread_self(), proc_name);
164 prctl(PR_SET_NAME, (unsigned long)proc_name, 0, 0, 0);
165
166 timerlat_u_main(i, params);
167 /* timerlat_u_main should exit()! Anyways... */
168 pthread_exit(&retval);
169 }
170
171 /* parent */
172 if (pid == -1) {
173 timerlat_u_send_kill(procs, nr_cpus);
174 debug_msg("Failed to create child processes");
175 pthread_exit(&retval);
176 }
177
178 procs_count++;
179 procs[i] = pid;
180 }
181
182 while (params->should_run) {
183 /* check if processes died */
184 pid = waitpid(-1, &wstatus, WNOHANG);
185 if (pid != 0) {
186 for (i = 0; i < nr_cpus; i++) {
187 if (procs[i] == pid) {
188 procs[i] = 0;
189 procs_count--;
190 }
191 }
192
193 if (!procs_count)
194 break;
195 }
196
197 sleep(1);
198 }
199
200 timerlat_u_send_kill(procs, nr_cpus);
201
202 while (procs_count) {
203 pid = waitpid(-1, &wstatus, 0);
204 if (pid == -1) {
205 err_msg("Failed to monitor child processes");
206 pthread_exit(&retval);
207 }
208 for (i = 0; i < nr_cpus; i++) {
209 if (procs[i] == pid) {
210 procs[i] = 0;
211 procs_count--;
212 }
213 }
214 }
215
216 params->stopped_running = 1;
217
218 free(procs);
219 retval = 0;
220 pthread_exit(&retval);
221
222 }
223