3186 lines · 76.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * OS Noise Tracer: computes the OS Noise suffered by a running thread. * Timerlat Tracer: measures the wakeup latency of a timer triggered IRQ and thread. * * Based on "hwlat_detector" tracer by: *   Copyright (C) 2008-2009 Jon Masters, Red Hat, Inc. <jcm@redhat.com> *   Copyright (C) 2013-2016 Steven Rostedt, Red Hat, Inc. <srostedt@redhat.com> *   With feedback from Clark Williams <williams@redhat.com> * * And also based on the rtsl tracer presented on: *  DE OLIVEIRA, Daniel Bristot, et al. Demystifying the real-time linux *  scheduling latency. In: 32nd Euromicro Conference on Real-Time Systems *  (ECRTS 2020). Schloss Dagstuhl-Leibniz-Zentrum fur Informatik, 2020. * * Copyright (C) 2021 Daniel Bristot de Oliveira, Red Hat, Inc. <bristot@redhat.com> */ #include <linux/kthread.h>#include <linux/tracefs.h>#include <linux/uaccess.h>#include <linux/cpumask.h>#include <linux/delay.h>#include <linux/sched/clock.h>#include <uapi/linux/sched/types.h>#include <linux/sched.h>#include <linux/string.h>#include "trace.h" #ifdef CONFIG_X86_LOCAL_APIC#include <asm/trace/irq_vectors.h>#undef TRACE_INCLUDE_PATH#undef TRACE_INCLUDE_FILE#endif /* CONFIG_X86_LOCAL_APIC */ #include <trace/events/irq.h>#include <trace/events/sched.h> #define CREATE_TRACE_POINTS#include <trace/events/osnoise.h> /* * Default values. */#define BANNER			"osnoise: "#define DEFAULT_SAMPLE_PERIOD	1000000			/* 1s */#define DEFAULT_SAMPLE_RUNTIME	1000000			/* 1s */ #define DEFAULT_TIMERLAT_PERIOD	1000			/* 1ms */#define DEFAULT_TIMERLAT_PRIO	95			/* FIFO 95 */ /* * osnoise/options entries. */enum osnoise_options_index {	OSN_DEFAULTS = 0,	OSN_WORKLOAD,	OSN_PANIC_ON_STOP,	OSN_PREEMPT_DISABLE,	OSN_IRQ_DISABLE,	OSN_TIMERLAT_ALIGN,	OSN_MAX}; static const char * const osnoise_options_str[OSN_MAX] = {							"DEFAULTS",							"OSNOISE_WORKLOAD",							"PANIC_ON_STOP",							"OSNOISE_PREEMPT_DISABLE",							"OSNOISE_IRQ_DISABLE",							"TIMERLAT_ALIGN" }; #define OSN_DEFAULT_OPTIONS		0x2static unsigned long osnoise_options	= OSN_DEFAULT_OPTIONS; /* * trace_array of the enabled osnoise/timerlat instances. */struct osnoise_instance {	struct list_head	list;	struct trace_array	*tr;}; static struct list_head osnoise_instances; static void osnoise_print(const char *fmt, ...){	struct osnoise_instance *inst;	struct trace_array *tr;	va_list ap; 	rcu_read_lock();	list_for_each_entry_rcu(inst, &osnoise_instances, list) {		tr = inst->tr;		va_start(ap, fmt);		trace_array_vprintk(tr, _RET_IP_, fmt, ap);		va_end(ap);	}	rcu_read_unlock();} static bool osnoise_has_registered_instances(void){	return !!list_first_or_null_rcu(&osnoise_instances,					struct osnoise_instance,					list);} /* * osnoise_instance_registered - check if a tr is already registered */static int osnoise_instance_registered(struct trace_array *tr){	struct osnoise_instance *inst;	int found = 0; 	rcu_read_lock();	list_for_each_entry_rcu(inst, &osnoise_instances, list) {		if (inst->tr == tr)			found = 1;	}	rcu_read_unlock(); 	return found;} /* * osnoise_register_instance - register a new trace instance * * Register a trace_array *tr in the list of instances running * osnoise/timerlat tracers. */static int osnoise_register_instance(struct trace_array *tr){	struct osnoise_instance *inst; 	/*	 * register/unregister serialization is provided by trace's	 * trace_types_lock.	 */	lockdep_assert_held(&trace_types_lock);	trace_array_init_printk(tr); 	inst = kmalloc_obj(*inst);	if (!inst)		return -ENOMEM; 	INIT_LIST_HEAD_RCU(&inst->list);	inst->tr = tr;	list_add_tail_rcu(&inst->list, &osnoise_instances); 	return 0;} /* *  osnoise_unregister_instance - unregister a registered trace instance * * Remove the trace_array *tr from the list of instances running * osnoise/timerlat tracers. */static void osnoise_unregister_instance(struct trace_array *tr){	struct osnoise_instance *inst;	int found = 0; 	/*	 * register/unregister serialization is provided by trace's	 * trace_types_lock.	 */	list_for_each_entry_rcu(inst, &osnoise_instances, list,				lockdep_is_held(&trace_types_lock)) {		if (inst->tr == tr) {			list_del_rcu(&inst->list);			found = 1;			break;		}	} 	if (!found)		return; 	/* Do a full sync to ensure that tr remains valid, not just inst */	synchronize_rcu();	kvfree(inst);} /* * NMI runtime info. */struct osn_nmi {	u64	count;	u64	delta_start;}; /* * IRQ runtime info. */struct osn_irq {	u64	count;	u64	arrival_time;	u64	delta_start;}; #define IRQ_CONTEXT	0#define THREAD_CONTEXT	1#define THREAD_URET	2/* * sofirq runtime info. */struct osn_softirq {	u64	count;	u64	arrival_time;	u64	delta_start;}; /* * thread runtime info. */struct osn_thread {	u64	count;	u64	arrival_time;	u64	delta_start;}; /* * Runtime information: this structure saves the runtime information used by * one sampling thread. */struct osnoise_variables {	struct task_struct	*kthread;	bool			sampling;	pid_t			pid;	struct osn_nmi		nmi;	struct osn_irq		irq;	struct osn_softirq	softirq;	struct osn_thread	thread;	local_t			int_counter;}; /* * Per-cpu runtime information. */static DEFINE_PER_CPU(struct osnoise_variables, per_cpu_osnoise_var); /* * this_cpu_osn_var - Return the per-cpu osnoise_variables on its relative CPU */static inline struct osnoise_variables *this_cpu_osn_var(void){	return this_cpu_ptr(&per_cpu_osnoise_var);} /* * Protect the interface. */static struct mutex interface_lock; #ifdef CONFIG_TIMERLAT_TRACER/* * Runtime information for the timer mode. */struct timerlat_variables {	struct task_struct	*kthread;	struct hrtimer		timer;	u64			rel_period;	u64			abs_period;	bool			tracing_thread;	u64			count;	bool			uthread_migrate;}; static DEFINE_PER_CPU(struct timerlat_variables, per_cpu_timerlat_var); /* * timerlat wake-up offset for next thread with TIMERLAT_ALIGN set. */static atomic64_t align_next; /* * this_cpu_tmr_var - Return the per-cpu timerlat_variables on its relative CPU */static inline struct timerlat_variables *this_cpu_tmr_var(void){	return this_cpu_ptr(&per_cpu_timerlat_var);} /* * tlat_var_reset - Reset the values of the given timerlat_variables */static inline void tlat_var_reset(void){	struct timerlat_variables *tlat_var;	int cpu; 	/* Synchronize with the timerlat interfaces */	mutex_lock(&interface_lock); 	/*	 * So far, all the values are initialized as 0, so	 * zeroing the structure is perfect.	 */	for_each_online_cpu(cpu) {		tlat_var = per_cpu_ptr(&per_cpu_timerlat_var, cpu);		if (tlat_var->kthread)			hrtimer_cancel(&tlat_var->timer);		memset(tlat_var, 0, sizeof(*tlat_var));	}	/*	 * Reset also align_next, to be filled by a new offset by the first timerlat	 * thread that wakes up, if TIMERLAT_ALIGN is set.	 */	atomic64_set(&align_next, 0); 	mutex_unlock(&interface_lock);}#else /* CONFIG_TIMERLAT_TRACER */#define tlat_var_reset()	do {} while (0)#endif /* CONFIG_TIMERLAT_TRACER */ /* * osn_var_reset - Reset the values of the given osnoise_variables */static inline void osn_var_reset(void){	struct osnoise_variables *osn_var;	int cpu; 	/*	 * So far, all the values are initialized as 0, so	 * zeroing the structure is perfect.	 */	for_each_online_cpu(cpu) {		osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);		memset(osn_var, 0, sizeof(*osn_var));	}} /* * osn_var_reset_all - Reset the value of all per-cpu osnoise_variables */static inline void osn_var_reset_all(void){	osn_var_reset();	tlat_var_reset();} /* * Tells NMIs to call back to the osnoise tracer to record timestamps. */bool trace_osnoise_callback_enabled; /* * Tracer data. */static struct osnoise_data {	u64	sample_period;		/* total sampling period */	u64	sample_runtime;		/* active sampling portion of period */	u64	stop_tracing;		/* stop trace in the internal operation (loop/irq) */	u64	stop_tracing_total;	/* stop trace in the final operation (report/thread) */#ifdef CONFIG_TIMERLAT_TRACER	u64	timerlat_period;	/* timerlat period */	u64	timerlat_align_us;	/* timerlat alignment */	u64	print_stack;		/* print IRQ stack if total > */	int	timerlat_tracer;	/* timerlat tracer */#endif	bool	tainted;		/* info users and developers about a problem */} osnoise_data = {	.sample_period			= DEFAULT_SAMPLE_PERIOD,	.sample_runtime			= DEFAULT_SAMPLE_RUNTIME,	.stop_tracing			= 0,	.stop_tracing_total		= 0,#ifdef CONFIG_TIMERLAT_TRACER	.print_stack			= 0,	.timerlat_period		= DEFAULT_TIMERLAT_PERIOD,	.timerlat_align_us		= 0,	.timerlat_tracer		= 0,#endif}; #ifdef CONFIG_TIMERLAT_TRACERstatic inline bool timerlat_enabled(void){	return osnoise_data.timerlat_tracer;} static inline int timerlat_softirq_exit(struct osnoise_variables *osn_var){	struct timerlat_variables *tlat_var = this_cpu_tmr_var();	/*	 * If the timerlat is enabled, but the irq handler did	 * not run yet enabling timerlat_tracer, do not trace.	 */	if (!tlat_var->tracing_thread) {		osn_var->softirq.arrival_time = 0;		osn_var->softirq.delta_start = 0;		return 0;	}	return 1;} static inline int timerlat_thread_exit(struct osnoise_variables *osn_var){	struct timerlat_variables *tlat_var = this_cpu_tmr_var();	/*	 * If the timerlat is enabled, but the irq handler did	 * not run yet enabling timerlat_tracer, do not trace.	 */	if (!tlat_var->tracing_thread) {		osn_var->thread.delta_start = 0;		osn_var->thread.arrival_time = 0;		return 0;	}	return 1;}#else /* CONFIG_TIMERLAT_TRACER */static inline bool timerlat_enabled(void){	return false;} static inline int timerlat_softirq_exit(struct osnoise_variables *osn_var){	return 1;}static inline int timerlat_thread_exit(struct osnoise_variables *osn_var){	return 1;}#endif #ifdef CONFIG_PREEMPT_RT/* * Print the osnoise header info. */static void print_osnoise_headers(struct seq_file *s){	if (osnoise_data.tainted)		seq_puts(s, "# osnoise is tainted!\n"); 	seq_puts(s, "#                                _-------=> irqs-off\n");	seq_puts(s, "#                               / _------=> need-resched\n");	seq_puts(s, "#                              | / _-----=> need-resched-lazy\n");	seq_puts(s, "#                              || / _----=> hardirq/softirq\n");	seq_puts(s, "#                              ||| / _---=> preempt-depth\n");	seq_puts(s, "#                              |||| / _--=> preempt-lazy-depth\n");	seq_puts(s, "#                              ||||| / _-=> migrate-disable\n"); 	seq_puts(s, "#                              |||||| /          ");	seq_puts(s, "                                     MAX\n"); 	seq_puts(s, "#                              ||||| /                         ");	seq_puts(s, "                    SINGLE      Interference counters:\n"); 	seq_puts(s, "#                              |||||||               RUNTIME   ");	seq_puts(s, "   NOISE  %% OF CPU  NOISE    +-----------------------------+\n"); 	seq_puts(s, "#           TASK-PID      CPU# |||||||   TIMESTAMP    IN US    ");	seq_puts(s, "   IN US  AVAILABLE  IN US     HW    NMI    IRQ   SIRQ THREAD\n"); 	seq_puts(s, "#              | |         |   |||||||      |           |      ");	seq_puts(s, "       |    |            |      |      |      |      |      |\n");}#else /* CONFIG_PREEMPT_RT */static void print_osnoise_headers(struct seq_file *s){	if (osnoise_data.tainted)		seq_puts(s, "# osnoise is tainted!\n"); 	seq_puts(s, "#                                _-----=> irqs-off\n");	seq_puts(s, "#                               / _----=> need-resched\n");	seq_puts(s, "#                              | / _---=> hardirq/softirq\n");	seq_puts(s, "#                              || / _--=> preempt-depth\n");	seq_puts(s, "#                              ||| / _-=> migrate-disable     ");	seq_puts(s, "                    MAX\n");	seq_puts(s, "#                              |||| /     delay               ");	seq_puts(s, "                    SINGLE      Interference counters:\n"); 	seq_puts(s, "#                              |||||               RUNTIME   ");	seq_puts(s, "   NOISE  %% OF CPU  NOISE    +-----------------------------+\n"); 	seq_puts(s, "#           TASK-PID      CPU# |||||   TIMESTAMP    IN US    ");	seq_puts(s, "   IN US  AVAILABLE  IN US     HW    NMI    IRQ   SIRQ THREAD\n"); 	seq_puts(s, "#              | |         |   |||||      |           |      ");	seq_puts(s, "       |    |            |      |      |      |      |      |\n");}#endif /* CONFIG_PREEMPT_RT */ /* * osnoise_taint - report an osnoise error. */#define osnoise_taint(msg) ({							\	osnoise_print(msg);							\	osnoise_data.tainted = true;						\}) /* * Record an osnoise_sample into the tracer buffer. */static void

Lines 1–500 of 3186.