xref: /linux/tools/tracing/rtla/src/timerlat.c (revision 244d0cbff2efa13931115784e5dc4d1270a04ec7)
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 	/*
81 	 * If the user did not specify a type of thread, try user-threads first.
82 	 * Fall back to kernel threads otherwise.
83 	 */
84 	if (!params->common.kernel_workload && !params->common.user_data) {
85 		retval = tracefs_file_exists(NULL, "osnoise/per_cpu/cpu0/timerlat_fd");
86 		if (retval) {
87 			debug_msg("User-space interface detected, setting user-threads\n");
88 			params->common.user_workload = 1;
89 			params->common.user_data = 1;
90 		} else {
91 			debug_msg("User-space interface not detected, setting kernel-threads\n");
92 			params->common.kernel_workload = 1;
93 		}
94 	}
95 
96 	return common_apply_config(tool, &params->common);
97 
98 out_err:
99 	return -1;
100 }
101 
102 int timerlat_enable(struct osnoise_tool *tool)
103 {
104 	struct timerlat_params *params = to_timerlat_params(tool->params);
105 	int retval, i;
106 
107 	if (params->dma_latency >= 0) {
108 		dma_latency_fd = set_cpu_dma_latency(params->dma_latency);
109 		if (dma_latency_fd < 0) {
110 			err_msg("Could not set /dev/cpu_dma_latency.\n");
111 			return -1;
112 		}
113 	}
114 
115 	if (params->deepest_idle_state >= -1) {
116 		if (!have_libcpupower_support()) {
117 			err_msg("rtla built without libcpupower, --deepest-idle-state is not supported\n");
118 			return -1;
119 		}
120 
121 		for_each_monitored_cpu(i, &params->common) {
122 			if (save_cpu_idle_disable_state(i) < 0) {
123 				err_msg("Could not save cpu idle state.\n");
124 				return -1;
125 			}
126 			if (set_deepest_cpu_idle_state(i, params->deepest_idle_state) < 0) {
127 				err_msg("Could not set deepest cpu idle state.\n");
128 				return -1;
129 			}
130 		}
131 	}
132 
133 	if (!params->no_aa) {
134 		tool->aa = osnoise_init_tool("timerlat_aa");
135 		if (!tool->aa)
136 			return -1;
137 
138 		retval = timerlat_aa_init(tool->aa, params->dump_tasks, params->stack_format);
139 		if (retval) {
140 			err_msg("Failed to enable the auto analysis instance\n");
141 			return retval;
142 		}
143 
144 		retval = enable_tracer_by_name(tool->aa->trace.inst, "timerlat");
145 		if (retval) {
146 			err_msg("Failed to enable aa tracer\n");
147 			return retval;
148 		}
149 	}
150 
151 	if (params->common.warmup > 0) {
152 		debug_msg("Warming up for %d seconds\n", params->common.warmup);
153 		sleep(params->common.warmup);
154 		if (stop_tracing)
155 			return -1;
156 	}
157 
158 	/*
159 	 * Start the tracers here, after having set all instances.
160 	 *
161 	 * Let the trace instance start first for the case of hitting a stop
162 	 * tracing while enabling other instances. The trace instance is the
163 	 * one with most valuable information.
164 	 */
165 	if (tool->record)
166 		trace_instance_start(&tool->record->trace);
167 	if (!params->no_aa)
168 		trace_instance_start(&tool->aa->trace);
169 	if (params->mode == TRACING_MODE_TRACEFS) {
170 		trace_instance_start(&tool->trace);
171 	} else {
172 		retval = timerlat_bpf_attach();
173 		if (retval) {
174 			err_msg("Error attaching BPF program\n");
175 			return retval;
176 		}
177 	}
178 
179 	/*
180 	 * In tracefs and mixed mode, timerlat tracer handles stopping
181 	 * on threshold
182 	 */
183 	if (params->mode != TRACING_MODE_BPF) {
184 		retval = osn_set_stop(tool);
185 		if (retval)
186 			return retval;
187 	}
188 
189 	return 0;
190 }
191 
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(tool->trace.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 
215 void timerlat_free(struct osnoise_tool *tool)
216 {
217 	struct timerlat_params *params = to_timerlat_params(tool->params);
218 	int 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_each_monitored_cpu(i, &params->common) {
225 			restore_cpu_idle_disable_state(i);
226 		}
227 	}
228 
229 	osnoise_destroy_tool(tool->aa);
230 
231 	if (params->mode != TRACING_MODE_TRACEFS)
232 		timerlat_bpf_destroy();
233 	free_cpu_idle_disable_states();
234 }
235 
236 __noreturn static void timerlat_usage(int err)
237 {
238 	int i;
239 
240 	static const char * const msg[] = {
241 		"",
242 		"timerlat version " VERSION,
243 		"",
244 		"  usage: [rtla] timerlat [MODE] ...",
245 		"",
246 		"  modes:",
247 		"     top   - prints the summary from timerlat tracer",
248 		"     hist  - prints a histogram of timer latencies",
249 		"",
250 		"if no MODE is given, the top mode is called, passing the arguments",
251 		NULL,
252 	};
253 
254 	for (i = 0; msg[i]; i++)
255 		fprintf(stderr, "%s\n", msg[i]);
256 	exit(err);
257 }
258 
259 int timerlat_main(int argc, char *argv[])
260 {
261 	if (argc == 0)
262 		goto usage;
263 
264 	/*
265 	 * if timerlat was called without any argument, run the
266 	 * default cmdline.
267 	 */
268 	if (argc == 1) {
269 		run_tool(&timerlat_top_ops, argc, argv);
270 		exit(0);
271 	}
272 
273 	if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
274 		timerlat_usage(129);
275 	} else if (str_has_prefix(argv[1], "-")) {
276 		/* the user skipped the tool, call the default one */
277 		run_tool(&timerlat_top_ops, argc, argv);
278 		exit(0);
279 	} else if (strcmp(argv[1], "top") == 0) {
280 		run_tool(&timerlat_top_ops, argc-1, &argv[1]);
281 		exit(0);
282 	} else if (strcmp(argv[1], "hist") == 0) {
283 		run_tool(&timerlat_hist_ops, argc-1, &argv[1]);
284 		exit(0);
285 	}
286 
287 usage:
288 	timerlat_usage(129);
289 }
290