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