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