1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
4 */
5 #define _GNU_SOURCE
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <pthread.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <stdio.h>
15 #include <sched.h>
16
17 #include "timerlat.h"
18 #include "timerlat_aa.h"
19 #include "timerlat_bpf.h"
20
21 #define DEFAULT_TIMERLAT_PERIOD 1000 /* 1ms */
22
23 static int dma_latency_fd = -1;
24
25 /*
26 * timerlat_apply_config - apply common configs to the initialized tool
27 */
28 int
timerlat_apply_config(struct osnoise_tool * tool,struct timerlat_params * params)29 timerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params)
30 {
31 int retval;
32
33 /*
34 * Try to enable BPF, unless disabled explicitly.
35 * If BPF enablement fails, fall back to tracefs mode.
36 */
37 if (getenv("RTLA_NO_BPF") && strncmp(getenv("RTLA_NO_BPF"), "1", 2) == 0) {
38 debug_msg("RTLA_NO_BPF set, disabling BPF\n");
39 params->mode = TRACING_MODE_TRACEFS;
40 } else if (!tep_find_event_by_name(tool->trace.tep, "osnoise", "timerlat_sample")) {
41 debug_msg("osnoise:timerlat_sample missing, disabling BPF\n");
42 params->mode = TRACING_MODE_TRACEFS;
43 } else {
44 retval = timerlat_bpf_init(params);
45 if (retval) {
46 debug_msg("Could not enable BPF\n");
47 params->mode = TRACING_MODE_TRACEFS;
48 }
49 }
50
51 if (params->mode != TRACING_MODE_BPF) {
52 /*
53 * In tracefs and mixed mode, timerlat tracer handles stopping
54 * on threshold
55 */
56 retval = osnoise_set_stop_us(tool->context, params->common.stop_us);
57 if (retval) {
58 err_msg("Failed to set stop us\n");
59 goto out_err;
60 }
61
62 retval = osnoise_set_stop_total_us(tool->context, params->common.stop_total_us);
63 if (retval) {
64 err_msg("Failed to set stop total us\n");
65 goto out_err;
66 }
67 }
68
69
70 retval = osnoise_set_timerlat_period_us(tool->context,
71 params->timerlat_period_us ?
72 params->timerlat_period_us :
73 DEFAULT_TIMERLAT_PERIOD);
74 if (retval) {
75 err_msg("Failed to set timerlat period\n");
76 goto out_err;
77 }
78
79
80 retval = osnoise_set_print_stack(tool->context, params->print_stack);
81 if (retval) {
82 err_msg("Failed to set print stack\n");
83 goto out_err;
84 }
85
86 /*
87 * If the user did not specify a type of thread, try user-threads first.
88 * Fall back to kernel threads otherwise.
89 */
90 if (!params->common.kernel_workload && !params->common.user_data) {
91 retval = tracefs_file_exists(NULL, "osnoise/per_cpu/cpu0/timerlat_fd");
92 if (retval) {
93 debug_msg("User-space interface detected, setting user-threads\n");
94 params->common.user_workload = 1;
95 params->common.user_data = 1;
96 } else {
97 debug_msg("User-space interface not detected, setting kernel-threads\n");
98 params->common.kernel_workload = 1;
99 }
100 }
101
102 return common_apply_config(tool, ¶ms->common);
103
104 out_err:
105 return -1;
106 }
107
timerlat_enable(struct osnoise_tool * tool)108 int timerlat_enable(struct osnoise_tool *tool)
109 {
110 struct timerlat_params *params = to_timerlat_params(tool->params);
111 int retval, nr_cpus, i;
112
113 if (params->dma_latency >= 0) {
114 dma_latency_fd = set_cpu_dma_latency(params->dma_latency);
115 if (dma_latency_fd < 0) {
116 err_msg("Could not set /dev/cpu_dma_latency.\n");
117 return -1;
118 }
119 }
120
121 if (params->deepest_idle_state >= -1) {
122 if (!have_libcpupower_support()) {
123 err_msg("rtla built without libcpupower, --deepest-idle-state is not supported\n");
124 return -1;
125 }
126
127 nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
128
129 for (i = 0; i < nr_cpus; i++) {
130 if (params->common.cpus && !CPU_ISSET(i, ¶ms->common.monitored_cpus))
131 continue;
132 if (save_cpu_idle_disable_state(i) < 0) {
133 err_msg("Could not save cpu idle state.\n");
134 return -1;
135 }
136 if (set_deepest_cpu_idle_state(i, params->deepest_idle_state) < 0) {
137 err_msg("Could not set deepest cpu idle state.\n");
138 return -1;
139 }
140 }
141 }
142
143 if (!params->no_aa) {
144 tool->aa = osnoise_init_tool("timerlat_aa");
145 if (!tool->aa)
146 return -1;
147
148 retval = timerlat_aa_init(tool->aa, params->dump_tasks);
149 if (retval) {
150 err_msg("Failed to enable the auto analysis instance\n");
151 return retval;
152 }
153
154 retval = enable_tracer_by_name(tool->aa->trace.inst, "timerlat");
155 if (retval) {
156 err_msg("Failed to enable aa tracer\n");
157 return retval;
158 }
159 }
160
161 if (params->common.warmup > 0) {
162 debug_msg("Warming up for %d seconds\n", params->common.warmup);
163 sleep(params->common.warmup);
164 if (stop_tracing)
165 return -1;
166 }
167
168 /*
169 * Start the tracers here, after having set all instances.
170 *
171 * Let the trace instance start first for the case of hitting a stop
172 * tracing while enabling other instances. The trace instance is the
173 * one with most valuable information.
174 */
175 if (tool->record)
176 trace_instance_start(&tool->record->trace);
177 if (!params->no_aa)
178 trace_instance_start(&tool->aa->trace);
179 if (params->mode == TRACING_MODE_TRACEFS) {
180 trace_instance_start(&tool->trace);
181 } else {
182 retval = timerlat_bpf_attach();
183 if (retval) {
184 err_msg("Error attaching BPF program\n");
185 return retval;
186 }
187 }
188
189 return 0;
190 }
191
timerlat_analyze(struct osnoise_tool * tool,bool stopped)192 void timerlat_analyze(struct osnoise_tool *tool, bool stopped)
193 {
194 struct timerlat_params *params = to_timerlat_params(tool->params);
195
196 if (stopped) {
197 if (!params->no_aa)
198 timerlat_auto_analysis(params->common.stop_us,
199 params->common.stop_total_us);
200 } else if (params->common.aa_only) {
201 char *max_lat;
202
203 /*
204 * If the trace did not stop with --aa-only, at least print
205 * the max known latency.
206 */
207 max_lat = tracefs_instance_file_read(trace_inst->inst, "tracing_max_latency", NULL);
208 if (max_lat) {
209 printf(" Max latency was %s\n", max_lat);
210 free(max_lat);
211 }
212 }
213 }
214
timerlat_free(struct osnoise_tool * tool)215 void timerlat_free(struct osnoise_tool *tool)
216 {
217 struct timerlat_params *params = to_timerlat_params(tool->params);
218 int nr_cpus, i;
219
220 timerlat_aa_destroy();
221 if (dma_latency_fd >= 0)
222 close(dma_latency_fd);
223 if (params->deepest_idle_state >= -1) {
224 for (i = 0; i < nr_cpus; i++) {
225 if (params->common.cpus &&
226 !CPU_ISSET(i, ¶ms->common.monitored_cpus))
227 continue;
228 restore_cpu_idle_disable_state(i);
229 }
230 }
231
232 osnoise_destroy_tool(tool->aa);
233
234 if (params->mode != TRACING_MODE_TRACEFS)
235 timerlat_bpf_destroy();
236 free_cpu_idle_disable_states();
237 }
238
timerlat_usage(int err)239 static void timerlat_usage(int err)
240 {
241 int i;
242
243 static const char * const msg[] = {
244 "",
245 "timerlat version " VERSION,
246 "",
247 " usage: [rtla] timerlat [MODE] ...",
248 "",
249 " modes:",
250 " top - prints the summary from timerlat tracer",
251 " hist - prints a histogram of timer latencies",
252 "",
253 "if no MODE is given, the top mode is called, passing the arguments",
254 NULL,
255 };
256
257 for (i = 0; msg[i]; i++)
258 fprintf(stderr, "%s\n", msg[i]);
259 exit(err);
260 }
261
timerlat_main(int argc,char * argv[])262 int timerlat_main(int argc, char *argv[])
263 {
264 if (argc == 0)
265 goto usage;
266
267 /*
268 * if timerlat was called without any argument, run the
269 * default cmdline.
270 */
271 if (argc == 1) {
272 run_tool(&timerlat_top_ops, argc, argv);
273 exit(0);
274 }
275
276 if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
277 timerlat_usage(0);
278 } else if (strncmp(argv[1], "-", 1) == 0) {
279 /* the user skipped the tool, call the default one */
280 run_tool(&timerlat_top_ops, argc, argv);
281 exit(0);
282 } else if (strcmp(argv[1], "top") == 0) {
283 run_tool(&timerlat_top_ops, argc-1, &argv[1]);
284 exit(0);
285 } else if (strcmp(argv[1], "hist") == 0) {
286 run_tool(&timerlat_hist_ops, argc-1, &argv[1]);
287 exit(0);
288 }
289
290 usage:
291 timerlat_usage(1);
292 exit(1);
293 }
294