xref: /openbmc/linux/tools/tracing/rtla/src/osnoise_top.c (revision 9235756885e865070c4be2facda75262dbd85967)
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  #include <getopt.h>
7  #include <stdlib.h>
8  #include <string.h>
9  #include <signal.h>
10  #include <unistd.h>
11  #include <stdio.h>
12  #include <time.h>
13  
14  #include "osnoise.h"
15  #include "utils.h"
16  
17  enum osnoise_mode {
18  	MODE_OSNOISE = 0,
19  	MODE_HWNOISE
20  };
21  
22  /*
23   * osnoise top parameters
24   */
25  struct osnoise_top_params {
26  	char			*cpus;
27  	char			*monitored_cpus;
28  	char			*trace_output;
29  	unsigned long long	runtime;
30  	unsigned long long	period;
31  	long long		threshold;
32  	long long		stop_us;
33  	long long		stop_total_us;
34  	int			sleep_time;
35  	int			duration;
36  	int			quiet;
37  	int			set_sched;
38  	struct sched_attr	sched_param;
39  	struct trace_events	*events;
40  	enum osnoise_mode	mode;
41  };
42  
43  struct osnoise_top_cpu {
44  	unsigned long long	sum_runtime;
45  	unsigned long long	sum_noise;
46  	unsigned long long	max_noise;
47  	unsigned long long	max_sample;
48  
49  	unsigned long long	hw_count;
50  	unsigned long long	nmi_count;
51  	unsigned long long	irq_count;
52  	unsigned long long	softirq_count;
53  	unsigned long long	thread_count;
54  
55  	int			sum_cycles;
56  };
57  
58  struct osnoise_top_data {
59  	struct osnoise_top_cpu	*cpu_data;
60  	int			nr_cpus;
61  };
62  
63  /*
64   * osnoise_free_top - free runtime data
65   */
66  static void
67  osnoise_free_top(struct osnoise_top_data *data)
68  {
69  	free(data->cpu_data);
70  	free(data);
71  }
72  
73  /*
74   * osnoise_alloc_histogram - alloc runtime data
75   */
76  static struct osnoise_top_data *osnoise_alloc_top(int nr_cpus)
77  {
78  	struct osnoise_top_data *data;
79  
80  	data = calloc(1, sizeof(*data));
81  	if (!data)
82  		return NULL;
83  
84  	data->nr_cpus = nr_cpus;
85  
86  	/* one set of histograms per CPU */
87  	data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus);
88  	if (!data->cpu_data)
89  		goto cleanup;
90  
91  	return data;
92  
93  cleanup:
94  	osnoise_free_top(data);
95  	return NULL;
96  }
97  
98  /*
99   * osnoise_top_handler - this is the handler for osnoise tracer events
100   */
101  static int
102  osnoise_top_handler(struct trace_seq *s, struct tep_record *record,
103  		    struct tep_event *event, void *context)
104  {
105  	struct trace_instance *trace = context;
106  	struct osnoise_tool *tool;
107  	unsigned long long val;
108  	struct osnoise_top_cpu *cpu_data;
109  	struct osnoise_top_data *data;
110  	int cpu = record->cpu;
111  
112  	tool = container_of(trace, struct osnoise_tool, trace);
113  
114  	data = tool->data;
115  	cpu_data = &data->cpu_data[cpu];
116  
117  	cpu_data->sum_cycles++;
118  
119  	tep_get_field_val(s, event, "runtime", record, &val, 1);
120  	update_sum(&cpu_data->sum_runtime, &val);
121  
122  	tep_get_field_val(s, event, "noise", record, &val, 1);
123  	update_max(&cpu_data->max_noise, &val);
124  	update_sum(&cpu_data->sum_noise, &val);
125  
126  	tep_get_field_val(s, event, "max_sample", record, &val, 1);
127  	update_max(&cpu_data->max_sample, &val);
128  
129  	tep_get_field_val(s, event, "hw_count", record, &val, 1);
130  	update_sum(&cpu_data->hw_count, &val);
131  
132  	tep_get_field_val(s, event, "nmi_count", record, &val, 1);
133  	update_sum(&cpu_data->nmi_count, &val);
134  
135  	tep_get_field_val(s, event, "irq_count", record, &val, 1);
136  	update_sum(&cpu_data->irq_count, &val);
137  
138  	tep_get_field_val(s, event, "softirq_count", record, &val, 1);
139  	update_sum(&cpu_data->softirq_count, &val);
140  
141  	tep_get_field_val(s, event, "thread_count", record, &val, 1);
142  	update_sum(&cpu_data->thread_count, &val);
143  
144  	return 0;
145  }
146  
147  /*
148   * osnoise_top_header - print the header of the tool output
149   */
150  static void osnoise_top_header(struct osnoise_tool *top)
151  {
152  	struct osnoise_top_params *params = top->params;
153  	struct trace_seq *s = top->trace.seq;
154  	char duration[26];
155  
156  	get_duration(top->start_time, duration, sizeof(duration));
157  
158  	trace_seq_printf(s, "\033[2;37;40m");
159  	trace_seq_printf(s, "                                          ");
160  
161  	if (params->mode == MODE_OSNOISE) {
162  		trace_seq_printf(s, "Operating System Noise");
163  		trace_seq_printf(s, "                                       ");
164  	} else if (params->mode == MODE_HWNOISE) {
165  		trace_seq_printf(s, "Hardware-related Noise");
166  	}
167  
168  	trace_seq_printf(s, "                                   ");
169  	trace_seq_printf(s, "\033[0;0;0m");
170  	trace_seq_printf(s, "\n");
171  
172  	trace_seq_printf(s, "duration: %9s | time is in us\n", duration);
173  
174  	trace_seq_printf(s, "\033[2;30;47m");
175  	trace_seq_printf(s, "CPU Period       Runtime ");
176  	trace_seq_printf(s, "       Noise ");
177  	trace_seq_printf(s, " %% CPU Aval ");
178  	trace_seq_printf(s, "  Max Noise   Max Single ");
179  	trace_seq_printf(s, "         HW          NMI");
180  
181  	if (params->mode == MODE_HWNOISE)
182  		goto eol;
183  
184  	trace_seq_printf(s, "          IRQ      Softirq       Thread");
185  
186  eol:
187  	trace_seq_printf(s, "\033[0;0;0m");
188  	trace_seq_printf(s, "\n");
189  }
190  
191  /*
192   * clear_terminal - clears the output terminal
193   */
194  static void clear_terminal(struct trace_seq *seq)
195  {
196  	if (!config_debug)
197  		trace_seq_printf(seq, "\033c");
198  }
199  
200  /*
201   * osnoise_top_print - prints the output of a given CPU
202   */
203  static void osnoise_top_print(struct osnoise_tool *tool, int cpu)
204  {
205  	struct osnoise_top_params *params = tool->params;
206  	struct trace_seq *s = tool->trace.seq;
207  	struct osnoise_top_cpu *cpu_data;
208  	struct osnoise_top_data *data;
209  	int percentage;
210  	int decimal;
211  
212  	data = tool->data;
213  	cpu_data = &data->cpu_data[cpu];
214  
215  	if (!cpu_data->sum_runtime)
216  		return;
217  
218  	percentage = ((cpu_data->sum_runtime - cpu_data->sum_noise) * 10000000)
219  			/ cpu_data->sum_runtime;
220  	decimal = percentage % 100000;
221  	percentage = percentage / 100000;
222  
223  	trace_seq_printf(s, "%3d #%-6d %12llu ", cpu, cpu_data->sum_cycles, cpu_data->sum_runtime);
224  	trace_seq_printf(s, "%12llu ", cpu_data->sum_noise);
225  	trace_seq_printf(s, "  %3d.%05d", percentage, decimal);
226  	trace_seq_printf(s, "%12llu %12llu", cpu_data->max_noise, cpu_data->max_sample);
227  
228  	trace_seq_printf(s, "%12llu ", cpu_data->hw_count);
229  	trace_seq_printf(s, "%12llu ", cpu_data->nmi_count);
230  
231  	if (params->mode == MODE_HWNOISE) {
232  		trace_seq_printf(s, "\n");
233  		return;
234  	}
235  
236  	trace_seq_printf(s, "%12llu ", cpu_data->irq_count);
237  	trace_seq_printf(s, "%12llu ", cpu_data->softirq_count);
238  	trace_seq_printf(s, "%12llu\n", cpu_data->thread_count);
239  }
240  
241  /*
242   * osnoise_print_stats - print data for all cpus
243   */
244  static void
245  osnoise_print_stats(struct osnoise_top_params *params, struct osnoise_tool *top)
246  {
247  	struct trace_instance *trace = &top->trace;
248  	static int nr_cpus = -1;
249  	int i;
250  
251  	if (nr_cpus == -1)
252  		nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
253  
254  	if (!params->quiet)
255  		clear_terminal(trace->seq);
256  
257  	osnoise_top_header(top);
258  
259  	for (i = 0; i < nr_cpus; i++) {
260  		if (params->cpus && !params->monitored_cpus[i])
261  			continue;
262  		osnoise_top_print(top, i);
263  	}
264  
265  	trace_seq_do_printf(trace->seq);
266  	trace_seq_reset(trace->seq);
267  }
268  
269  /*
270   * osnoise_top_usage - prints osnoise top usage message
271   */
272  static void osnoise_top_usage(struct osnoise_top_params *params, char *usage)
273  {
274  	int i;
275  
276  	static const char * const msg[] = {
277  		" [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
278  		"	  [-T us] [-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
279  		"	  [-c cpu-list] [-P priority]",
280  		"",
281  		"	  -h/--help: print this menu",
282  		"	  -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
283  		"	  -p/--period us: osnoise period in us",
284  		"	  -r/--runtime us: osnoise runtime in us",
285  		"	  -s/--stop us: stop trace if a single sample is higher than the argument in us",
286  		"	  -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
287  		"	  -T/--threshold us: the minimum delta to be considered a noise",
288  		"	  -c/--cpus cpu-list: list of cpus to run osnoise threads",
289  		"	  -d/--duration time[s|m|h|d]: duration of the session",
290  		"	  -D/--debug: print debug info",
291  		"	  -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]",
292  		"	  -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
293  		"	     --filter <filter>: enable a trace event filter to the previous -e event",
294  		"	     --trigger <trigger>: enable a trace event trigger to the previous -e event",
295  		"	  -q/--quiet print only a summary at the end",
296  		"	  -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
297  		"		o:prio - use SCHED_OTHER with prio",
298  		"		r:prio - use SCHED_RR with prio",
299  		"		f:prio - use SCHED_FIFO with prio",
300  		"		d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
301  		"						       in nanoseconds",
302  		NULL,
303  	};
304  
305  	if (usage)
306  		fprintf(stderr, "%s\n", usage);
307  
308  	if (params->mode == MODE_OSNOISE) {
309  		fprintf(stderr,
310  			"rtla osnoise top: a per-cpu summary of the OS noise (version %s)\n",
311  			VERSION);
312  
313  		fprintf(stderr, "  usage: rtla osnoise [top]");
314  	}
315  
316  	if (params->mode == MODE_HWNOISE) {
317  		fprintf(stderr,
318  			"rtla hwnoise: a summary of hardware-related noise (version %s)\n",
319  			VERSION);
320  
321  		fprintf(stderr, "  usage: rtla hwnoise");
322  	}
323  
324  	for (i = 0; msg[i]; i++)
325  		fprintf(stderr, "%s\n", msg[i]);
326  	exit(1);
327  }
328  
329  /*
330   * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
331   */
332  struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv)
333  {
334  	struct osnoise_top_params *params;
335  	struct trace_events *tevent;
336  	int retval;
337  	int c;
338  
339  	params = calloc(1, sizeof(*params));
340  	if (!params)
341  		exit(1);
342  
343  	if (strcmp(argv[0], "hwnoise") == 0)
344  		params->mode = MODE_HWNOISE;
345  
346  	while (1) {
347  		static struct option long_options[] = {
348  			{"auto",		required_argument,	0, 'a'},
349  			{"cpus",		required_argument,	0, 'c'},
350  			{"debug",		no_argument,		0, 'D'},
351  			{"duration",		required_argument,	0, 'd'},
352  			{"event",		required_argument,	0, 'e'},
353  			{"help",		no_argument,		0, 'h'},
354  			{"period",		required_argument,	0, 'p'},
355  			{"priority",		required_argument,	0, 'P'},
356  			{"quiet",		no_argument,		0, 'q'},
357  			{"runtime",		required_argument,	0, 'r'},
358  			{"stop",		required_argument,	0, 's'},
359  			{"stop-total",		required_argument,	0, 'S'},
360  			{"threshold",		required_argument,	0, 'T'},
361  			{"trace",		optional_argument,	0, 't'},
362  			{"trigger",		required_argument,	0, '0'},
363  			{"filter",		required_argument,	0, '1'},
364  			{0, 0, 0, 0}
365  		};
366  
367  		/* getopt_long stores the option index here. */
368  		int option_index = 0;
369  
370  		c = getopt_long(argc, argv, "a:c:d:De:hp:P:qr:s:S:t::T:0:1:",
371  				 long_options, &option_index);
372  
373  		/* Detect the end of the options. */
374  		if (c == -1)
375  			break;
376  
377  		switch (c) {
378  		case 'a':
379  			/* set sample stop to auto_thresh */
380  			params->stop_us = get_llong_from_str(optarg);
381  
382  			/* set sample threshold to 1 */
383  			params->threshold = 1;
384  
385  			/* set trace */
386  			params->trace_output = "osnoise_trace.txt";
387  
388  			break;
389  		case 'c':
390  			retval = parse_cpu_list(optarg, &params->monitored_cpus);
391  			if (retval)
392  				osnoise_top_usage(params, "\nInvalid -c cpu list\n");
393  			params->cpus = optarg;
394  			break;
395  		case 'D':
396  			config_debug = 1;
397  			break;
398  		case 'd':
399  			params->duration = parse_seconds_duration(optarg);
400  			if (!params->duration)
401  				osnoise_top_usage(params, "Invalid -D duration\n");
402  			break;
403  		case 'e':
404  			tevent = trace_event_alloc(optarg);
405  			if (!tevent) {
406  				err_msg("Error alloc trace event");
407  				exit(EXIT_FAILURE);
408  			}
409  
410  			if (params->events)
411  				tevent->next = params->events;
412  			params->events = tevent;
413  
414  			break;
415  		case 'h':
416  		case '?':
417  			osnoise_top_usage(params, NULL);
418  			break;
419  		case 'p':
420  			params->period = get_llong_from_str(optarg);
421  			if (params->period > 10000000)
422  				osnoise_top_usage(params, "Period longer than 10 s\n");
423  			break;
424  		case 'P':
425  			retval = parse_prio(optarg, &params->sched_param);
426  			if (retval == -1)
427  				osnoise_top_usage(params, "Invalid -P priority");
428  			params->set_sched = 1;
429  			break;
430  		case 'q':
431  			params->quiet = 1;
432  			break;
433  		case 'r':
434  			params->runtime = get_llong_from_str(optarg);
435  			if (params->runtime < 100)
436  				osnoise_top_usage(params, "Runtime shorter than 100 us\n");
437  			break;
438  		case 's':
439  			params->stop_us = get_llong_from_str(optarg);
440  			break;
441  		case 'S':
442  			params->stop_total_us = get_llong_from_str(optarg);
443  			break;
444  		case 't':
445  			if (optarg)
446  				/* skip = */
447  				params->trace_output = &optarg[1];
448  			else
449  				params->trace_output = "osnoise_trace.txt";
450  			break;
451  		case 'T':
452  			params->threshold = get_llong_from_str(optarg);
453  			break;
454  		case '0': /* trigger */
455  			if (params->events) {
456  				retval = trace_event_add_trigger(params->events, optarg);
457  				if (retval) {
458  					err_msg("Error adding trigger %s\n", optarg);
459  					exit(EXIT_FAILURE);
460  				}
461  			} else {
462  				osnoise_top_usage(params, "--trigger requires a previous -e\n");
463  			}
464  			break;
465  		case '1': /* filter */
466  			if (params->events) {
467  				retval = trace_event_add_filter(params->events, optarg);
468  				if (retval) {
469  					err_msg("Error adding filter %s\n", optarg);
470  					exit(EXIT_FAILURE);
471  				}
472  			} else {
473  				osnoise_top_usage(params, "--filter requires a previous -e\n");
474  			}
475  			break;
476  		default:
477  			osnoise_top_usage(params, "Invalid option");
478  		}
479  	}
480  
481  	if (geteuid()) {
482  		err_msg("osnoise needs root permission\n");
483  		exit(EXIT_FAILURE);
484  	}
485  
486  	return params;
487  }
488  
489  /*
490   * osnoise_top_apply_config - apply the top configs to the initialized tool
491   */
492  static int
493  osnoise_top_apply_config(struct osnoise_tool *tool, struct osnoise_top_params *params)
494  {
495  	int retval;
496  
497  	if (!params->sleep_time)
498  		params->sleep_time = 1;
499  
500  	if (params->cpus) {
501  		retval = osnoise_set_cpus(tool->context, params->cpus);
502  		if (retval) {
503  			err_msg("Failed to apply CPUs config\n");
504  			goto out_err;
505  		}
506  	}
507  
508  	if (params->runtime || params->period) {
509  		retval = osnoise_set_runtime_period(tool->context,
510  						    params->runtime,
511  						    params->period);
512  		if (retval) {
513  			err_msg("Failed to set runtime and/or period\n");
514  			goto out_err;
515  		}
516  	}
517  
518  	if (params->stop_us) {
519  		retval = osnoise_set_stop_us(tool->context, params->stop_us);
520  		if (retval) {
521  			err_msg("Failed to set stop us\n");
522  			goto out_err;
523  		}
524  	}
525  
526  	if (params->stop_total_us) {
527  		retval = osnoise_set_stop_total_us(tool->context, params->stop_total_us);
528  		if (retval) {
529  			err_msg("Failed to set stop total us\n");
530  			goto out_err;
531  		}
532  	}
533  
534  	if (params->threshold) {
535  		retval = osnoise_set_tracing_thresh(tool->context, params->threshold);
536  		if (retval) {
537  			err_msg("Failed to set tracing_thresh\n");
538  			goto out_err;
539  		}
540  	}
541  
542  	if (params->mode == MODE_HWNOISE) {
543  		retval = osnoise_set_irq_disable(tool->context, 1);
544  		if (retval) {
545  			err_msg("Failed to set OSNOISE_IRQ_DISABLE option\n");
546  			goto out_err;
547  		}
548  	}
549  
550  	return 0;
551  
552  out_err:
553  	return -1;
554  }
555  
556  /*
557   * osnoise_init_top - initialize a osnoise top tool with parameters
558   */
559  struct osnoise_tool *osnoise_init_top(struct osnoise_top_params *params)
560  {
561  	struct osnoise_tool *tool;
562  	int nr_cpus;
563  
564  	nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
565  
566  	tool = osnoise_init_tool("osnoise_top");
567  	if (!tool)
568  		return NULL;
569  
570  	tool->data = osnoise_alloc_top(nr_cpus);
571  	if (!tool->data)
572  		goto out_err;
573  
574  	tool->params = params;
575  
576  	tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise",
577  				   osnoise_top_handler, NULL);
578  
579  	return tool;
580  
581  out_err:
582  	osnoise_free_top(tool->data);
583  	osnoise_destroy_tool(tool);
584  	return NULL;
585  }
586  
587  static int stop_tracing;
588  static void stop_top(int sig)
589  {
590  	stop_tracing = 1;
591  }
592  
593  /*
594   * osnoise_top_set_signals - handles the signal to stop the tool
595   */
596  static void osnoise_top_set_signals(struct osnoise_top_params *params)
597  {
598  	signal(SIGINT, stop_top);
599  	if (params->duration) {
600  		signal(SIGALRM, stop_top);
601  		alarm(params->duration);
602  	}
603  }
604  
605  int osnoise_top_main(int argc, char **argv)
606  {
607  	struct osnoise_top_params *params;
608  	struct osnoise_tool *record = NULL;
609  	struct osnoise_tool *tool = NULL;
610  	struct trace_instance *trace;
611  	int return_value = 1;
612  	int retval;
613  
614  	params = osnoise_top_parse_args(argc, argv);
615  	if (!params)
616  		exit(1);
617  
618  	tool = osnoise_init_top(params);
619  	if (!tool) {
620  		err_msg("Could not init osnoise top\n");
621  		goto out_exit;
622  	}
623  
624  	retval = osnoise_top_apply_config(tool, params);
625  	if (retval) {
626  		err_msg("Could not apply config\n");
627  		goto out_free;
628  	}
629  
630  	trace = &tool->trace;
631  
632  	retval = enable_osnoise(trace);
633  	if (retval) {
634  		err_msg("Failed to enable osnoise tracer\n");
635  		goto out_free;
636  	}
637  
638  	if (params->set_sched) {
639  		retval = set_comm_sched_attr("osnoise/", &params->sched_param);
640  		if (retval) {
641  			err_msg("Failed to set sched parameters\n");
642  			goto out_free;
643  		}
644  	}
645  
646  	trace_instance_start(trace);
647  
648  	if (params->trace_output) {
649  		record = osnoise_init_trace_tool("osnoise");
650  		if (!record) {
651  			err_msg("Failed to enable the trace instance\n");
652  			goto out_free;
653  		}
654  
655  		if (params->events) {
656  			retval = trace_events_enable(&record->trace, params->events);
657  			if (retval)
658  				goto out_top;
659  		}
660  
661  		trace_instance_start(&record->trace);
662  	}
663  
664  	tool->start_time = time(NULL);
665  	osnoise_top_set_signals(params);
666  
667  	while (!stop_tracing) {
668  		sleep(params->sleep_time);
669  
670  		retval = tracefs_iterate_raw_events(trace->tep,
671  						    trace->inst,
672  						    NULL,
673  						    0,
674  						    collect_registered_events,
675  						    trace);
676  		if (retval < 0) {
677  			err_msg("Error iterating on events\n");
678  			goto out_top;
679  		}
680  
681  		if (!params->quiet)
682  			osnoise_print_stats(params, tool);
683  
684  		if (trace_is_off(&tool->trace, &record->trace))
685  			break;
686  
687  	}
688  
689  	osnoise_print_stats(params, tool);
690  
691  	return_value = 0;
692  
693  	if (trace_is_off(&tool->trace, &record->trace)) {
694  		printf("osnoise hit stop tracing\n");
695  		if (params->trace_output) {
696  			printf("  Saving trace to %s\n", params->trace_output);
697  			save_trace_to_file(record->trace.inst, params->trace_output);
698  		}
699  	}
700  
701  out_top:
702  	trace_events_destroy(&record->trace, params->events);
703  	params->events = NULL;
704  out_free:
705  	osnoise_free_top(tool->data);
706  	osnoise_destroy_tool(record);
707  	osnoise_destroy_tool(tool);
708  	free(params);
709  out_exit:
710  	exit(return_value);
711  }
712