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