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