File Index
Files and directories
Sign in
FILE

osnoise_top.c

File preview with syntax highlighting.

linux-6.19.14/tools/tracing/rtla/src/osnoise_top.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
  4 */
  5
  6#define _GNU_SOURCE
  7#include <getopt.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <signal.h>
 11#include <unistd.h>
 12#include <stdio.h>
 13#include <time.h>
 14
 15#include "osnoise.h"
 16
 17struct osnoise_top_cpu {
 18	unsigned long long	sum_runtime;
 19	unsigned long long	sum_noise;
 20	unsigned long long	max_noise;
 21	unsigned long long	max_sample;
 22
 23	unsigned long long	hw_count;
 24	unsigned long long	nmi_count;
 25	unsigned long long	irq_count;
 26	unsigned long long	softirq_count;
 27	unsigned long long	thread_count;
 28
 29	int			sum_cycles;
 30};
 31
 32struct osnoise_top_data {
 33	struct osnoise_top_cpu	*cpu_data;
 34	int			nr_cpus;
 35};
 36
 37/*
 38 * osnoise_free_top - free runtime data
 39 */
 40static void osnoise_free_top(struct osnoise_top_data *data)
 41{
 42	free(data->cpu_data);
 43	free(data);
 44}
 45
 46static void osnoise_free_top_tool(struct osnoise_tool *tool)
 47{
 48	osnoise_free_top(tool->data);
 49}
 50
 51/*
 52 * osnoise_alloc_histogram - alloc runtime data
 53 */
 54static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus)
 55{
 56	struct osnoise_top_data *data;
 57
 58	data = calloc(1, sizeof(*data));
 59	if (!data)
 60		return NULL;
 61
 62	data->nr_cpus = nr_cpus;
 63
 64	/* one set of histograms per CPU */
 65	data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
 66	if (!data->cpu_data)
 67		goto cleanup;
 68
 69	return data;
 70
 71cleanup:
 72	osnoise_free_top(data);
 73	return NULL;
 74}
 75
 76/*
 77 * osnoise_top_handler - this is the handler for osnoise tracer events
 78 */
 79static int
 80osnoise_top_handler(struct trace_seq *s, struct tep_record *record,
 81		    struct tep_event *event, void *context)
 82{
 83	struct trace_instance *trace = context;
 84	struct osnoise_tool *tool;
 85	unsigned long long val;
 86	struct osnoise_top_cpu *cpu_data;
 87	struct osnoise_top_data *data;
 88	int cpu = record->cpu;
 89
 90	tool = container_of(trace, struct osnoise_tool, trace);
 91
 92	data = tool->data;
 93	cpu_data = &data->cpu_data[cpu];
 94
 95	cpu_data->sum_cycles++;
 96
 97	tep_get_field_val(s, event, "runtime", record, &val, 1);
 98	update_sum(&cpu_data->sum_runtime, &val);
 99
100	tep_get_field_val(s, event, "noise", record, &val, 1);
101	update_max(&cpu_data->max_noise, &val);
102	update_sum(&cpu_data->sum_noise, &val);
103
104	tep_get_field_val(s, event, "max_sample", record, &val, 1);
105	update_max(&cpu_data->max_sample, &val);
106
107	tep_get_field_val(s, event, "hw_count", record, &val, 1);
108	update_sum(&cpu_data->hw_count, &val);
109
110	tep_get_field_val(s, event, "nmi_count", record, &val, 1);
111	update_sum(&cpu_data->nmi_count, &val);
112
113	tep_get_field_val(s, event, "irq_count", record, &val, 1);
114	update_sum(&cpu_data->irq_count, &val);
115
116	tep_get_field_val(s, event, "softirq_count", record, &val, 1);
117	update_sum(&cpu_data->softirq_count, &val);
118
119	tep_get_field_val(s, event, "thread_count", record, &val, 1);
120	update_sum(&cpu_data->thread_count, &val);
121
122	return 0;
123}
124
125/*
126 * osnoise_top_header - print the header of the tool output
127 */
128static void osnoise_top_header(struct osnoise_tool *top)
129{
130	struct osnoise_params *params = to_osnoise_params(top->params);
131	struct trace_seq *s = top->trace.seq;
132	bool pretty = params->common.pretty_output;
133	char duration[26];
134
135	get_duration(top->start_time, duration, sizeof(duration));
136
137	if (pretty)
138		trace_seq_printf(s, "\033[2;37;40m");
139
140	trace_seq_printf(s, "                                          ");
141
142	if (params->mode == MODE_OSNOISE) {
143		trace_seq_printf(s, "Operating System Noise");
144		trace_seq_printf(s, "                                       ");
145	} else if (params->mode == MODE_HWNOISE) {
146		trace_seq_printf(s, "Hardware-related Noise");
147	}
148
149	trace_seq_printf(s, "                                   ");
150
151	if (pretty)
152		trace_seq_printf(s, "\033[0;0;0m");
153	trace_seq_printf(s, "\n");
154
155	trace_seq_printf(s, "duration: %9s | time is in us\n", duration);
156
157	if (pretty)
158		trace_seq_printf(s, "\033[2;30;47m");
159
160	trace_seq_printf(s, "CPU Period       Runtime ");
161	trace_seq_printf(s, "       Noise ");
162	trace_seq_printf(s, " %% CPU Aval ");
163	trace_seq_printf(s, "  Max Noise   Max Single ");
164	trace_seq_printf(s, "         HW          NMI");
165
166	if (params->mode == MODE_HWNOISE)
167		goto eol;
168
169	trace_seq_printf(s, "          IRQ      Softirq       Thread");
170
171eol:
172	if (pretty)
173		trace_seq_printf(s, "\033[0;0;0m");
174	trace_seq_printf(s, "\n");
175}
176
177/*
178 * clear_terminal - clears the output terminal
179 */
180static void clear_terminal(struct trace_seq *seq)
181{
182	if (!config_debug)
183		trace_seq_printf(seq, "\033c");
184}
185
186/*
187 * osnoise_top_print - prints the output of a given CPU
188 */
189static void osnoise_top_print(struct osnoise_tool *tool, int cpu)
190{
191	struct osnoise_params *params = to_osnoise_params(tool->params);
192	struct trace_seq *s = tool->trace.seq;
193	struct osnoise_top_cpu *cpu_data;
194	struct osnoise_top_data *data;
195	int percentage;
196	int decimal;
197
198	data = tool->data;
199	cpu_data = &data->cpu_data[cpu];
200
201	if (!cpu_data->sum_runtime)
202		return;
203
204	percentage = ((cpu_data->sum_runtime - cpu_data->sum_noise) * 10000000)
205			/ cpu_data->sum_runtime;
206	decimal = percentage % 100000;
207	percentage = percentage / 100000;
208
209	trace_seq_printf(s, "%3d #%-6d %12llu ", cpu, cpu_data->sum_cycles, cpu_data->sum_runtime);
210	trace_seq_printf(s, "%12llu ", cpu_data->sum_noise);
211	trace_seq_printf(s, "  %3d.%05d", percentage, decimal);
212	trace_seq_printf(s, "%12llu %12llu", cpu_data->max_noise, cpu_data->max_sample);
213
214	trace_seq_printf(s, "%12llu ", cpu_data->hw_count);
215	trace_seq_printf(s, "%12llu ", cpu_data->nmi_count);
216
217	if (params->mode == MODE_HWNOISE) {
218		trace_seq_printf(s, "\n");
219		return;
220	}
221
222	trace_seq_printf(s, "%12llu ", cpu_data->irq_count);
223	trace_seq_printf(s, "%12llu ", cpu_data->softirq_count);
224	trace_seq_printf(s, "%12llu\n", cpu_data->thread_count);
225}
226
227/*
228 * osnoise_print_stats - print data for all cpus
229 */
230static void
231osnoise_print_stats(struct osnoise_tool *top)
232{
233	struct osnoise_params *params = to_osnoise_params(top->params);
234	struct trace_instance *trace = &top->trace;
235	static int nr_cpus = -1;
236	int i;
237
238	if (nr_cpus == -1)
239		nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
240
241	if (!params->common.quiet)
242		clear_terminal(trace->seq);
243
244	osnoise_top_header(top);
245
246	for_each_monitored_cpu(i, nr_cpus, &params->common) {
247		osnoise_top_print(top, i);
248	}
249
250	trace_seq_do_printf(trace->seq);
251	trace_seq_reset(trace->seq);
252	osnoise_report_missed_events(top);
253}
254
255/*
256 * osnoise_top_usage - prints osnoise top usage message
257 */
258static void osnoise_top_usage(struct osnoise_params *params)
259{
260	int i;
261
262	static const char * const msg[] = {
263		" [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
264		"	  [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
265		"	  [-c cpu-list] [-H cpu-list] [-P priority] [-C [cgroup_name]] [--warm-up s]",
266		"",
267		"	  -h/--help: print this menu",
268		"	  -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
269		"	  -p/--period us: osnoise period in us",
270		"	  -r/--runtime us: osnoise runtime in us",
271		"	  -s/--stop us: stop trace if a single sample is higher than the argument in us",
272		"	  -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
273		"	  -T/--threshold us: the minimum delta to be considered a noise",
274		"	  -c/--cpus cpu-list: list of cpus to run osnoise threads",
275		"	  -H/--house-keeping cpus: run rtla control threads only on the given cpus",
276		"	  -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
277		"	  -d/--duration time[s|m|h|d]: duration of the session",
278		"	  -D/--debug: print debug info",
279		"	  -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
280		"	  -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
281		"	     --filter <filter>: enable a trace event filter to the previous -e event",
282		"	     --trigger <trigger>: enable a trace event trigger to the previous -e event",
283		"	  -q/--quiet print only a summary at the end",
284		"	  -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
285		"		o:prio - use SCHED_OTHER with prio",
286		"		r:prio - use SCHED_RR with prio",
287		"		f:prio - use SCHED_FIFO with prio",
288		"		d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
289		"						       in nanoseconds",
290		"	     --warm-up s: let the workload run for s seconds before collecting data",
291		"	     --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
292		"	     --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
293		"	     --on-end: define action to be executed at measurement end, multiple are allowed",
294		NULL,
295	};
296
297	if (params->mode == MODE_OSNOISE) {
298		fprintf(stderr,
299			"rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n",
300			VERSION);
301
302		fprintf(stderr, "  usage: rtla osnoise [top]");
303	}
304
305	if (params->mode == MODE_HWNOISE) {
306		fprintf(stderr,
307			"rtla hwnoise: a summary of hardware-related noise (version %s)\n",
308			VERSION);
309
310		fprintf(stderr, "  usage: rtla hwnoise");
311	}
312
313	for (i = 0; msg[i]; i++)
314		fprintf(stderr, "%s\n", msg[i]);
315
316	exit(EXIT_SUCCESS);
317}
318
319/*
320 * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
321 */
322struct common_params *osnoise_top_parse_args(int argc, char **argv)
323{
324	struct osnoise_params *params;
325	struct trace_events *tevent;
326	int retval;
327	int c;
328	char *trace_output = NULL;
329
330	params = calloc(1, sizeof(*params));
331	if (!params)
332		exit(1);
333
334	actions_init(&params->common.threshold_actions);
335	actions_init(&params->common.end_actions);
336
337	if (strcmp(argv[0], "hwnoise") == 0) {
338		params->mode = MODE_HWNOISE;
339		/*
340		 * Reduce CPU usage for 75% to avoid killing the system.
341		 */
342		params->runtime = 750000;
343		params->period = 1000000;
344	}
345
346	while (1) {
347		static struct option long_options[] = {
348			{"auto",		required_argument,	0, 'a'},
349			{"cpus",		required_argument,	0, 'c'},
350			{"cgroup",		optional_argument,	0, 'C'},
351			{"debug",		no_argument,		0, 'D'},
352			{"duration",		required_argument,	0, 'd'},
353			{"event",		required_argument,	0, 'e'},
354			{"house-keeping",	required_argument,	0, 'H'},
355			{"help",		no_argument,		0, 'h'},
356			{"period",		required_argument,	0, 'p'},
357			{"priority",		required_argument,	0, 'P'},
358			{"quiet",		no_argument,		0, 'q'},
359			{"runtime",		required_argument,	0, 'r'},
360			{"stop",		required_argument,	0, 's'},
361			{"stop-total",		required_argument,	0, 'S'},
362			{"threshold",		required_argument,	0, 'T'},
363			{"trace",		optional_argument,	0, 't'},
364			{"trigger",		required_argument,	0, '0'},
365			{"filter",		required_argument,	0, '1'},
366			{"warm-up",		required_argument,	0, '2'},
367			{"trace-buffer-size",	required_argument,	0, '3'},
368			{"on-threshold",	required_argument,	0, '4'},
369			{"on-end",		required_argument,	0, '5'},
370			{0, 0, 0, 0}
371		};
372
373		c = getopt_long(argc, argv, "a:c:C::d:De:hH:p:P:qr:s:S:t::T:0:1:2:3:",
374				 long_options, NULL);
375
376		/* Detect the end of the options. */
377		if (c == -1)
378			break;
379
380		switch (c) {
381		case 'a':
382			/* set sample stop to auto_thresh */
383			params->common.stop_us = get_llong_from_str(optarg);
384
385			/* set sample threshold to 1 */
386			params->threshold = 1;
387
388			/* set trace */
389			if (!trace_output)
390				trace_output = "osnoise_trace.txt";
391
392			break;
393		case 'c':
394			retval = parse_cpu_set(optarg, &params->common.monitored_cpus);
395			if (retval)
396				fatal("Invalid -c cpu list");
397			params->common.cpus = optarg;
398			break;
399		case 'C':
400			params->common.cgroup = 1;
401			params->common.cgroup_name = parse_optional_arg(argc, argv);
402			break;
403		case 'D':
404			config_debug = 1;
405			break;
406		case 'd':
407			params->common.duration = parse_seconds_duration(optarg);
408			if (!params->common.duration)
409				fatal("Invalid -d duration");
410			break;
411		case 'e':
412			tevent = trace_event_alloc(optarg);
413			if (!tevent)
414				fatal("Error alloc trace event");
415
416			if (params->common.events)
417				tevent->next = params->common.events;
418			params->common.events = tevent;
419
420			break;
421		case 'h':
422		case '?':
423			osnoise_top_usage(params);
424			break;
425		case 'H':
426			params->common.hk_cpus = 1;
427			retval = parse_cpu_set(optarg, &params->common.hk_cpu_set);
428			if (retval)
429				fatal("Error parsing house keeping CPUs");
430			break;
431		case 'p':
432			params->period = get_llong_from_str(optarg);
433			if (params->period > 10000000)
434				fatal("Period longer than 10 s");
435			break;
436		case 'P':
437			retval = parse_prio(optarg, &params->common.sched_param);
438			if (retval == -1)
439				fatal("Invalid -P priority");
440			params->common.set_sched = 1;
441			break;
442		case 'q':
443			params->common.quiet = 1;
444			break;
445		case 'r':
446			params->runtime = get_llong_from_str(optarg);
447			if (params->runtime < 100)
448				fatal("Runtime shorter than 100 us");
449			break;
450		case 's':
451			params->common.stop_us = get_llong_from_str(optarg);
452			break;
453		case 'S':
454			params->common.stop_total_us = get_llong_from_str(optarg);
455			break;
456		case 't':
457			trace_output = parse_optional_arg(argc, argv);
458			if (!trace_output)
459				trace_output = "osnoise_trace.txt";
460			break;
461		case 'T':
462			params->threshold = get_llong_from_str(optarg);
463			break;
464		case '0': /* trigger */
465			if (params->common.events) {
466				retval = trace_event_add_trigger(params->common.events, optarg);
467				if (retval)
468					fatal("Error adding trigger %s", optarg);
469			} else {
470				fatal("--trigger requires a previous -e");
471			}
472			break;
473		case '1': /* filter */
474			if (params->common.events) {
475				retval = trace_event_add_filter(params->common.events, optarg);
476				if (retval)
477					fatal("Error adding filter %s", optarg);
478			} else {
479				fatal("--filter requires a previous -e");
480			}
481			break;
482		case '2':
483			params->common.warmup = get_llong_from_str(optarg);
484			break;
485		case '3':
486			params->common.buffer_size = get_llong_from_str(optarg);
487			break;
488		case '4':
489			retval = actions_parse(&params->common.threshold_actions, optarg,
490					       "osnoise_trace.txt");
491			if (retval)
492				fatal("Invalid action %s", optarg);
493			break;
494		case '5':
495			retval = actions_parse(&params->common.end_actions, optarg,
496					       "osnoise_trace.txt");
497			if (retval)
498				fatal("Invalid action %s", optarg);
499			break;
500		default:
501			fatal("Invalid option");
502		}
503	}
504
505	if (trace_output)
506		actions_add_trace_output(&params->common.threshold_actions, trace_output);
507
508	if (geteuid())
509		fatal("osnoise needs root permission");
510
511	return &params->common;
512}
513
514/*
515 * osnoise_top_apply_config - apply the top configs to the initialized tool
516 */
517static int
518osnoise_top_apply_config(struct osnoise_tool *tool)
519{
520	struct osnoise_params *params = to_osnoise_params(tool->params);
521	int retval;
522
523	retval = osnoise_apply_config(tool, params);
524	if (retval)
525		goto out_err;
526
527	if (params->mode == MODE_HWNOISE) {
528		retval = osnoise_set_irq_disable(tool->context, 1);
529		if (retval) {
530			err_msg("Failed to set OSNOISE_IRQ_DISABLE option\n");
531			goto out_err;
532		}
533	}
534
535	if (isatty(STDOUT_FILENO) && !params->common.quiet)
536		params->common.pretty_output = 1;
537
538	return 0;
539
540out_err:
541	return -1;
542}
543
544/*
545 * osnoise_init_top - initialize a osnoise top tool with parameters
546 */
547struct osnoise_tool *osnoise_init_top(struct common_params *params)
548{
549	struct osnoise_tool *tool;
550	int nr_cpus;
551
552	nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
553
554	tool = osnoise_init_tool("osnoise_top");
555	if (!tool)
556		return NULL;
557
558	tool->data = osnoise_alloc_top(nr_cpus);
559	if (!tool->data) {
560		osnoise_destroy_tool(tool);
561		return NULL;
562	}
563
564	tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise",
565				   osnoise_top_handler, NULL);
566
567	return tool;
568}
569
570struct tool_ops osnoise_top_ops = {
571	.tracer = "osnoise",
572	.comm_prefix = "osnoise/",
573	.parse_args = osnoise_top_parse_args,
574	.init_tool = osnoise_init_top,
575	.apply_config = osnoise_top_apply_config,
576	.enable = osnoise_enable,
577	.main = top_main_loop,
578	.print_stats = osnoise_print_stats,
579	.free = osnoise_free_top_tool,
580};
~/ Read-only file preview.