307 lines · 7.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> */#define _GNU_SOURCE#include <sys/types.h>#include <sys/stat.h>#include <pthread.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <fcntl.h>#include <stdio.h>#include <sched.h> #include <linux/compiler.h> #include "timerlat.h"#include "timerlat_aa.h"#include "timerlat_bpf.h" #define DEFAULT_TIMERLAT_PERIOD	1000			/* 1ms */ static int dma_latency_fd = -1; /* * timerlat_apply_config - apply common configs to the initialized tool */inttimerlat_apply_config(struct osnoise_tool *tool, struct timerlat_params *params){	int retval;	const char *const rtla_no_bpf = getenv("RTLA_NO_BPF"); 	/*	 * Try to enable BPF, unless disabled explicitly.	 * If BPF enablement fails, fall back to tracefs mode.	 */	if (rtla_no_bpf && strncmp_static(rtla_no_bpf, "1") == 0) {		debug_msg("RTLA_NO_BPF set, disabling BPF\n");		params->mode = TRACING_MODE_TRACEFS;	} else if (!tep_find_event_by_name(tool->trace.tep, "osnoise", "timerlat_sample")) {		debug_msg("osnoise:timerlat_sample missing, disabling BPF\n");		params->mode = TRACING_MODE_TRACEFS;	} else {		retval = timerlat_bpf_init(params);		if (retval) {			debug_msg("Could not enable BPF\n");			params->mode = TRACING_MODE_TRACEFS;		}	} 	/* Check if BPF action program is requested but BPF is not available */	if (params->bpf_action_program) {		if (params->mode == TRACING_MODE_TRACEFS) {			err_msg("BPF actions are not supported in tracefs-only mode\n");			goto out_err;		} 		if (timerlat_load_bpf_action_program(params->bpf_action_program))			goto out_err;	} 	retval = osnoise_set_timerlat_period_us(tool->context,						params->timerlat_period_us ?						params->timerlat_period_us :						DEFAULT_TIMERLAT_PERIOD);	if (retval) {		err_msg("Failed to set timerlat period\n");		goto out_err;	}  	retval = osnoise_set_print_stack(tool->context, params->print_stack);	if (retval) {		err_msg("Failed to set print stack\n");		goto out_err;	} 	retval = osnoise_set_timerlat_align(tool->context, params->timerlat_align);	if (retval && params->timerlat_align) {		/*		 * We might be running on a kernel that does not support timerlat align.		 * Unless user requested it explicitly, ignore the error.		 */		err_msg("Failed to enable timerlat align\n");		goto out_err;	} 	if (params->timerlat_align) {		retval = osnoise_set_timerlat_align_us(tool->context, params->timerlat_align_us);		if (retval) {			err_msg("Failed to set timerlat align us\n");			goto out_err;		}	} 	/*	 * If the user did not specify a type of thread, try user-threads first.	 * Fall back to kernel threads otherwise.	 */	if (!params->common.kernel_workload && !params->common.user_data) {		retval = tracefs_file_exists(NULL, "osnoise/per_cpu/cpu0/timerlat_fd");		if (retval) {			debug_msg("User-space interface detected, setting user-threads\n");			params->common.user_workload = 1;			params->common.user_data = 1;		} else {			debug_msg("User-space interface not detected, setting kernel-threads\n");			params->common.kernel_workload = 1;		}	} 	return common_apply_config(tool, &params->common); out_err:	return -1;} int timerlat_enable(struct osnoise_tool *tool){	struct timerlat_params *params = to_timerlat_params(tool->params);	int retval, i; 	if (params->dma_latency >= 0) {		dma_latency_fd = set_cpu_dma_latency(params->dma_latency);		if (dma_latency_fd < 0) {			err_msg("Could not set /dev/cpu_dma_latency.\n");			return -1;		}	} 	if (params->deepest_idle_state >= -1) {		if (!have_libcpupower_support()) {			err_msg("rtla built without libcpupower, --deepest-idle-state is not supported\n");			return -1;		} 		for_each_monitored_cpu(i, &params->common) {			if (save_cpu_idle_disable_state(i) < 0) {				err_msg("Could not save cpu idle state.\n");				return -1;			}			if (set_deepest_cpu_idle_state(i, params->deepest_idle_state) < 0) {				err_msg("Could not set deepest cpu idle state.\n");				return -1;			}		}	} 	if (!params->no_aa) {		tool->aa = osnoise_init_tool("timerlat_aa");		if (!tool->aa)			return -1; 		retval = timerlat_aa_init(tool->aa, params->dump_tasks, params->stack_format);		if (retval) {			err_msg("Failed to enable the auto analysis instance\n");			return retval;		} 		retval = enable_tracer_by_name(tool->aa->trace.inst, "timerlat");		if (retval) {			err_msg("Failed to enable aa tracer\n");			return retval;		}	} 	if (params->common.warmup > 0) {		debug_msg("Warming up for %d seconds\n", params->common.warmup);		sleep(params->common.warmup);		if (stop_tracing)			return -1;	} 	/*	 * Start the tracers here, after having set all instances.	 *	 * Let the trace instance start first for the case of hitting a stop	 * tracing while enabling other instances. The trace instance is the	 * one with most valuable information.	 */	if (tool->record)		trace_instance_start(&tool->record->trace);	if (!params->no_aa)		trace_instance_start(&tool->aa->trace);	if (params->mode == TRACING_MODE_TRACEFS) {		trace_instance_start(&tool->trace);	} else {		retval = timerlat_bpf_attach();		if (retval) {			err_msg("Error attaching BPF program\n");			return retval;		}	} 	/*	 * In tracefs and mixed mode, timerlat tracer handles stopping	 * on threshold	 */	if (params->mode != TRACING_MODE_BPF) {		retval = osn_set_stop(tool);		if (retval)			return retval;	} 	return 0;} void timerlat_analyze(struct osnoise_tool *tool, bool stopped){	struct timerlat_params *params = to_timerlat_params(tool->params); 	if (stopped) {		if (!params->no_aa)			timerlat_auto_analysis(params->common.stop_us,					       params->common.stop_total_us);	} else if (params->common.aa_only) {		char *max_lat; 		/*		 * If the trace did not stop with --aa-only, at least print		 * the max known latency.		 */		max_lat = tracefs_instance_file_read(tool->trace.inst, "tracing_max_latency", NULL);		if (max_lat) {			printf("  Max latency was %s\n", max_lat);			free(max_lat);		}	}} void timerlat_free(struct osnoise_tool *tool){	struct timerlat_params *params = to_timerlat_params(tool->params);	int i; 	timerlat_aa_destroy();	if (dma_latency_fd >= 0)		close(dma_latency_fd);	if (params->deepest_idle_state >= -1) {		for_each_monitored_cpu(i, &params->common) {			restore_cpu_idle_disable_state(i);		}	} 	osnoise_destroy_tool(tool->aa); 	if (params->mode != TRACING_MODE_TRACEFS)		timerlat_bpf_destroy();	free_cpu_idle_disable_states();} __noreturn static void timerlat_usage(int err){	int i; 	static const char * const msg[] = {		"",		"timerlat version " VERSION,		"",		"  usage: [rtla] timerlat [MODE] ...",		"",		"  modes:",		"     top   - prints the summary from timerlat tracer",		"     hist  - prints a histogram of timer latencies",		"",		"if no MODE is given, the top mode is called, passing the arguments",		NULL,	}; 	for (i = 0; msg[i]; i++)		fprintf(stderr, "%s\n", msg[i]);	exit(err);} int timerlat_main(int argc, char *argv[]){	if (argc == 0)		goto usage; 	/*	 * if timerlat was called without any argument, run the	 * default cmdline.	 */	if (argc == 1) {		run_tool(&timerlat_top_ops, argc, argv);		exit(0);	} 	if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {		timerlat_usage(129);	} else if (str_has_prefix(argv[1], "-")) {		/* the user skipped the tool, call the default one */		run_tool(&timerlat_top_ops, argc, argv);		exit(0);	} else if (strcmp(argv[1], "top") == 0) {		run_tool(&timerlat_top_ops, argc-1, &argv[1]);		exit(0);	} else if (strcmp(argv[1], "hist") == 0) {		run_tool(&timerlat_hist_ops, argc-1, &argv[1]);		exit(0);	} usage:	timerlat_usage(129);}