| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> |
| */ |
| |
| #define _GNU_SOURCE |
| #include <stdlib.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <time.h> |
| |
| #include "osnoise.h" |
| #include "cli.h" |
| |
| struct osnoise_top_cpu { |
| unsigned long long sum_runtime; |
| unsigned long long sum_noise; |
| unsigned long long max_noise; |
| unsigned long long max_sample; |
| |
| unsigned long long hw_count; |
| unsigned long long nmi_count; |
| unsigned long long irq_count; |
| unsigned long long softirq_count; |
| unsigned long long thread_count; |
| |
| int sum_cycles; |
| }; |
| |
| struct osnoise_top_data { |
| struct osnoise_top_cpu *cpu_data; |
| }; |
| |
| /* |
| * osnoise_free_top - free runtime data |
| */ |
| static void osnoise_free_top(struct osnoise_top_data *data) |
| { |
| free(data->cpu_data); |
| free(data); |
| } |
| |
| static void osnoise_free_top_tool(struct osnoise_tool *tool) |
| { |
| osnoise_free_top(tool->data); |
| } |
| |
| /* |
| * osnoise_alloc_histogram - alloc runtime data |
| */ |
| static struct osnoise_top_data *osnoise_alloc_top(void) |
| { |
| struct osnoise_top_data *data; |
| |
| data = calloc(1, sizeof(*data)); |
| if (!data) |
| return NULL; |
| |
| /* one set of histograms per CPU */ |
| data->cpu_data = calloc(1, sizeof(*data->cpu_data) * nr_cpus); |
| if (!data->cpu_data) |
| goto cleanup; |
| |
| return data; |
| |
| cleanup: |
| osnoise_free_top(data); |
| return NULL; |
| } |
| |
| /* |
| * osnoise_top_handler - this is the handler for osnoise tracer events |
| */ |
| static int |
| osnoise_top_handler(struct trace_seq *s, struct tep_record *record, |
| struct tep_event *event, void *context) |
| { |
| struct trace_instance *trace = context; |
| struct osnoise_tool *tool; |
| unsigned long long val; |
| struct osnoise_top_cpu *cpu_data; |
| struct osnoise_top_data *data; |
| int cpu = record->cpu; |
| |
| tool = container_of(trace, struct osnoise_tool, trace); |
| |
| data = tool->data; |
| cpu_data = &data->cpu_data[cpu]; |
| |
| cpu_data->sum_cycles++; |
| |
| tep_get_field_val(s, event, "runtime", record, &val, 1); |
| update_sum(&cpu_data->sum_runtime, &val); |
| |
| tep_get_field_val(s, event, "noise", record, &val, 1); |
| update_max(&cpu_data->max_noise, &val); |
| update_sum(&cpu_data->sum_noise, &val); |
| |
| tep_get_field_val(s, event, "max_sample", record, &val, 1); |
| update_max(&cpu_data->max_sample, &val); |
| |
| tep_get_field_val(s, event, "hw_count", record, &val, 1); |
| update_sum(&cpu_data->hw_count, &val); |
| |
| tep_get_field_val(s, event, "nmi_count", record, &val, 1); |
| update_sum(&cpu_data->nmi_count, &val); |
| |
| tep_get_field_val(s, event, "irq_count", record, &val, 1); |
| update_sum(&cpu_data->irq_count, &val); |
| |
| tep_get_field_val(s, event, "softirq_count", record, &val, 1); |
| update_sum(&cpu_data->softirq_count, &val); |
| |
| tep_get_field_val(s, event, "thread_count", record, &val, 1); |
| update_sum(&cpu_data->thread_count, &val); |
| |
| return 0; |
| } |
| |
| /* |
| * osnoise_top_header - print the header of the tool output |
| */ |
| static void osnoise_top_header(struct osnoise_tool *top) |
| { |
| struct osnoise_params *params = to_osnoise_params(top->params); |
| struct trace_seq *s = top->trace.seq; |
| bool pretty = params->common.pretty_output; |
| char duration[26]; |
| |
| get_duration(top->start_time, duration, sizeof(duration)); |
| |
| if (pretty) |
| trace_seq_printf(s, "\033[2;37;40m"); |
| |
| trace_seq_printf(s, " "); |
| |
| if (params->mode == MODE_OSNOISE) { |
| trace_seq_printf(s, "Operating System Noise"); |
| trace_seq_printf(s, " "); |
| } else if (params->mode == MODE_HWNOISE) { |
| trace_seq_printf(s, "Hardware-related Noise"); |
| } |
| |
| trace_seq_printf(s, " "); |
| |
| if (pretty) |
| trace_seq_printf(s, "\033[0;0;0m"); |
| trace_seq_printf(s, "\n"); |
| |
| trace_seq_printf(s, "duration: %9s | time is in us\n", duration); |
| |
| if (pretty) |
| trace_seq_printf(s, "\033[2;30;47m"); |
| |
| trace_seq_printf(s, "CPU Period Runtime "); |
| trace_seq_printf(s, " Noise "); |
| trace_seq_printf(s, " %% CPU Aval "); |
| trace_seq_printf(s, " Max Noise Max Single "); |
| trace_seq_printf(s, " HW NMI"); |
| |
| if (params->mode == MODE_HWNOISE) |
| goto eol; |
| |
| trace_seq_printf(s, " IRQ Softirq Thread"); |
| |
| eol: |
| if (pretty) |
| trace_seq_printf(s, "\033[0;0;0m"); |
| trace_seq_printf(s, "\n"); |
| } |
| |
| /* |
| * clear_terminal - clears the output terminal |
| */ |
| static void clear_terminal(struct trace_seq *seq) |
| { |
| if (!config_debug) |
| trace_seq_printf(seq, "\033c"); |
| } |
| |
| /* |
| * osnoise_top_print - prints the output of a given CPU |
| */ |
| static void osnoise_top_print(struct osnoise_tool *tool, int cpu) |
| { |
| struct osnoise_params *params = to_osnoise_params(tool->params); |
| struct trace_seq *s = tool->trace.seq; |
| struct osnoise_top_cpu *cpu_data; |
| struct osnoise_top_data *data; |
| int percentage; |
| int decimal; |
| |
| data = tool->data; |
| cpu_data = &data->cpu_data[cpu]; |
| |
| if (!cpu_data->sum_runtime) |
| return; |
| |
| percentage = ((cpu_data->sum_runtime - cpu_data->sum_noise) * 10000000) |
| / cpu_data->sum_runtime; |
| decimal = percentage % 100000; |
| percentage = percentage / 100000; |
| |
| trace_seq_printf(s, "%3d #%-6d %12llu ", cpu, cpu_data->sum_cycles, cpu_data->sum_runtime); |
| trace_seq_printf(s, "%12llu ", cpu_data->sum_noise); |
| trace_seq_printf(s, " %3d.%05d", percentage, decimal); |
| trace_seq_printf(s, "%12llu %12llu", cpu_data->max_noise, cpu_data->max_sample); |
| |
| trace_seq_printf(s, "%12llu ", cpu_data->hw_count); |
| trace_seq_printf(s, "%12llu ", cpu_data->nmi_count); |
| |
| if (params->mode == MODE_HWNOISE) { |
| trace_seq_printf(s, "\n"); |
| return; |
| } |
| |
| trace_seq_printf(s, "%12llu ", cpu_data->irq_count); |
| trace_seq_printf(s, "%12llu ", cpu_data->softirq_count); |
| trace_seq_printf(s, "%12llu\n", cpu_data->thread_count); |
| } |
| |
| /* |
| * osnoise_print_stats - print data for all cpus |
| */ |
| static void |
| osnoise_print_stats(struct osnoise_tool *top) |
| { |
| struct osnoise_params *params = to_osnoise_params(top->params); |
| struct trace_instance *trace = &top->trace; |
| int i; |
| |
| if (!params->common.quiet) |
| clear_terminal(trace->seq); |
| |
| osnoise_top_header(top); |
| |
| for_each_monitored_cpu(i, ¶ms->common) { |
| osnoise_top_print(top, i); |
| } |
| |
| trace_seq_do_printf(trace->seq); |
| trace_seq_reset(trace->seq); |
| osnoise_report_missed_events(top); |
| } |
| |
| /* |
| * osnoise_top_apply_config - apply the top configs to the initialized tool |
| */ |
| static int |
| osnoise_top_apply_config(struct osnoise_tool *tool) |
| { |
| struct osnoise_params *params = to_osnoise_params(tool->params); |
| int retval; |
| |
| retval = osnoise_apply_config(tool, params); |
| if (retval) |
| goto out_err; |
| |
| if (params->mode == MODE_HWNOISE) { |
| retval = osnoise_set_irq_disable(tool->context, 1); |
| if (retval) { |
| err_msg("Failed to set OSNOISE_IRQ_DISABLE option\n"); |
| goto out_err; |
| } |
| } |
| |
| if (isatty(STDOUT_FILENO) && !params->common.quiet) |
| params->common.pretty_output = 1; |
| |
| return 0; |
| |
| out_err: |
| return -1; |
| } |
| |
| /* |
| * osnoise_init_top - initialize a osnoise top tool with parameters |
| */ |
| struct osnoise_tool *osnoise_init_top(struct common_params *params) |
| { |
| struct osnoise_tool *tool; |
| |
| tool = osnoise_init_tool("osnoise_top"); |
| if (!tool) |
| return NULL; |
| |
| tool->data = osnoise_alloc_top(); |
| if (!tool->data) { |
| osnoise_destroy_tool(tool); |
| return NULL; |
| } |
| |
| tep_register_event_handler(tool->trace.tep, -1, "ftrace", "osnoise", |
| osnoise_top_handler, NULL); |
| |
| return tool; |
| } |
| |
| struct tool_ops osnoise_top_ops = { |
| .tracer = "osnoise", |
| .comm_prefix = "osnoise/", |
| .parse_args = osnoise_top_parse_args, |
| .init_tool = osnoise_init_top, |
| .apply_config = osnoise_top_apply_config, |
| .enable = osnoise_enable, |
| .main = top_main_loop, |
| .print_stats = osnoise_print_stats, |
| .free = osnoise_free_top_tool, |
| }; |