mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	There is a spelling mistake in the -g help option, I believe it should be "graph". There is also a spelling mistake in a warning message. Fix both mistakes. Link: https://lkml.kernel.org/r/20210225165248.22050-2-Viktor.Rosendahl@bmw.de Signed-off-by: Colin Ian King <colin.king@canonical.com> Signed-off-by: Viktor Rosendahl <Viktor.Rosendahl@bmw.de> Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
		
			
				
	
	
		
			2108 lines
		
	
	
	
		
			50 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2108 lines
		
	
	
	
		
			50 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0
 | 
						|
/*
 | 
						|
 * Copyright (C) 2017, 2018, 2019, 2021 BMW Car IT GmbH
 | 
						|
 * Author: Viktor Rosendahl (viktor.rosendahl@bmw.de)
 | 
						|
 */
 | 
						|
 | 
						|
#define _GNU_SOURCE
 | 
						|
#define _POSIX_C_SOURCE 200809L
 | 
						|
 | 
						|
#include <ctype.h>
 | 
						|
#include <stdbool.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#include <err.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <getopt.h>
 | 
						|
#include <sched.h>
 | 
						|
#include <linux/unistd.h>
 | 
						|
#include <signal.h>
 | 
						|
#include <sys/inotify.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <pthread.h>
 | 
						|
#include <tracefs.h>
 | 
						|
 | 
						|
static const char *prg_name;
 | 
						|
static const char *prg_unknown = "unknown program name";
 | 
						|
 | 
						|
static int fd_stdout;
 | 
						|
 | 
						|
static int sched_policy;
 | 
						|
static bool sched_policy_set;
 | 
						|
 | 
						|
static int sched_pri;
 | 
						|
static bool sched_pri_set;
 | 
						|
 | 
						|
static bool trace_enable = true;
 | 
						|
static bool setup_ftrace = true;
 | 
						|
static bool use_random_sleep;
 | 
						|
 | 
						|
#define TRACE_OPTS				\
 | 
						|
	C(FUNC_TR, "function-trace"),		\
 | 
						|
	C(DISP_GR, "display-graph"),		\
 | 
						|
	C(NR,       NULL)
 | 
						|
 | 
						|
#undef C
 | 
						|
#define C(a, b) OPTIDX_##a
 | 
						|
 | 
						|
enum traceopt {
 | 
						|
	TRACE_OPTS
 | 
						|
};
 | 
						|
 | 
						|
#undef C
 | 
						|
#define C(a, b)  b
 | 
						|
 | 
						|
static const char *const optstr[] = {
 | 
						|
	TRACE_OPTS
 | 
						|
};
 | 
						|
 | 
						|
enum errhandling {
 | 
						|
	ERR_EXIT = 0,
 | 
						|
	ERR_WARN,
 | 
						|
	ERR_CLEANUP,
 | 
						|
};
 | 
						|
 | 
						|
static bool use_options[OPTIDX_NR];
 | 
						|
 | 
						|
static char inotify_buffer[655360];
 | 
						|
 | 
						|
#define likely(x)      __builtin_expect(!!(x), 1)
 | 
						|
#define unlikely(x)    __builtin_expect(!!(x), 0)
 | 
						|
#define bool2str(x)    (x ? "true":"false")
 | 
						|
 | 
						|
#define DEFAULT_NR_PRINTER_THREADS (3)
 | 
						|
static unsigned int nr_threads = DEFAULT_NR_PRINTER_THREADS;
 | 
						|
 | 
						|
#define DEFAULT_TABLE_SIZE (2)
 | 
						|
static unsigned int table_startsize = DEFAULT_TABLE_SIZE;
 | 
						|
 | 
						|
static int verbosity;
 | 
						|
 | 
						|
#define verbose_sizechange() (verbosity >= 1)
 | 
						|
#define verbose_lostevent()  (verbosity >= 2)
 | 
						|
#define verbose_ftrace()     (verbosity >= 1)
 | 
						|
 | 
						|
#define was_changed(ORIG, CUR) (strcmp(ORIG, CUR) != 0)
 | 
						|
#define needs_change(CUR, WANTED) (strcmp(CUR, WANTED) != 0)
 | 
						|
 | 
						|
static const char *debug_tracefile;
 | 
						|
static const char *debug_tracefile_dflt;
 | 
						|
static const char *debug_maxlat;
 | 
						|
static const char *debug_maxlat_dflt;
 | 
						|
static const char * const DEBUG_NOFILE = "[file not found]";
 | 
						|
 | 
						|
static const char * const TR_MAXLAT  = "tracing_max_latency";
 | 
						|
static const char * const TR_THRESH  = "tracing_thresh";
 | 
						|
static const char * const TR_CURRENT = "current_tracer";
 | 
						|
static const char * const TR_OPTIONS = "trace_options";
 | 
						|
 | 
						|
static const char * const NOP_TRACER = "nop";
 | 
						|
 | 
						|
static const char * const OPT_NO_PREFIX = "no";
 | 
						|
 | 
						|
#define DFLT_THRESHOLD_US "0"
 | 
						|
static const char *threshold = DFLT_THRESHOLD_US;
 | 
						|
 | 
						|
#define DEV_URANDOM     "/dev/urandom"
 | 
						|
#define RT_DEFAULT_PRI (99)
 | 
						|
#define DEFAULT_PRI    (0)
 | 
						|
 | 
						|
#define USEC_PER_MSEC (1000L)
 | 
						|
#define NSEC_PER_USEC (1000L)
 | 
						|
#define NSEC_PER_MSEC (USEC_PER_MSEC * NSEC_PER_USEC)
 | 
						|
 | 
						|
#define MSEC_PER_SEC (1000L)
 | 
						|
#define USEC_PER_SEC (USEC_PER_MSEC * MSEC_PER_SEC)
 | 
						|
#define NSEC_PER_SEC (NSEC_PER_MSEC * MSEC_PER_SEC)
 | 
						|
 | 
						|
#define SLEEP_TIME_MS_DEFAULT (1000L)
 | 
						|
#define TRY_PRINTMUTEX_MS (1000)
 | 
						|
 | 
						|
static long sleep_time = (USEC_PER_MSEC * SLEEP_TIME_MS_DEFAULT);
 | 
						|
 | 
						|
static const char * const queue_full_warning =
 | 
						|
"Could not queue trace for printing. It is likely that events happen faster\n"
 | 
						|
"than what they can be printed. Probably partly because of random sleeping\n";
 | 
						|
 | 
						|
static const char * const no_tracer_msg =
 | 
						|
"Could not find any tracers! Running this program as root may help!\n";
 | 
						|
 | 
						|
static const char * const no_latency_tr_msg =
 | 
						|
"No latency tracers are supported by your kernel!\n";
 | 
						|
 | 
						|
struct policy {
 | 
						|
	const char *name;
 | 
						|
	int policy;
 | 
						|
	int default_pri;
 | 
						|
};
 | 
						|
 | 
						|
static const struct policy policies[] = {
 | 
						|
	{ "other", SCHED_OTHER, DEFAULT_PRI    },
 | 
						|
	{ "batch", SCHED_BATCH, DEFAULT_PRI    },
 | 
						|
	{ "idle",  SCHED_IDLE,  DEFAULT_PRI    },
 | 
						|
	{ "rr",    SCHED_RR,    RT_DEFAULT_PRI },
 | 
						|
	{ "fifo",  SCHED_FIFO,  RT_DEFAULT_PRI },
 | 
						|
	{ NULL,    0,           DEFAULT_PRI    }
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
 * The default tracer will be the first on this list that is supported by the
 | 
						|
 * currently running Linux kernel.
 | 
						|
 */
 | 
						|
static const char * const relevant_tracers[] = {
 | 
						|
	"preemptirqsoff",
 | 
						|
	"preemptoff",
 | 
						|
	"irqsoff",
 | 
						|
	"wakeup",
 | 
						|
	"wakeup_rt",
 | 
						|
	"wakeup_dl",
 | 
						|
	NULL
 | 
						|
};
 | 
						|
 | 
						|
/* This is the list of tracers for which random sleep makes sense */
 | 
						|
static const char * const random_tracers[] = {
 | 
						|
	"preemptirqsoff",
 | 
						|
	"preemptoff",
 | 
						|
	"irqsoff",
 | 
						|
	NULL
 | 
						|
};
 | 
						|
 | 
						|
static const char *current_tracer;
 | 
						|
static bool force_tracer;
 | 
						|
 | 
						|
struct ftrace_state {
 | 
						|
	char *tracer;
 | 
						|
	char *thresh;
 | 
						|
	bool opt[OPTIDX_NR];
 | 
						|
	bool opt_valid[OPTIDX_NR];
 | 
						|
	pthread_mutex_t mutex;
 | 
						|
};
 | 
						|
 | 
						|
struct entry {
 | 
						|
	int ticket;
 | 
						|
	int ticket_completed_ref;
 | 
						|
};
 | 
						|
 | 
						|
struct print_state {
 | 
						|
	int ticket_counter;
 | 
						|
	int ticket_completed;
 | 
						|
	pthread_mutex_t mutex;
 | 
						|
	pthread_cond_t cond;
 | 
						|
	int cnt;
 | 
						|
	pthread_mutex_t cnt_mutex;
 | 
						|
};
 | 
						|
 | 
						|
struct short_msg {
 | 
						|
	char buf[160];
 | 
						|
	int len;
 | 
						|
};
 | 
						|
 | 
						|
static struct print_state printstate;
 | 
						|
static struct ftrace_state save_state;
 | 
						|
volatile sig_atomic_t signal_flag;
 | 
						|
 | 
						|
#define PROB_TABLE_MAX_SIZE (1000)
 | 
						|
 | 
						|
int probabilities[PROB_TABLE_MAX_SIZE];
 | 
						|
 | 
						|
struct sleep_table {
 | 
						|
	int *table;
 | 
						|
	int size;
 | 
						|
	pthread_mutex_t mutex;
 | 
						|
};
 | 
						|
 | 
						|
static struct sleep_table sleeptable;
 | 
						|
 | 
						|
#define QUEUE_SIZE (10)
 | 
						|
 | 
						|
struct queue {
 | 
						|
	struct entry entries[QUEUE_SIZE];
 | 
						|
	int next_prod_idx;
 | 
						|
	int next_cons_idx;
 | 
						|
	pthread_mutex_t mutex;
 | 
						|
	pthread_cond_t cond;
 | 
						|
};
 | 
						|
 | 
						|
#define MAX_THREADS (40)
 | 
						|
 | 
						|
struct queue printqueue;
 | 
						|
pthread_t printthread[MAX_THREADS];
 | 
						|
pthread_mutex_t print_mtx;
 | 
						|
#define PRINT_BUFFER_SIZE (16 * 1024 * 1024)
 | 
						|
 | 
						|
static void cleanup_exit(int status);
 | 
						|
static int set_trace_opt(const char *opt, bool value);
 | 
						|
 | 
						|
static __always_inline void *malloc_or_die(size_t size)
 | 
						|
{
 | 
						|
	void *ptr = malloc(size);
 | 
						|
 | 
						|
	if (unlikely(ptr == NULL)) {
 | 
						|
		warn("malloc() failed");
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
	return ptr;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void *malloc_or_die_nocleanup(size_t size)
 | 
						|
{
 | 
						|
	void *ptr = malloc(size);
 | 
						|
 | 
						|
	if (unlikely(ptr == NULL))
 | 
						|
		err(0, "malloc() failed");
 | 
						|
	return ptr;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void write_or_die(int fd, const char *buf, size_t count)
 | 
						|
{
 | 
						|
	ssize_t r;
 | 
						|
 | 
						|
	do {
 | 
						|
		r = write(fd, buf, count);
 | 
						|
		if (unlikely(r < 0)) {
 | 
						|
			if (errno == EINTR)
 | 
						|
				continue;
 | 
						|
			warn("write() failed");
 | 
						|
			cleanup_exit(EXIT_FAILURE);
 | 
						|
		}
 | 
						|
		count -= r;
 | 
						|
		buf += r;
 | 
						|
	} while (count > 0);
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void clock_gettime_or_die(clockid_t clk_id,
 | 
						|
						 struct timespec *tp)
 | 
						|
{
 | 
						|
	int r = clock_gettime(clk_id, tp);
 | 
						|
 | 
						|
	if (unlikely(r != 0))
 | 
						|
		err(EXIT_FAILURE, "clock_gettime() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void sigemptyset_or_die(sigset_t *s)
 | 
						|
{
 | 
						|
	if (unlikely(sigemptyset(s) != 0)) {
 | 
						|
		warn("sigemptyset() failed");
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void sigaddset_or_die(sigset_t *s, int signum)
 | 
						|
{
 | 
						|
	if (unlikely(sigaddset(s, signum) != 0)) {
 | 
						|
		warn("sigemptyset() failed");
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void sigaction_or_die(int signum,
 | 
						|
					     const struct sigaction *act,
 | 
						|
					     struct sigaction *oldact)
 | 
						|
{
 | 
						|
	if (unlikely(sigaction(signum, act, oldact) != 0)) {
 | 
						|
		warn("sigaction() failed");
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void open_stdout(void)
 | 
						|
{
 | 
						|
	if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
 | 
						|
		err(EXIT_FAILURE, "setvbuf() failed");
 | 
						|
	fd_stdout = fileno(stdout);
 | 
						|
	if (fd_stdout < 0)
 | 
						|
		err(EXIT_FAILURE, "fileno() failed");
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * It's not worth it to call cleanup_exit() from mutex functions because
 | 
						|
 * cleanup_exit() uses mutexes.
 | 
						|
 */
 | 
						|
static __always_inline void mutex_lock(pthread_mutex_t *mtx)
 | 
						|
{
 | 
						|
	errno = pthread_mutex_lock(mtx);
 | 
						|
	if (unlikely(errno))
 | 
						|
		err(EXIT_FAILURE, "pthread_mutex_lock() failed");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static __always_inline void mutex_unlock(pthread_mutex_t *mtx)
 | 
						|
{
 | 
						|
	errno = pthread_mutex_unlock(mtx);
 | 
						|
	if (unlikely(errno))
 | 
						|
		err(EXIT_FAILURE, "pthread_mutex_unlock() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void cond_signal(pthread_cond_t *cond)
 | 
						|
{
 | 
						|
	errno = pthread_cond_signal(cond);
 | 
						|
	if (unlikely(errno))
 | 
						|
		err(EXIT_FAILURE, "pthread_cond_signal() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void cond_wait(pthread_cond_t *restrict cond,
 | 
						|
				      pthread_mutex_t *restrict mutex)
 | 
						|
{
 | 
						|
	errno = pthread_cond_wait(cond, mutex);
 | 
						|
	if (unlikely(errno))
 | 
						|
		err(EXIT_FAILURE, "pthread_cond_wait() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void cond_broadcast(pthread_cond_t *cond)
 | 
						|
{
 | 
						|
	errno = pthread_cond_broadcast(cond);
 | 
						|
	if (unlikely(errno))
 | 
						|
		err(EXIT_FAILURE, "pthread_cond_broadcast() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void
 | 
						|
mutex_init(pthread_mutex_t *mutex,
 | 
						|
	   const pthread_mutexattr_t *attr)
 | 
						|
{
 | 
						|
	errno = pthread_mutex_init(mutex, attr);
 | 
						|
	if (errno)
 | 
						|
		err(EXIT_FAILURE, "pthread_mutex_init() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void mutexattr_init(pthread_mutexattr_t *attr)
 | 
						|
{
 | 
						|
	errno = pthread_mutexattr_init(attr);
 | 
						|
	if (errno)
 | 
						|
		err(EXIT_FAILURE, "pthread_mutexattr_init() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void mutexattr_destroy(pthread_mutexattr_t *attr)
 | 
						|
{
 | 
						|
	errno = pthread_mutexattr_destroy(attr);
 | 
						|
	if (errno)
 | 
						|
		err(EXIT_FAILURE, "pthread_mutexattr_destroy() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void mutexattr_settype(pthread_mutexattr_t *attr,
 | 
						|
					      int type)
 | 
						|
{
 | 
						|
	errno = pthread_mutexattr_settype(attr, type);
 | 
						|
	if (errno)
 | 
						|
		err(EXIT_FAILURE, "pthread_mutexattr_settype() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void condattr_init(pthread_condattr_t *attr)
 | 
						|
{
 | 
						|
	errno = pthread_condattr_init(attr);
 | 
						|
	if (errno)
 | 
						|
		err(EXIT_FAILURE, "pthread_condattr_init() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void condattr_destroy(pthread_condattr_t *attr)
 | 
						|
{
 | 
						|
	errno = pthread_condattr_destroy(attr);
 | 
						|
	if (errno)
 | 
						|
		err(EXIT_FAILURE, "pthread_condattr_destroy() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void condattr_setclock(pthread_condattr_t *attr,
 | 
						|
					      clockid_t clock_id)
 | 
						|
{
 | 
						|
	errno = pthread_condattr_setclock(attr, clock_id);
 | 
						|
	if (unlikely(errno))
 | 
						|
		err(EXIT_FAILURE, "pthread_condattr_setclock() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void cond_init(pthread_cond_t *cond,
 | 
						|
				      const pthread_condattr_t *attr)
 | 
						|
{
 | 
						|
	errno = pthread_cond_init(cond, attr);
 | 
						|
	if (errno)
 | 
						|
		err(EXIT_FAILURE, "pthread_cond_init() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline int
 | 
						|
cond_timedwait(pthread_cond_t *restrict cond,
 | 
						|
	       pthread_mutex_t *restrict mutex,
 | 
						|
	       const struct timespec *restrict abstime)
 | 
						|
{
 | 
						|
	errno = pthread_cond_timedwait(cond, mutex, abstime);
 | 
						|
	if (errno && errno != ETIMEDOUT)
 | 
						|
		err(EXIT_FAILURE, "pthread_cond_timedwait() failed");
 | 
						|
	return errno;
 | 
						|
}
 | 
						|
 | 
						|
static void init_printstate(void)
 | 
						|
{
 | 
						|
	pthread_condattr_t cattr;
 | 
						|
 | 
						|
	printstate.ticket_counter = 0;
 | 
						|
	printstate.ticket_completed = 0;
 | 
						|
	printstate.cnt = 0;
 | 
						|
 | 
						|
	mutex_init(&printstate.mutex, NULL);
 | 
						|
 | 
						|
	condattr_init(&cattr);
 | 
						|
	condattr_setclock(&cattr, CLOCK_MONOTONIC);
 | 
						|
	cond_init(&printstate.cond, &cattr);
 | 
						|
	condattr_destroy(&cattr);
 | 
						|
}
 | 
						|
 | 
						|
static void init_print_mtx(void)
 | 
						|
{
 | 
						|
	pthread_mutexattr_t mattr;
 | 
						|
 | 
						|
	mutexattr_init(&mattr);
 | 
						|
	mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
 | 
						|
	mutex_init(&print_mtx, &mattr);
 | 
						|
	mutexattr_destroy(&mattr);
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
static void signal_blocking(int how)
 | 
						|
{
 | 
						|
	sigset_t s;
 | 
						|
 | 
						|
	sigemptyset_or_die(&s);
 | 
						|
	sigaddset_or_die(&s, SIGHUP);
 | 
						|
	sigaddset_or_die(&s, SIGTERM);
 | 
						|
	sigaddset_or_die(&s, SIGINT);
 | 
						|
 | 
						|
	errno = pthread_sigmask(how, &s, NULL);
 | 
						|
	if (unlikely(errno)) {
 | 
						|
		warn("pthread_sigmask() failed");
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void signal_handler(int num)
 | 
						|
{
 | 
						|
	signal_flag = num;
 | 
						|
}
 | 
						|
 | 
						|
static void setup_sig_handler(void)
 | 
						|
{
 | 
						|
	struct sigaction sa;
 | 
						|
 | 
						|
	memset(&sa, 0, sizeof(sa));
 | 
						|
	sa.sa_handler = signal_handler;
 | 
						|
 | 
						|
	sigaction_or_die(SIGHUP, &sa, NULL);
 | 
						|
	sigaction_or_die(SIGTERM, &sa, NULL);
 | 
						|
	sigaction_or_die(SIGINT, &sa, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static void process_signal(int signal)
 | 
						|
{
 | 
						|
	char *name;
 | 
						|
 | 
						|
	name = strsignal(signal);
 | 
						|
	if (name == NULL)
 | 
						|
		printf("Received signal %d\n", signal);
 | 
						|
	else
 | 
						|
		printf("Received signal %d (%s)\n", signal, name);
 | 
						|
	cleanup_exit(EXIT_SUCCESS);
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void check_signals(void)
 | 
						|
{
 | 
						|
	int signal = signal_flag;
 | 
						|
 | 
						|
	if (unlikely(signal))
 | 
						|
		process_signal(signal);
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void get_time_in_future(struct timespec *future,
 | 
						|
					       long time_us)
 | 
						|
{
 | 
						|
	long nsec;
 | 
						|
 | 
						|
	clock_gettime_or_die(CLOCK_MONOTONIC, future);
 | 
						|
	future->tv_sec += time_us / USEC_PER_SEC;
 | 
						|
	nsec = future->tv_nsec + (time_us * NSEC_PER_USEC) % NSEC_PER_SEC;
 | 
						|
	if (nsec >= NSEC_PER_SEC) {
 | 
						|
		future->tv_nsec = nsec % NSEC_PER_SEC;
 | 
						|
		future->tv_sec += 1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline bool time_has_passed(const struct timespec *time)
 | 
						|
{
 | 
						|
	struct timespec now;
 | 
						|
 | 
						|
	clock_gettime_or_die(CLOCK_MONOTONIC, &now);
 | 
						|
	if (now.tv_sec > time->tv_sec)
 | 
						|
		return true;
 | 
						|
	if (now.tv_sec < time->tv_sec)
 | 
						|
		return false;
 | 
						|
	return (now.tv_nsec >= time->tv_nsec);
 | 
						|
}
 | 
						|
 | 
						|
static bool mutex_trylock_limit(pthread_mutex_t *mutex, int time_ms)
 | 
						|
{
 | 
						|
	long time_us = time_ms * USEC_PER_MSEC;
 | 
						|
	struct timespec limit;
 | 
						|
 | 
						|
	get_time_in_future(&limit, time_us);
 | 
						|
	do {
 | 
						|
		errno =  pthread_mutex_trylock(mutex);
 | 
						|
		if (errno && errno != EBUSY)
 | 
						|
			err(EXIT_FAILURE, "pthread_mutex_trylock() failed");
 | 
						|
	} while (errno && !time_has_passed(&limit));
 | 
						|
	return errno == 0;
 | 
						|
}
 | 
						|
 | 
						|
static void restore_trace_opts(const struct ftrace_state *state,
 | 
						|
				const bool *cur)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	int r;
 | 
						|
 | 
						|
	for (i = 0; i < OPTIDX_NR; i++)
 | 
						|
		if (state->opt_valid[i] && state->opt[i] != cur[i]) {
 | 
						|
			r = set_trace_opt(optstr[i], state->opt[i]);
 | 
						|
			if (r < 0)
 | 
						|
				warnx("Failed to restore the %s option to %s",
 | 
						|
				      optstr[i], bool2str(state->opt[i]));
 | 
						|
			else if (verbose_ftrace())
 | 
						|
				printf("Restored the %s option in %s to %s\n",
 | 
						|
				       optstr[i], TR_OPTIONS,
 | 
						|
				       bool2str(state->opt[i]));
 | 
						|
		}
 | 
						|
}
 | 
						|
 | 
						|
static char *read_file(const char *file, enum errhandling h)
 | 
						|
{
 | 
						|
	int psize;
 | 
						|
	char *r;
 | 
						|
	static const char *emsg = "Failed to read the %s file";
 | 
						|
 | 
						|
	r = tracefs_instance_file_read(NULL, file, &psize);
 | 
						|
	if (!r) {
 | 
						|
		if (h) {
 | 
						|
			warn(emsg, file);
 | 
						|
			if (h == ERR_CLEANUP)
 | 
						|
				cleanup_exit(EXIT_FAILURE);
 | 
						|
		} else
 | 
						|
			errx(EXIT_FAILURE, emsg, file);
 | 
						|
	}
 | 
						|
 | 
						|
	if (r && r[psize - 1] == '\n')
 | 
						|
		r[psize - 1] = '\0';
 | 
						|
	return r;
 | 
						|
}
 | 
						|
 | 
						|
static void restore_file(const char *file, char **saved, const char *cur)
 | 
						|
{
 | 
						|
	if (*saved && was_changed(*saved, cur)) {
 | 
						|
		if (tracefs_instance_file_write(NULL, file, *saved) < 0)
 | 
						|
			warnx("Failed to restore %s to %s!", file, *saved);
 | 
						|
		else if (verbose_ftrace())
 | 
						|
			printf("Restored %s to %s\n", file, *saved);
 | 
						|
		free(*saved);
 | 
						|
		*saved = NULL;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void restore_ftrace(void)
 | 
						|
{
 | 
						|
	mutex_lock(&save_state.mutex);
 | 
						|
 | 
						|
	restore_file(TR_CURRENT, &save_state.tracer, current_tracer);
 | 
						|
	restore_file(TR_THRESH, &save_state.thresh, threshold);
 | 
						|
	restore_trace_opts(&save_state, use_options);
 | 
						|
 | 
						|
	mutex_unlock(&save_state.mutex);
 | 
						|
}
 | 
						|
 | 
						|
static void cleanup_exit(int status)
 | 
						|
{
 | 
						|
	char *maxlat;
 | 
						|
 | 
						|
	if (!setup_ftrace)
 | 
						|
		exit(status);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * We try the print_mtx for 1 sec in order to avoid garbled
 | 
						|
	 * output if possible, but if it cannot be obtained we proceed anyway.
 | 
						|
	 */
 | 
						|
	mutex_trylock_limit(&print_mtx, TRY_PRINTMUTEX_MS);
 | 
						|
 | 
						|
	maxlat = read_file(TR_MAXLAT, ERR_WARN);
 | 
						|
	if (maxlat) {
 | 
						|
		printf("The maximum detected latency was: %sus\n", maxlat);
 | 
						|
		free(maxlat);
 | 
						|
	}
 | 
						|
 | 
						|
	restore_ftrace();
 | 
						|
	/*
 | 
						|
	 * We do not need to unlock the print_mtx here because we will exit at
 | 
						|
	 * the end of this function. Unlocking print_mtx causes problems if a
 | 
						|
	 * print thread happens to be waiting for the mutex because we have
 | 
						|
	 * just changed the ftrace settings to the original and thus the
 | 
						|
	 * print thread would output incorrect data from ftrace.
 | 
						|
	 */
 | 
						|
	exit(status);
 | 
						|
}
 | 
						|
 | 
						|
static void init_save_state(void)
 | 
						|
{
 | 
						|
	pthread_mutexattr_t mattr;
 | 
						|
 | 
						|
	mutexattr_init(&mattr);
 | 
						|
	mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
 | 
						|
	mutex_init(&save_state.mutex, &mattr);
 | 
						|
	mutexattr_destroy(&mattr);
 | 
						|
 | 
						|
	save_state.tracer = NULL;
 | 
						|
	save_state.thresh = NULL;
 | 
						|
	save_state.opt_valid[OPTIDX_FUNC_TR] = false;
 | 
						|
	save_state.opt_valid[OPTIDX_DISP_GR] = false;
 | 
						|
}
 | 
						|
 | 
						|
static int printstate_next_ticket(struct entry *req)
 | 
						|
{
 | 
						|
	int r;
 | 
						|
 | 
						|
	r = ++(printstate.ticket_counter);
 | 
						|
	req->ticket = r;
 | 
						|
	req->ticket_completed_ref = printstate.ticket_completed;
 | 
						|
	cond_broadcast(&printstate.cond);
 | 
						|
	return r;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline
 | 
						|
void printstate_mark_req_completed(const struct entry *req)
 | 
						|
{
 | 
						|
	if (req->ticket > printstate.ticket_completed)
 | 
						|
		printstate.ticket_completed = req->ticket;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline
 | 
						|
bool printstate_has_new_req_arrived(const struct entry *req)
 | 
						|
{
 | 
						|
	return (printstate.ticket_counter != req->ticket);
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline int printstate_cnt_inc(void)
 | 
						|
{
 | 
						|
	int value;
 | 
						|
 | 
						|
	mutex_lock(&printstate.cnt_mutex);
 | 
						|
	value = ++printstate.cnt;
 | 
						|
	mutex_unlock(&printstate.cnt_mutex);
 | 
						|
	return value;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline int printstate_cnt_dec(void)
 | 
						|
{
 | 
						|
	int value;
 | 
						|
 | 
						|
	mutex_lock(&printstate.cnt_mutex);
 | 
						|
	value = --printstate.cnt;
 | 
						|
	mutex_unlock(&printstate.cnt_mutex);
 | 
						|
	return value;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline int printstate_cnt_read(void)
 | 
						|
{
 | 
						|
	int value;
 | 
						|
 | 
						|
	mutex_lock(&printstate.cnt_mutex);
 | 
						|
	value = printstate.cnt;
 | 
						|
	mutex_unlock(&printstate.cnt_mutex);
 | 
						|
	return value;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline
 | 
						|
bool prev_req_won_race(const struct entry *req)
 | 
						|
{
 | 
						|
	return (printstate.ticket_completed != req->ticket_completed_ref);
 | 
						|
}
 | 
						|
 | 
						|
static void sleeptable_resize(int size, bool printout, struct short_msg *msg)
 | 
						|
{
 | 
						|
	int bytes;
 | 
						|
 | 
						|
	if (printout) {
 | 
						|
		msg->len = 0;
 | 
						|
		if (unlikely(size > PROB_TABLE_MAX_SIZE))
 | 
						|
			bytes = snprintf(msg->buf, sizeof(msg->buf),
 | 
						|
"Cannot increase probability table to %d (maximum size reached)\n", size);
 | 
						|
		else
 | 
						|
			bytes = snprintf(msg->buf, sizeof(msg->buf),
 | 
						|
"Increasing probability table to %d\n", size);
 | 
						|
		if (bytes < 0)
 | 
						|
			warn("snprintf() failed");
 | 
						|
		else
 | 
						|
			msg->len = bytes;
 | 
						|
	}
 | 
						|
 | 
						|
	if (unlikely(size < 0)) {
 | 
						|
		/* Should never happen */
 | 
						|
		warnx("Bad program state at %s:%d", __FILE__, __LINE__);
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	sleeptable.size = size;
 | 
						|
	sleeptable.table = &probabilities[PROB_TABLE_MAX_SIZE - size];
 | 
						|
}
 | 
						|
 | 
						|
static void init_probabilities(void)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	int j = 1000;
 | 
						|
 | 
						|
	for (i = 0; i < PROB_TABLE_MAX_SIZE; i++) {
 | 
						|
		probabilities[i] = 1000 / j;
 | 
						|
		j--;
 | 
						|
	}
 | 
						|
	mutex_init(&sleeptable.mutex, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static int table_get_probability(const struct entry *req,
 | 
						|
				 struct short_msg *msg)
 | 
						|
{
 | 
						|
	int diff = req->ticket - req->ticket_completed_ref;
 | 
						|
	int rval = 0;
 | 
						|
 | 
						|
	msg->len = 0;
 | 
						|
	diff--;
 | 
						|
	/* Should never happen...*/
 | 
						|
	if (unlikely(diff < 0)) {
 | 
						|
		warnx("Programmer assumption error at %s:%d\n", __FILE__,
 | 
						|
		      __LINE__);
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
	mutex_lock(&sleeptable.mutex);
 | 
						|
	if (diff >= (sleeptable.size - 1)) {
 | 
						|
		rval = sleeptable.table[sleeptable.size - 1];
 | 
						|
		sleeptable_resize(sleeptable.size + 1, verbose_sizechange(),
 | 
						|
				  msg);
 | 
						|
	} else {
 | 
						|
		rval = sleeptable.table[diff];
 | 
						|
	}
 | 
						|
	mutex_unlock(&sleeptable.mutex);
 | 
						|
	return rval;
 | 
						|
}
 | 
						|
 | 
						|
static void init_queue(struct queue *q)
 | 
						|
{
 | 
						|
	q->next_prod_idx = 0;
 | 
						|
	q->next_cons_idx = 0;
 | 
						|
	mutex_init(&q->mutex, NULL);
 | 
						|
	errno = pthread_cond_init(&q->cond, NULL);
 | 
						|
	if (errno)
 | 
						|
		err(EXIT_FAILURE, "pthread_cond_init() failed");
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline int queue_len(const struct queue *q)
 | 
						|
{
 | 
						|
	if (q->next_prod_idx >= q->next_cons_idx)
 | 
						|
		return q->next_prod_idx - q->next_cons_idx;
 | 
						|
	else
 | 
						|
		return QUEUE_SIZE - q->next_cons_idx + q->next_prod_idx;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline int queue_nr_free(const struct queue *q)
 | 
						|
{
 | 
						|
	int nr_free = QUEUE_SIZE - queue_len(q);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If there is only one slot left we will anyway lie and claim that the
 | 
						|
	 * queue is full because adding an element will make it appear empty
 | 
						|
	 */
 | 
						|
	if (nr_free == 1)
 | 
						|
		nr_free = 0;
 | 
						|
	return nr_free;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void queue_idx_inc(int *idx)
 | 
						|
{
 | 
						|
	*idx = (*idx + 1) % QUEUE_SIZE;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void queue_push_to_back(struct queue *q,
 | 
						|
					      const struct entry *e)
 | 
						|
{
 | 
						|
	q->entries[q->next_prod_idx] = *e;
 | 
						|
	queue_idx_inc(&q->next_prod_idx);
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline struct entry queue_pop_from_front(struct queue *q)
 | 
						|
{
 | 
						|
	struct entry e = q->entries[q->next_cons_idx];
 | 
						|
 | 
						|
	queue_idx_inc(&q->next_cons_idx);
 | 
						|
	return e;
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void queue_cond_signal(struct queue *q)
 | 
						|
{
 | 
						|
	cond_signal(&q->cond);
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline void queue_cond_wait(struct queue *q)
 | 
						|
{
 | 
						|
	cond_wait(&q->cond, &q->mutex);
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline int queue_try_to_add_entry(struct queue *q,
 | 
						|
						  const struct entry *e)
 | 
						|
{
 | 
						|
	int r = 0;
 | 
						|
 | 
						|
	mutex_lock(&q->mutex);
 | 
						|
	if (queue_nr_free(q) > 0) {
 | 
						|
		queue_push_to_back(q, e);
 | 
						|
		cond_signal(&q->cond);
 | 
						|
	} else
 | 
						|
		r = -1;
 | 
						|
	mutex_unlock(&q->mutex);
 | 
						|
	return r;
 | 
						|
}
 | 
						|
 | 
						|
static struct entry queue_wait_for_entry(struct queue *q)
 | 
						|
{
 | 
						|
	struct entry e;
 | 
						|
 | 
						|
	mutex_lock(&q->mutex);
 | 
						|
	while (true) {
 | 
						|
		if (queue_len(&printqueue) > 0) {
 | 
						|
			e = queue_pop_from_front(q);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		queue_cond_wait(q);
 | 
						|
	}
 | 
						|
	mutex_unlock(&q->mutex);
 | 
						|
 | 
						|
	return e;
 | 
						|
}
 | 
						|
 | 
						|
static const struct policy *policy_from_name(const char *name)
 | 
						|
{
 | 
						|
	const struct policy *p = &policies[0];
 | 
						|
 | 
						|
	while (p->name != NULL) {
 | 
						|
		if (!strcmp(name, p->name))
 | 
						|
			return p;
 | 
						|
		p++;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static const char *policy_name(int policy)
 | 
						|
{
 | 
						|
	const struct policy *p = &policies[0];
 | 
						|
	static const char *rval = "unknown";
 | 
						|
 | 
						|
	while (p->name != NULL) {
 | 
						|
		if (p->policy == policy)
 | 
						|
			return p->name;
 | 
						|
		p++;
 | 
						|
	}
 | 
						|
	return rval;
 | 
						|
}
 | 
						|
 | 
						|
static bool is_relevant_tracer(const char *name)
 | 
						|
{
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	for (i = 0; relevant_tracers[i]; i++)
 | 
						|
		if (!strcmp(name, relevant_tracers[i]))
 | 
						|
			return true;
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
static bool random_makes_sense(const char *name)
 | 
						|
{
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	for (i = 0; random_tracers[i]; i++)
 | 
						|
		if (!strcmp(name, random_tracers[i]))
 | 
						|
			return true;
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
static void show_available(void)
 | 
						|
{
 | 
						|
	char **tracers;
 | 
						|
	int found = 0;
 | 
						|
	int i;
 | 
						|
 | 
						|
	tracers = tracefs_tracers(NULL);
 | 
						|
	for (i = 0; tracers && tracers[i]; i++) {
 | 
						|
		if (is_relevant_tracer(tracers[i]))
 | 
						|
			found++;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!tracers) {
 | 
						|
		warnx(no_tracer_msg);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!found) {
 | 
						|
		warnx(no_latency_tr_msg);
 | 
						|
		tracefs_list_free(tracers);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	printf("The following latency tracers are available on your system:\n");
 | 
						|
	for (i = 0; tracers[i]; i++) {
 | 
						|
		if (is_relevant_tracer(tracers[i]))
 | 
						|
			printf("%s\n", tracers[i]);
 | 
						|
	}
 | 
						|
	tracefs_list_free(tracers);
 | 
						|
}
 | 
						|
 | 
						|
static bool tracer_valid(const char *name, bool *notracer)
 | 
						|
{
 | 
						|
	char **tracers;
 | 
						|
	int i;
 | 
						|
	bool rval = false;
 | 
						|
 | 
						|
	*notracer = false;
 | 
						|
	tracers = tracefs_tracers(NULL);
 | 
						|
	if (!tracers) {
 | 
						|
		*notracer = true;
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	for (i = 0; tracers[i]; i++)
 | 
						|
		if (!strcmp(tracers[i], name)) {
 | 
						|
			rval = true;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	tracefs_list_free(tracers);
 | 
						|
	return rval;
 | 
						|
}
 | 
						|
 | 
						|
static const char *find_default_tracer(void)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	bool notracer;
 | 
						|
	bool valid;
 | 
						|
 | 
						|
	for (i = 0; relevant_tracers[i]; i++) {
 | 
						|
		valid = tracer_valid(relevant_tracers[i], ¬racer);
 | 
						|
		if (notracer)
 | 
						|
			errx(EXIT_FAILURE, no_tracer_msg);
 | 
						|
		if (valid)
 | 
						|
			return relevant_tracers[i];
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static bool toss_coin(struct drand48_data *buffer, unsigned int prob)
 | 
						|
{
 | 
						|
	long r;
 | 
						|
 | 
						|
	if (unlikely(lrand48_r(buffer, &r))) {
 | 
						|
		warnx("lrand48_r() failed");
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
	r = r % 1000L;
 | 
						|
	if (r < prob)
 | 
						|
		return true;
 | 
						|
	else
 | 
						|
		return false;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static long go_to_sleep(const struct entry *req)
 | 
						|
{
 | 
						|
	struct timespec future;
 | 
						|
	long delay = sleep_time;
 | 
						|
 | 
						|
	get_time_in_future(&future, delay);
 | 
						|
 | 
						|
	mutex_lock(&printstate.mutex);
 | 
						|
	while (!printstate_has_new_req_arrived(req)) {
 | 
						|
		cond_timedwait(&printstate.cond, &printstate.mutex, &future);
 | 
						|
		if (time_has_passed(&future))
 | 
						|
			break;
 | 
						|
	};
 | 
						|
 | 
						|
	if (printstate_has_new_req_arrived(req))
 | 
						|
		delay = -1;
 | 
						|
	mutex_unlock(&printstate.mutex);
 | 
						|
 | 
						|
	return delay;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void set_priority(void)
 | 
						|
{
 | 
						|
	int r;
 | 
						|
	pid_t pid;
 | 
						|
	struct sched_param param;
 | 
						|
 | 
						|
	memset(¶m, 0, sizeof(param));
 | 
						|
	param.sched_priority = sched_pri;
 | 
						|
 | 
						|
	pid = getpid();
 | 
						|
	r = sched_setscheduler(pid, sched_policy, ¶m);
 | 
						|
 | 
						|
	if (r != 0)
 | 
						|
		err(EXIT_FAILURE, "sched_setscheduler() failed");
 | 
						|
}
 | 
						|
 | 
						|
pid_t latency_collector_gettid(void)
 | 
						|
{
 | 
						|
	return (pid_t) syscall(__NR_gettid);
 | 
						|
}
 | 
						|
 | 
						|
static void print_priority(void)
 | 
						|
{
 | 
						|
	pid_t tid;
 | 
						|
	int policy;
 | 
						|
	int r;
 | 
						|
	struct sched_param param;
 | 
						|
 | 
						|
	tid = latency_collector_gettid();
 | 
						|
	r = pthread_getschedparam(pthread_self(), &policy, ¶m);
 | 
						|
	if (r != 0) {
 | 
						|
		warn("pthread_getschedparam() failed");
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
	mutex_lock(&print_mtx);
 | 
						|
	printf("Thread %d runs with scheduling policy %s and priority %d\n",
 | 
						|
	       tid, policy_name(policy), param.sched_priority);
 | 
						|
	mutex_unlock(&print_mtx);
 | 
						|
}
 | 
						|
 | 
						|
static __always_inline
 | 
						|
void __print_skipmessage(const struct short_msg *resize_msg,
 | 
						|
			 const struct timespec *timestamp, char *buffer,
 | 
						|
			 size_t bufspace, const struct entry *req, bool excuse,
 | 
						|
			 const char *str)
 | 
						|
{
 | 
						|
	ssize_t bytes = 0;
 | 
						|
	char *p = &buffer[0];
 | 
						|
	long us, sec;
 | 
						|
	int r;
 | 
						|
 | 
						|
	sec = timestamp->tv_sec;
 | 
						|
	us = timestamp->tv_nsec / 1000;
 | 
						|
 | 
						|
	if (resize_msg != NULL && resize_msg->len > 0) {
 | 
						|
		strncpy(p, resize_msg->buf, resize_msg->len);
 | 
						|
		bytes += resize_msg->len;
 | 
						|
		p += resize_msg->len;
 | 
						|
		bufspace -= resize_msg->len;
 | 
						|
	}
 | 
						|
 | 
						|
	if (excuse)
 | 
						|
		r = snprintf(p, bufspace,
 | 
						|
"%ld.%06ld Latency %d printout skipped due to %s\n",
 | 
						|
			     sec, us, req->ticket, str);
 | 
						|
	else
 | 
						|
		r = snprintf(p, bufspace, "%ld.%06ld Latency %d detected\n",
 | 
						|
			    sec, us, req->ticket);
 | 
						|
 | 
						|
	if (r < 0)
 | 
						|
		warn("snprintf() failed");
 | 
						|
	else
 | 
						|
		bytes += r;
 | 
						|
 | 
						|
	/* These prints could happen concurrently */
 | 
						|
	mutex_lock(&print_mtx);
 | 
						|
	write_or_die(fd_stdout, buffer, bytes);
 | 
						|
	mutex_unlock(&print_mtx);
 | 
						|
}
 | 
						|
 | 
						|
static void print_skipmessage(const struct short_msg *resize_msg,
 | 
						|
			      const struct timespec *timestamp, char *buffer,
 | 
						|
			      size_t bufspace, const struct entry *req,
 | 
						|
			      bool excuse)
 | 
						|
{
 | 
						|
	__print_skipmessage(resize_msg, timestamp, buffer, bufspace, req,
 | 
						|
			    excuse, "random delay");
 | 
						|
}
 | 
						|
 | 
						|
static void print_lostmessage(const struct timespec *timestamp, char *buffer,
 | 
						|
			      size_t bufspace, const struct entry *req,
 | 
						|
			      const char *reason)
 | 
						|
{
 | 
						|
	__print_skipmessage(NULL, timestamp, buffer, bufspace, req, true,
 | 
						|
			    reason);
 | 
						|
}
 | 
						|
 | 
						|
static void print_tracefile(const struct short_msg *resize_msg,
 | 
						|
			    const struct timespec *timestamp, char *buffer,
 | 
						|
			    size_t bufspace, long slept,
 | 
						|
			    const struct entry *req)
 | 
						|
{
 | 
						|
	static const int reserve = 256;
 | 
						|
	char *p = &buffer[0];
 | 
						|
	ssize_t bytes = 0;
 | 
						|
	ssize_t bytes_tot = 0;
 | 
						|
	long us, sec;
 | 
						|
	long slept_ms;
 | 
						|
	int trace_fd;
 | 
						|
 | 
						|
	/* Save some space for the final string and final null char */
 | 
						|
	bufspace = bufspace - reserve - 1;
 | 
						|
 | 
						|
	if (resize_msg != NULL && resize_msg->len > 0) {
 | 
						|
		bytes = resize_msg->len;
 | 
						|
		strncpy(p, resize_msg->buf, bytes);
 | 
						|
		bytes_tot += bytes;
 | 
						|
		p += bytes;
 | 
						|
		bufspace -= bytes;
 | 
						|
	}
 | 
						|
 | 
						|
	trace_fd = open(debug_tracefile, O_RDONLY);
 | 
						|
 | 
						|
	if (trace_fd < 0) {
 | 
						|
		warn("open() failed on %s", debug_tracefile);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	sec = timestamp->tv_sec;
 | 
						|
	us = timestamp->tv_nsec / 1000;
 | 
						|
 | 
						|
	if (slept != 0) {
 | 
						|
		slept_ms = slept / 1000;
 | 
						|
		bytes = snprintf(p, bufspace,
 | 
						|
"%ld.%06ld Latency %d randomly sleep for %ld ms before print\n",
 | 
						|
				 sec, us, req->ticket, slept_ms);
 | 
						|
	} else {
 | 
						|
		bytes = snprintf(p, bufspace,
 | 
						|
				 "%ld.%06ld Latency %d immediate print\n", sec,
 | 
						|
				 us, req->ticket);
 | 
						|
	}
 | 
						|
 | 
						|
	if (bytes < 0) {
 | 
						|
		warn("snprintf() failed");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	p += bytes;
 | 
						|
	bufspace -= bytes;
 | 
						|
	bytes_tot += bytes;
 | 
						|
 | 
						|
	bytes = snprintf(p, bufspace,
 | 
						|
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> BEGIN <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"
 | 
						|
		);
 | 
						|
 | 
						|
	if (bytes < 0) {
 | 
						|
		warn("snprintf() failed");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	p += bytes;
 | 
						|
	bufspace -= bytes;
 | 
						|
	bytes_tot += bytes;
 | 
						|
 | 
						|
	do {
 | 
						|
		bytes = read(trace_fd, p, bufspace);
 | 
						|
		if (bytes < 0) {
 | 
						|
			if (errno == EINTR)
 | 
						|
				continue;
 | 
						|
			warn("read() failed on %s", debug_tracefile);
 | 
						|
			if (unlikely(close(trace_fd) != 0))
 | 
						|
				warn("close() failed on %s", debug_tracefile);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (bytes == 0)
 | 
						|
			break;
 | 
						|
		p += bytes;
 | 
						|
		bufspace -= bytes;
 | 
						|
		bytes_tot += bytes;
 | 
						|
	} while (true);
 | 
						|
 | 
						|
	if (unlikely(close(trace_fd) != 0))
 | 
						|
		warn("close() failed on %s", debug_tracefile);
 | 
						|
 | 
						|
	printstate_cnt_dec();
 | 
						|
	/* Add the reserve space back to the budget for the final string */
 | 
						|
	bufspace += reserve;
 | 
						|
 | 
						|
	bytes = snprintf(p, bufspace,
 | 
						|
			 ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
 | 
						|
 | 
						|
	if (bytes < 0) {
 | 
						|
		warn("snprintf() failed");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	bytes_tot += bytes;
 | 
						|
 | 
						|
	/* These prints could happen concurrently */
 | 
						|
	mutex_lock(&print_mtx);
 | 
						|
	write_or_die(fd_stdout, buffer, bytes_tot);
 | 
						|
	mutex_unlock(&print_mtx);
 | 
						|
}
 | 
						|
 | 
						|
static char *get_no_opt(const char *opt)
 | 
						|
{
 | 
						|
	char *no_opt;
 | 
						|
	int s;
 | 
						|
 | 
						|
	s = strlen(opt) + strlen(OPT_NO_PREFIX) + 1;
 | 
						|
	/* We may be called from cleanup_exit() via set_trace_opt() */
 | 
						|
	no_opt = malloc_or_die_nocleanup(s);
 | 
						|
	strcpy(no_opt, OPT_NO_PREFIX);
 | 
						|
	strcat(no_opt, opt);
 | 
						|
	return no_opt;
 | 
						|
}
 | 
						|
 | 
						|
static char *find_next_optstr(const char *allopt, const char **next)
 | 
						|
{
 | 
						|
	const char *begin;
 | 
						|
	const char *end;
 | 
						|
	char *r;
 | 
						|
	int s = 0;
 | 
						|
 | 
						|
	if (allopt == NULL)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	for (begin = allopt; *begin != '\0'; begin++) {
 | 
						|
		if (isgraph(*begin))
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	if (*begin == '\0')
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	for (end = begin; *end != '\0' && isgraph(*end); end++)
 | 
						|
		s++;
 | 
						|
 | 
						|
	r = malloc_or_die_nocleanup(s + 1);
 | 
						|
	strncpy(r, begin, s);
 | 
						|
	r[s] = '\0';
 | 
						|
	*next = begin + s;
 | 
						|
	return r;
 | 
						|
}
 | 
						|
 | 
						|
static bool get_trace_opt(const char *allopt, const char *opt, bool *found)
 | 
						|
{
 | 
						|
	*found = false;
 | 
						|
	char *no_opt;
 | 
						|
	char *str;
 | 
						|
	const char *next = allopt;
 | 
						|
	bool rval = false;
 | 
						|
 | 
						|
	no_opt = get_no_opt(opt);
 | 
						|
 | 
						|
	do {
 | 
						|
		str = find_next_optstr(next, &next);
 | 
						|
		if (str == NULL)
 | 
						|
			break;
 | 
						|
		if (!strcmp(str, opt)) {
 | 
						|
			*found = true;
 | 
						|
			rval = true;
 | 
						|
			free(str);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		if (!strcmp(str, no_opt)) {
 | 
						|
			*found = true;
 | 
						|
			rval = false;
 | 
						|
			free(str);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		free(str);
 | 
						|
	} while (true);
 | 
						|
	free(no_opt);
 | 
						|
 | 
						|
	return rval;
 | 
						|
}
 | 
						|
 | 
						|
static int set_trace_opt(const char *opt, bool value)
 | 
						|
{
 | 
						|
	char *str;
 | 
						|
	int r;
 | 
						|
 | 
						|
	if (value)
 | 
						|
		str = strdup(opt);
 | 
						|
	else
 | 
						|
		str = get_no_opt(opt);
 | 
						|
 | 
						|
	r = tracefs_instance_file_write(NULL, TR_OPTIONS, str);
 | 
						|
	free(str);
 | 
						|
	return r;
 | 
						|
}
 | 
						|
 | 
						|
void save_trace_opts(struct ftrace_state *state)
 | 
						|
{
 | 
						|
	char *allopt;
 | 
						|
	int psize;
 | 
						|
	int i;
 | 
						|
 | 
						|
	allopt = tracefs_instance_file_read(NULL, TR_OPTIONS, &psize);
 | 
						|
	if (!allopt)
 | 
						|
		errx(EXIT_FAILURE, "Failed to read the %s file\n", TR_OPTIONS);
 | 
						|
 | 
						|
	for (i = 0; i < OPTIDX_NR; i++)
 | 
						|
		state->opt[i] = get_trace_opt(allopt, optstr[i],
 | 
						|
					      &state->opt_valid[i]);
 | 
						|
 | 
						|
	free(allopt);
 | 
						|
}
 | 
						|
 | 
						|
static void write_file(const char *file, const char *cur, const char *new,
 | 
						|
		       enum errhandling h)
 | 
						|
{
 | 
						|
	int r;
 | 
						|
	static const char *emsg = "Failed to write to the %s file!";
 | 
						|
 | 
						|
	/* Do nothing if we now that the current and new value are equal */
 | 
						|
	if (cur && !needs_change(cur, new))
 | 
						|
		return;
 | 
						|
 | 
						|
	r = tracefs_instance_file_write(NULL, file, new);
 | 
						|
	if (r < 0) {
 | 
						|
		if (h) {
 | 
						|
			warnx(emsg, file);
 | 
						|
			if (h == ERR_CLEANUP)
 | 
						|
				cleanup_exit(EXIT_FAILURE);
 | 
						|
		} else
 | 
						|
			errx(EXIT_FAILURE, emsg, file);
 | 
						|
	}
 | 
						|
	if (verbose_ftrace()) {
 | 
						|
		mutex_lock(&print_mtx);
 | 
						|
		printf("%s was set to %s\n", file, new);
 | 
						|
		mutex_unlock(&print_mtx);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void reset_max_latency(void)
 | 
						|
{
 | 
						|
	write_file(TR_MAXLAT, NULL, "0", ERR_CLEANUP);
 | 
						|
}
 | 
						|
 | 
						|
static void save_and_disable_tracer(void)
 | 
						|
{
 | 
						|
	char *orig_th;
 | 
						|
	char *tracer;
 | 
						|
	bool need_nop = false;
 | 
						|
 | 
						|
	mutex_lock(&save_state.mutex);
 | 
						|
 | 
						|
	save_trace_opts(&save_state);
 | 
						|
	tracer = read_file(TR_CURRENT, ERR_EXIT);
 | 
						|
	orig_th = read_file(TR_THRESH, ERR_EXIT);
 | 
						|
 | 
						|
	if (needs_change(tracer, NOP_TRACER)) {
 | 
						|
		mutex_lock(&print_mtx);
 | 
						|
		if (force_tracer) {
 | 
						|
			printf(
 | 
						|
				"The %s tracer is already in use but proceeding anyway!\n",
 | 
						|
				tracer);
 | 
						|
		} else {
 | 
						|
			printf(
 | 
						|
				"The %s tracer is already in use, cowardly bailing out!\n"
 | 
						|
				"This could indicate that another program or instance is tracing.\n"
 | 
						|
				"Use the -F [--force] option to disregard the current tracer.\n", tracer);
 | 
						|
			exit(0);
 | 
						|
		}
 | 
						|
		mutex_unlock(&print_mtx);
 | 
						|
		need_nop = true;
 | 
						|
	}
 | 
						|
 | 
						|
	save_state.tracer =  tracer;
 | 
						|
	save_state.thresh = orig_th;
 | 
						|
 | 
						|
	if (need_nop)
 | 
						|
		write_file(TR_CURRENT, NULL, NOP_TRACER, ERR_EXIT);
 | 
						|
 | 
						|
	mutex_unlock(&save_state.mutex);
 | 
						|
}
 | 
						|
 | 
						|
void set_trace_opts(struct ftrace_state *state, bool *new)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	int r;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * We only set options if we earlier detected that the option exists in
 | 
						|
	 * the trace_options file and that the wanted setting is different from
 | 
						|
	 * the one we saw in save_and_disable_tracer()
 | 
						|
	 */
 | 
						|
	for (i = 0; i < OPTIDX_NR; i++)
 | 
						|
		if (state->opt_valid[i] &&
 | 
						|
		    state->opt[i] != new[i]) {
 | 
						|
			r = set_trace_opt(optstr[i], new[i]);
 | 
						|
			if (r < 0) {
 | 
						|
				warnx("Failed to set the %s option to %s",
 | 
						|
				      optstr[i], bool2str(new[i]));
 | 
						|
				cleanup_exit(EXIT_FAILURE);
 | 
						|
			}
 | 
						|
			if (verbose_ftrace()) {
 | 
						|
				mutex_lock(&print_mtx);
 | 
						|
				printf("%s in %s was set to %s\n", optstr[i],
 | 
						|
				       TR_OPTIONS, bool2str(new[i]));
 | 
						|
				mutex_unlock(&print_mtx);
 | 
						|
			}
 | 
						|
		}
 | 
						|
}
 | 
						|
 | 
						|
static void enable_tracer(void)
 | 
						|
{
 | 
						|
	mutex_lock(&save_state.mutex);
 | 
						|
	set_trace_opts(&save_state, use_options);
 | 
						|
 | 
						|
	write_file(TR_THRESH, save_state.thresh, threshold, ERR_CLEANUP);
 | 
						|
	write_file(TR_CURRENT, NOP_TRACER, current_tracer, ERR_CLEANUP);
 | 
						|
 | 
						|
	mutex_unlock(&save_state.mutex);
 | 
						|
}
 | 
						|
 | 
						|
static void tracing_loop(void)
 | 
						|
{
 | 
						|
	int ifd = inotify_init();
 | 
						|
	int wd;
 | 
						|
	const ssize_t bufsize = sizeof(inotify_buffer);
 | 
						|
	const ssize_t istructsize = sizeof(struct inotify_event);
 | 
						|
	char *buf = &inotify_buffer[0];
 | 
						|
	ssize_t nr_read;
 | 
						|
	char *p;
 | 
						|
	int modified;
 | 
						|
	struct inotify_event *event;
 | 
						|
	struct entry req;
 | 
						|
	char *buffer;
 | 
						|
	const size_t bufspace = PRINT_BUFFER_SIZE;
 | 
						|
	struct timespec timestamp;
 | 
						|
 | 
						|
	print_priority();
 | 
						|
 | 
						|
	buffer = malloc_or_die(bufspace);
 | 
						|
 | 
						|
	if (ifd < 0)
 | 
						|
		err(EXIT_FAILURE, "inotify_init() failed!");
 | 
						|
 | 
						|
 | 
						|
	if (setup_ftrace) {
 | 
						|
		/*
 | 
						|
		 * We must disable the tracer before resetting the max_latency
 | 
						|
		 */
 | 
						|
		save_and_disable_tracer();
 | 
						|
		/*
 | 
						|
		 * We must reset the max_latency before the inotify_add_watch()
 | 
						|
		 * call.
 | 
						|
		 */
 | 
						|
		reset_max_latency();
 | 
						|
	}
 | 
						|
 | 
						|
	wd = inotify_add_watch(ifd, debug_maxlat, IN_MODIFY);
 | 
						|
	if (wd < 0)
 | 
						|
		err(EXIT_FAILURE, "inotify_add_watch() failed!");
 | 
						|
 | 
						|
	if (setup_ftrace)
 | 
						|
		enable_tracer();
 | 
						|
 | 
						|
	signal_blocking(SIG_UNBLOCK);
 | 
						|
 | 
						|
	while (true) {
 | 
						|
		modified = 0;
 | 
						|
		check_signals();
 | 
						|
		nr_read = read(ifd, buf, bufsize);
 | 
						|
		check_signals();
 | 
						|
		if (nr_read < 0) {
 | 
						|
			if (errno == EINTR)
 | 
						|
				continue;
 | 
						|
			warn("read() failed on inotify fd!");
 | 
						|
			cleanup_exit(EXIT_FAILURE);
 | 
						|
		}
 | 
						|
		if (nr_read == bufsize)
 | 
						|
			warnx("inotify() buffer filled, skipping events");
 | 
						|
		if (nr_read < istructsize) {
 | 
						|
			warnx("read() returned too few bytes on inotify fd");
 | 
						|
			cleanup_exit(EXIT_FAILURE);
 | 
						|
		}
 | 
						|
 | 
						|
		for (p = buf; p < buf + nr_read;) {
 | 
						|
			event = (struct inotify_event *) p;
 | 
						|
			if ((event->mask & IN_MODIFY) != 0)
 | 
						|
				modified++;
 | 
						|
			p += istructsize + event->len;
 | 
						|
		}
 | 
						|
		while (modified > 0) {
 | 
						|
			check_signals();
 | 
						|
			mutex_lock(&printstate.mutex);
 | 
						|
			check_signals();
 | 
						|
			printstate_next_ticket(&req);
 | 
						|
			if (printstate_cnt_read() > 0) {
 | 
						|
				printstate_mark_req_completed(&req);
 | 
						|
				mutex_unlock(&printstate.mutex);
 | 
						|
				if (verbose_lostevent()) {
 | 
						|
					clock_gettime_or_die(CLOCK_MONOTONIC,
 | 
						|
							     ×tamp);
 | 
						|
					print_lostmessage(×tamp, buffer,
 | 
						|
							  bufspace, &req,
 | 
						|
							  "inotify loop");
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			mutex_unlock(&printstate.mutex);
 | 
						|
			if (queue_try_to_add_entry(&printqueue, &req) != 0) {
 | 
						|
				/* These prints could happen concurrently */
 | 
						|
				check_signals();
 | 
						|
				mutex_lock(&print_mtx);
 | 
						|
				check_signals();
 | 
						|
				write_or_die(fd_stdout, queue_full_warning,
 | 
						|
					     sizeof(queue_full_warning));
 | 
						|
				mutex_unlock(&print_mtx);
 | 
						|
			}
 | 
						|
			modified--;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void *do_printloop(void *arg)
 | 
						|
{
 | 
						|
	const size_t bufspace = PRINT_BUFFER_SIZE;
 | 
						|
	char *buffer;
 | 
						|
	long *rseed = (long *) arg;
 | 
						|
	struct drand48_data drandbuf;
 | 
						|
	long slept = 0;
 | 
						|
	struct entry req;
 | 
						|
	int prob = 0;
 | 
						|
	struct timespec timestamp;
 | 
						|
	struct short_msg resize_msg;
 | 
						|
 | 
						|
	print_priority();
 | 
						|
 | 
						|
	if (srand48_r(*rseed, &drandbuf) != 0) {
 | 
						|
		warn("srand48_r() failed!\n");
 | 
						|
		cleanup_exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
 | 
						|
	buffer = malloc_or_die(bufspace);
 | 
						|
 | 
						|
	while (true) {
 | 
						|
		req = queue_wait_for_entry(&printqueue);
 | 
						|
		clock_gettime_or_die(CLOCK_MONOTONIC, ×tamp);
 | 
						|
		mutex_lock(&printstate.mutex);
 | 
						|
		if (prev_req_won_race(&req)) {
 | 
						|
			printstate_mark_req_completed(&req);
 | 
						|
			mutex_unlock(&printstate.mutex);
 | 
						|
			if (verbose_lostevent())
 | 
						|
				print_lostmessage(×tamp, buffer, bufspace,
 | 
						|
						  &req, "print loop");
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		mutex_unlock(&printstate.mutex);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Toss a coin to decide if we want to sleep before printing
 | 
						|
		 * out the backtrace. The reason for this is that opening
 | 
						|
		 * /sys/kernel/debug/tracing/trace will cause a blackout of
 | 
						|
		 * hundreds of ms, where no latencies will be noted by the
 | 
						|
		 * latency tracer. Thus by randomly sleeping we try to avoid
 | 
						|
		 * missing traces systematically due to this. With this option
 | 
						|
		 * we will sometimes get the first latency, some other times
 | 
						|
		 * some of the later ones, in case of closely spaced traces.
 | 
						|
		 */
 | 
						|
		if (trace_enable && use_random_sleep) {
 | 
						|
			slept = 0;
 | 
						|
			prob = table_get_probability(&req, &resize_msg);
 | 
						|
			if (!toss_coin(&drandbuf, prob))
 | 
						|
				slept = go_to_sleep(&req);
 | 
						|
			if (slept >= 0) {
 | 
						|
				/* A print is ongoing */
 | 
						|
				printstate_cnt_inc();
 | 
						|
				/*
 | 
						|
				 * We will do the printout below so we have to
 | 
						|
				 * mark it as completed while we still have the
 | 
						|
				 * mutex.
 | 
						|
				 */
 | 
						|
				mutex_lock(&printstate.mutex);
 | 
						|
				printstate_mark_req_completed(&req);
 | 
						|
				mutex_unlock(&printstate.mutex);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (trace_enable) {
 | 
						|
			/*
 | 
						|
			 * slept < 0  means that we detected another
 | 
						|
			 * notification in go_to_sleep() above
 | 
						|
			 */
 | 
						|
			if (slept >= 0)
 | 
						|
				/*
 | 
						|
				 * N.B. printstate_cnt_dec(); will be called
 | 
						|
				 * inside print_tracefile()
 | 
						|
				 */
 | 
						|
				print_tracefile(&resize_msg, ×tamp, buffer,
 | 
						|
						bufspace, slept, &req);
 | 
						|
			else
 | 
						|
				print_skipmessage(&resize_msg, ×tamp,
 | 
						|
						  buffer, bufspace, &req, true);
 | 
						|
		} else {
 | 
						|
			print_skipmessage(&resize_msg, ×tamp, buffer,
 | 
						|
					  bufspace, &req, false);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static void start_printthread(void)
 | 
						|
{
 | 
						|
	unsigned int i;
 | 
						|
	long *seed;
 | 
						|
	int ufd;
 | 
						|
 | 
						|
	ufd = open(DEV_URANDOM, O_RDONLY);
 | 
						|
	if (nr_threads > MAX_THREADS) {
 | 
						|
		warnx(
 | 
						|
"Number of requested print threads was %d, max number is %d\n",
 | 
						|
		      nr_threads, MAX_THREADS);
 | 
						|
		nr_threads = MAX_THREADS;
 | 
						|
	}
 | 
						|
	for (i = 0; i < nr_threads; i++) {
 | 
						|
		seed = malloc_or_die(sizeof(*seed));
 | 
						|
		if (ufd <  0 ||
 | 
						|
		    read(ufd, seed, sizeof(*seed)) != sizeof(*seed)) {
 | 
						|
			printf(
 | 
						|
"Warning! Using trivial random number seed, since %s not available\n",
 | 
						|
			DEV_URANDOM);
 | 
						|
			fflush(stdout);
 | 
						|
			*seed = i;
 | 
						|
		}
 | 
						|
		errno = pthread_create(&printthread[i], NULL, do_printloop,
 | 
						|
				       seed);
 | 
						|
		if (errno)
 | 
						|
			err(EXIT_FAILURE, "pthread_create()");
 | 
						|
	}
 | 
						|
	if (ufd > 0 && close(ufd) != 0)
 | 
						|
		warn("close() failed");
 | 
						|
}
 | 
						|
 | 
						|
static void show_usage(void)
 | 
						|
{
 | 
						|
	printf(
 | 
						|
"Usage: %s [OPTION]...\n\n"
 | 
						|
"Collect closely occurring latencies from %s\n"
 | 
						|
"with any of the following tracers: preemptirqsoff, preemptoff, irqsoff, "
 | 
						|
"wakeup,\nwakeup_dl, or wakeup_rt.\n\n"
 | 
						|
 | 
						|
"The occurrence of a latency is detected by monitoring the file\n"
 | 
						|
"%s with inotify.\n\n"
 | 
						|
 | 
						|
"The following options are supported:\n\n"
 | 
						|
 | 
						|
"-l, --list\t\tList the latency tracers that are supported by the\n"
 | 
						|
"\t\t\tcurrently running Linux kernel. If you don't see the\n"
 | 
						|
"\t\t\ttracer that you want, you will probably need to\n"
 | 
						|
"\t\t\tchange your kernel config and build a new kernel.\n\n"
 | 
						|
 | 
						|
"-t, --tracer TR\t\tUse the tracer TR. The default is to use the first\n"
 | 
						|
"\t\t\ttracer that is supported by the kernel in the following\n"
 | 
						|
"\t\t\torder of precedence:\n\n"
 | 
						|
"\t\t\tpreemptirqsoff\n"
 | 
						|
"\t\t\tpreemptoff\n"
 | 
						|
"\t\t\tirqsoff\n"
 | 
						|
"\t\t\twakeup\n"
 | 
						|
"\t\t\twakeup_rt\n"
 | 
						|
"\t\t\twakeup_dl\n"
 | 
						|
"\n"
 | 
						|
"\t\t\tIf TR is not on the list above, then a warning will be\n"
 | 
						|
"\t\t\tprinted.\n\n"
 | 
						|
 | 
						|
"-F, --force\t\tProceed even if another ftrace tracer is active. Without\n"
 | 
						|
"\t\t\tthis option, the program will refuse to start tracing if\n"
 | 
						|
"\t\t\tany other tracer than the nop tracer is active.\n\n"
 | 
						|
 | 
						|
"-s, --threshold TH\tConfigure ftrace to use a threshold of TH microseconds\n"
 | 
						|
"\t\t\tfor the tracer. The default is 0, which means that\n"
 | 
						|
"\t\t\ttracing_max_latency will be used. tracing_max_latency is\n"
 | 
						|
"\t\t\tset to 0 when the program is started and contains the\n"
 | 
						|
"\t\t\tmaximum of the latencies that have been encountered.\n\n"
 | 
						|
 | 
						|
"-f, --function\t\tEnable the function-trace option in trace_options. With\n"
 | 
						|
"\t\t\tthis option, ftrace will trace the functions that are\n"
 | 
						|
"\t\t\texecuted during a latency, without it we only get the\n"
 | 
						|
"\t\t\tbeginning, end, and backtrace.\n\n"
 | 
						|
 | 
						|
"-g, --graph\t\tEnable the display-graph option in trace_option. This\n"
 | 
						|
"\t\t\toption causes ftrace to show the graph of how functions\n"
 | 
						|
"\t\t\tare calling other functions.\n\n"
 | 
						|
 | 
						|
"-c, --policy POL\tRun the program with scheduling policy POL. POL can be\n"
 | 
						|
"\t\t\tother, batch, idle, rr or fifo. The default is rr. When\n"
 | 
						|
"\t\t\tusing rr or fifo, remember that these policies may cause\n"
 | 
						|
"\t\t\tother tasks to experience latencies.\n\n"
 | 
						|
 | 
						|
"-p, --priority PRI\tRun the program with priority PRI. The acceptable range\n"
 | 
						|
"\t\t\tof PRI depends on the scheduling policy.\n\n"
 | 
						|
 | 
						|
"-n, --notrace\t\tIf latency is detected, do not print out the content of\n"
 | 
						|
"\t\t\tthe trace file to standard output\n\n"
 | 
						|
 | 
						|
"-t, --threads NRTHR\tRun NRTHR threads for printing. Default is %d.\n\n"
 | 
						|
 | 
						|
"-r, --random\t\tArbitrarily sleep a certain amount of time, default\n"
 | 
						|
"\t\t\t%ld ms, before reading the trace file. The\n"
 | 
						|
"\t\t\tprobabilities for sleep are chosen so that the\n"
 | 
						|
"\t\t\tprobability of obtaining any of a cluster of closely\n"
 | 
						|
"\t\t\toccurring latencies are equal, i.e. we will randomly\n"
 | 
						|
"\t\t\tchoose which one we collect from the trace file.\n\n"
 | 
						|
"\t\t\tThis option is probably only useful with the irqsoff,\n"
 | 
						|
"\t\t\tpreemptoff, and preemptirqsoff tracers.\n\n"
 | 
						|
 | 
						|
"-a, --nrlat NRLAT\tFor the purpose of arbitrary delay, assume that there\n"
 | 
						|
"\t\t\tare no more than NRLAT clustered latencies. If NRLAT\n"
 | 
						|
"\t\t\tlatencies are detected during a run, this value will\n"
 | 
						|
"\t\t\tautomatically be increased to NRLAT + 1 and then to\n"
 | 
						|
"\t\t\tNRLAT + 2 and so on. The default is %d. This option\n"
 | 
						|
"\t\t\timplies -r. We need to know this number in order to\n"
 | 
						|
"\t\t\tbe able to calculate the probabilities of sleeping.\n"
 | 
						|
"\t\t\tSpecifically, the probabilities of not sleeping, i.e. to\n"
 | 
						|
"\t\t\tdo an immediate printout will be:\n\n"
 | 
						|
"\t\t\t1/NRLAT  1/(NRLAT - 1) ... 1/3  1/2  1\n\n"
 | 
						|
"\t\t\tThe probability of sleeping will be:\n\n"
 | 
						|
"\t\t\t1 - P, where P is from the series above\n\n"
 | 
						|
"\t\t\tThis descending probability will cause us to choose\n"
 | 
						|
"\t\t\tan occurrence at random. Observe that the final\n"
 | 
						|
"\t\t\tprobability is 0, it is when we reach this probability\n"
 | 
						|
"\t\t\tthat we increase NRLAT automatically. As an example,\n"
 | 
						|
"\t\t\twith the default value of 2, the probabilities will be:\n\n"
 | 
						|
"\t\t\t1/2  0\n\n"
 | 
						|
"\t\t\tThis means, when a latency is detected we will sleep\n"
 | 
						|
"\t\t\twith 50%% probability. If we ever detect another latency\n"
 | 
						|
"\t\t\tduring the sleep period, then the probability of sleep\n"
 | 
						|
"\t\t\twill be 0%% and the table will be expanded to:\n\n"
 | 
						|
"\t\t\t1/3  1/2  0\n\n"
 | 
						|
 | 
						|
"-v, --verbose\t\tIncrease the verbosity. If this option is given once,\n"
 | 
						|
"\t\t\tthen print a message every time that the NRLAT value\n"
 | 
						|
"\t\t\tis automatically increased. It also causes a message to\n"
 | 
						|
"\t\t\tbe printed when the ftrace settings are changed. If this\n"
 | 
						|
"\t\t\toption is given at least twice, then also print a\n"
 | 
						|
"\t\t\twarning for lost events.\n\n"
 | 
						|
 | 
						|
"-u, --time TIME\t\tArbitrarily sleep for a specified time TIME ms before\n"
 | 
						|
"\t\t\tprinting out the trace from the trace file. The default\n"
 | 
						|
"\t\t\tis %ld ms. This option implies -r.\n\n"
 | 
						|
 | 
						|
"-x, --no-ftrace\t\tDo not configure ftrace. This assume that the user\n"
 | 
						|
"\t\t\tconfigures the ftrace files in sysfs such as\n"
 | 
						|
"\t\t\t/sys/kernel/tracing/current_tracer or equivalent.\n\n"
 | 
						|
 | 
						|
"-i, --tracefile FILE\tUse FILE as trace file. The default is\n"
 | 
						|
"\t\t\t%s.\n"
 | 
						|
"\t\t\tThis options implies -x\n\n"
 | 
						|
 | 
						|
"-m, --max-lat FILE\tUse FILE as tracing_max_latency file. The default is\n"
 | 
						|
"\t\t\t%s.\n"
 | 
						|
"\t\t\tThis options implies -x\n\n"
 | 
						|
,
 | 
						|
prg_name, debug_tracefile_dflt, debug_maxlat_dflt, DEFAULT_NR_PRINTER_THREADS,
 | 
						|
SLEEP_TIME_MS_DEFAULT, DEFAULT_TABLE_SIZE, SLEEP_TIME_MS_DEFAULT,
 | 
						|
debug_tracefile_dflt, debug_maxlat_dflt);
 | 
						|
}
 | 
						|
 | 
						|
static void find_tracefiles(void)
 | 
						|
{
 | 
						|
	debug_tracefile_dflt = tracefs_get_tracing_file("trace");
 | 
						|
	if (debug_tracefile_dflt == NULL) {
 | 
						|
		/* This is needed in show_usage() */
 | 
						|
		debug_tracefile_dflt = DEBUG_NOFILE;
 | 
						|
	}
 | 
						|
 | 
						|
	debug_maxlat_dflt = tracefs_get_tracing_file("tracing_max_latency");
 | 
						|
	if (debug_maxlat_dflt == NULL) {
 | 
						|
		/* This is needed in show_usage() */
 | 
						|
		debug_maxlat_dflt = DEBUG_NOFILE;
 | 
						|
	}
 | 
						|
 | 
						|
	debug_tracefile = debug_tracefile_dflt;
 | 
						|
	debug_maxlat = debug_maxlat_dflt;
 | 
						|
}
 | 
						|
 | 
						|
bool alldigits(const char *s)
 | 
						|
{
 | 
						|
	for (; *s != '\0'; s++)
 | 
						|
		if (!isdigit(*s))
 | 
						|
			return false;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void check_alldigits(const char *optarg, const char *argname)
 | 
						|
{
 | 
						|
	if (!alldigits(optarg))
 | 
						|
		errx(EXIT_FAILURE,
 | 
						|
		     "The %s parameter expects a decimal argument\n", argname);
 | 
						|
}
 | 
						|
 | 
						|
static void scan_arguments(int argc, char *argv[])
 | 
						|
{
 | 
						|
	int c;
 | 
						|
	int i;
 | 
						|
	int option_idx = 0;
 | 
						|
 | 
						|
	static struct option long_options[] = {
 | 
						|
		{ "list",       no_argument,            0, 'l' },
 | 
						|
		{ "tracer",	required_argument,	0, 't' },
 | 
						|
		{ "force",      no_argument,            0, 'F' },
 | 
						|
		{ "threshold",  required_argument,      0, 's' },
 | 
						|
		{ "function",   no_argument,            0, 'f' },
 | 
						|
		{ "graph",      no_argument,            0, 'g' },
 | 
						|
		{ "policy",	required_argument,	0, 'c' },
 | 
						|
		{ "priority",	required_argument,	0, 'p' },
 | 
						|
		{ "help",	no_argument,		0, 'h' },
 | 
						|
		{ "notrace",	no_argument,		0, 'n' },
 | 
						|
		{ "random",	no_argument,		0, 'r' },
 | 
						|
		{ "nrlat",	required_argument,	0, 'a' },
 | 
						|
		{ "threads",	required_argument,	0, 'e' },
 | 
						|
		{ "time",	required_argument,	0, 'u' },
 | 
						|
		{ "verbose",	no_argument,		0, 'v' },
 | 
						|
		{ "no-ftrace",  no_argument,            0, 'x' },
 | 
						|
		{ "tracefile",	required_argument,	0, 'i' },
 | 
						|
		{ "max-lat",	required_argument,	0, 'm' },
 | 
						|
		{ 0,		0,			0,  0  }
 | 
						|
	};
 | 
						|
	const struct policy *p;
 | 
						|
	int max, min;
 | 
						|
	int value;
 | 
						|
	bool notracer, valid;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * We must do this before parsing the arguments because show_usage()
 | 
						|
	 * needs to display these.
 | 
						|
	 */
 | 
						|
	find_tracefiles();
 | 
						|
 | 
						|
	while (true) {
 | 
						|
		c = getopt_long(argc, argv, "lt:Fs:fgc:p:hnra:e:u:vxi:m:",
 | 
						|
				long_options, &option_idx);
 | 
						|
		if (c == -1)
 | 
						|
			break;
 | 
						|
 | 
						|
		switch (c) {
 | 
						|
		case 'l':
 | 
						|
			show_available();
 | 
						|
			exit(0);
 | 
						|
			break;
 | 
						|
		case 't':
 | 
						|
			current_tracer = strdup(optarg);
 | 
						|
			if (!is_relevant_tracer(current_tracer)) {
 | 
						|
				warnx("%s is not a known latency tracer!\n",
 | 
						|
				      current_tracer);
 | 
						|
			}
 | 
						|
			valid = tracer_valid(current_tracer, ¬racer);
 | 
						|
			if (notracer)
 | 
						|
				errx(EXIT_FAILURE, no_tracer_msg);
 | 
						|
			if (!valid)
 | 
						|
				errx(EXIT_FAILURE,
 | 
						|
"The tracer %s is not supported by your kernel!\n", current_tracer);
 | 
						|
			break;
 | 
						|
		case 'F':
 | 
						|
			force_tracer = true;
 | 
						|
			break;
 | 
						|
		case 's':
 | 
						|
			check_alldigits(optarg, "-s [--threshold]");
 | 
						|
			threshold = strdup(optarg);
 | 
						|
			break;
 | 
						|
		case 'f':
 | 
						|
			use_options[OPTIDX_FUNC_TR] = true;
 | 
						|
			break;
 | 
						|
		case 'g':
 | 
						|
			use_options[OPTIDX_DISP_GR] = true;
 | 
						|
			break;
 | 
						|
		case 'c':
 | 
						|
			p = policy_from_name(optarg);
 | 
						|
			if (p != NULL) {
 | 
						|
				sched_policy = p->policy;
 | 
						|
				sched_policy_set = true;
 | 
						|
				if (!sched_pri_set) {
 | 
						|
					sched_pri = p->default_pri;
 | 
						|
					sched_pri_set = true;
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				warnx("Unknown scheduling %s\n", optarg);
 | 
						|
				show_usage();
 | 
						|
				exit(0);
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		case 'p':
 | 
						|
			check_alldigits(optarg, "-p [--priority]");
 | 
						|
			sched_pri = atoi(optarg);
 | 
						|
			sched_pri_set = true;
 | 
						|
			break;
 | 
						|
		case 'h':
 | 
						|
			show_usage();
 | 
						|
			exit(0);
 | 
						|
			break;
 | 
						|
		case 'n':
 | 
						|
			trace_enable = false;
 | 
						|
			use_random_sleep = false;
 | 
						|
			break;
 | 
						|
		case 'e':
 | 
						|
			check_alldigits(optarg, "-e [--threads]");
 | 
						|
			value = atoi(optarg);
 | 
						|
			if (value > 0)
 | 
						|
				nr_threads = value;
 | 
						|
			else {
 | 
						|
				warnx("NRTHR must be > 0\n");
 | 
						|
				show_usage();
 | 
						|
				exit(0);
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		case 'u':
 | 
						|
			check_alldigits(optarg, "-u [--time]");
 | 
						|
			value = atoi(optarg);
 | 
						|
			if (value < 0) {
 | 
						|
				warnx("TIME must be >= 0\n");
 | 
						|
				show_usage();
 | 
						|
				;
 | 
						|
			}
 | 
						|
			trace_enable = true;
 | 
						|
			use_random_sleep = true;
 | 
						|
			sleep_time = value * USEC_PER_MSEC;
 | 
						|
			break;
 | 
						|
		case 'v':
 | 
						|
			verbosity++;
 | 
						|
			break;
 | 
						|
		case 'r':
 | 
						|
			trace_enable = true;
 | 
						|
			use_random_sleep = true;
 | 
						|
			break;
 | 
						|
		case 'a':
 | 
						|
			check_alldigits(optarg, "-a [--nrlat]");
 | 
						|
			value = atoi(optarg);
 | 
						|
			if (value <= 0) {
 | 
						|
				warnx("NRLAT must be > 0\n");
 | 
						|
				show_usage();
 | 
						|
				exit(0);
 | 
						|
			}
 | 
						|
			trace_enable = true;
 | 
						|
			use_random_sleep = true;
 | 
						|
			table_startsize = value;
 | 
						|
			break;
 | 
						|
		case 'x':
 | 
						|
			setup_ftrace = false;
 | 
						|
			break;
 | 
						|
		case 'i':
 | 
						|
			setup_ftrace = false;
 | 
						|
			debug_tracefile = strdup(optarg);
 | 
						|
			break;
 | 
						|
		case 'm':
 | 
						|
			setup_ftrace = false;
 | 
						|
			debug_maxlat = strdup(optarg);
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			show_usage();
 | 
						|
			exit(0);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (setup_ftrace) {
 | 
						|
		if (!current_tracer) {
 | 
						|
			current_tracer = find_default_tracer();
 | 
						|
			if (!current_tracer)
 | 
						|
				errx(EXIT_FAILURE,
 | 
						|
"No default tracer found and tracer not specified\n");
 | 
						|
		}
 | 
						|
 | 
						|
		if (use_random_sleep && !random_makes_sense(current_tracer)) {
 | 
						|
			warnx("WARNING: The tracer is %s and random sleep has",
 | 
						|
			      current_tracer);
 | 
						|
			fprintf(stderr,
 | 
						|
"been enabled. Random sleep is intended for the following tracers:\n");
 | 
						|
			for (i = 0; random_tracers[i]; i++)
 | 
						|
				fprintf(stderr, "%s\n", random_tracers[i]);
 | 
						|
			fprintf(stderr, "\n");
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (debug_tracefile == DEBUG_NOFILE ||
 | 
						|
	    debug_maxlat == DEBUG_NOFILE)
 | 
						|
		errx(EXIT_FAILURE,
 | 
						|
"Could not find tracing directory e.g. /sys/kernel/tracing\n");
 | 
						|
 | 
						|
	if (!sched_policy_set) {
 | 
						|
		sched_policy = SCHED_RR;
 | 
						|
		sched_policy_set = true;
 | 
						|
		if (!sched_pri_set) {
 | 
						|
			sched_pri = RT_DEFAULT_PRI;
 | 
						|
			sched_pri_set = true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	max = sched_get_priority_max(sched_policy);
 | 
						|
	min = sched_get_priority_min(sched_policy);
 | 
						|
 | 
						|
	if (sched_pri < min) {
 | 
						|
		printf(
 | 
						|
"ATTENTION: Increasing priority to minimum, which is %d\n", min);
 | 
						|
		sched_pri = min;
 | 
						|
	}
 | 
						|
	if (sched_pri > max) {
 | 
						|
		printf(
 | 
						|
"ATTENTION: Reducing priority to maximum, which is %d\n", max);
 | 
						|
		sched_pri = max;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void show_params(void)
 | 
						|
{
 | 
						|
	printf(
 | 
						|
"\n"
 | 
						|
"Running with scheduling policy %s and priority %d. Using %d print threads.\n",
 | 
						|
		policy_name(sched_policy), sched_pri, nr_threads);
 | 
						|
	if (trace_enable) {
 | 
						|
		if (use_random_sleep) {
 | 
						|
			printf(
 | 
						|
"%s will be printed with random delay\n"
 | 
						|
"Start size of the probability table:\t\t\t%d\n"
 | 
						|
"Print a message when the prob. table changes size:\t%s\n"
 | 
						|
"Print a warning when an event has been lost:\t\t%s\n"
 | 
						|
"Sleep time is:\t\t\t\t\t\t%ld ms\n",
 | 
						|
debug_tracefile,
 | 
						|
table_startsize,
 | 
						|
bool2str(verbose_sizechange()),
 | 
						|
bool2str(verbose_lostevent()),
 | 
						|
sleep_time / USEC_PER_MSEC);
 | 
						|
		} else {
 | 
						|
			printf("%s will be printed immediately\n",
 | 
						|
			       debug_tracefile);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		printf("%s will not be printed\n",
 | 
						|
		       debug_tracefile);
 | 
						|
	}
 | 
						|
	if (setup_ftrace) {
 | 
						|
		printf("Tracer:\t\t\t\t\t\t\t%s\n"
 | 
						|
		       "%s option:\t\t\t\t\t%s\n"
 | 
						|
		       "%s option:\t\t\t\t\t%s\n",
 | 
						|
		       current_tracer,
 | 
						|
		       optstr[OPTIDX_FUNC_TR],
 | 
						|
		       bool2str(use_options[OPTIDX_FUNC_TR]),
 | 
						|
		       optstr[OPTIDX_DISP_GR],
 | 
						|
		       bool2str(use_options[OPTIDX_DISP_GR]));
 | 
						|
		if (!strcmp(threshold, "0"))
 | 
						|
			printf("Threshold:\t\t\t\t\t\ttracing_max_latency\n");
 | 
						|
		else
 | 
						|
			printf("Threshold:\t\t\t\t\t\t%s\n", threshold);
 | 
						|
	}
 | 
						|
	printf("\n");
 | 
						|
}
 | 
						|
 | 
						|
int main(int argc, char *argv[])
 | 
						|
{
 | 
						|
	init_save_state();
 | 
						|
	signal_blocking(SIG_BLOCK);
 | 
						|
	setup_sig_handler();
 | 
						|
	open_stdout();
 | 
						|
 | 
						|
	if (argc >= 1)
 | 
						|
		prg_name = argv[0];
 | 
						|
	else
 | 
						|
		prg_name = prg_unknown;
 | 
						|
 | 
						|
	scan_arguments(argc, argv);
 | 
						|
	show_params();
 | 
						|
 | 
						|
	init_printstate();
 | 
						|
	init_print_mtx();
 | 
						|
	if (use_random_sleep) {
 | 
						|
		init_probabilities();
 | 
						|
		if (verbose_sizechange())
 | 
						|
			printf("Initializing probability table to %d\n",
 | 
						|
			       table_startsize);
 | 
						|
		sleeptable_resize(table_startsize, false, NULL);
 | 
						|
	}
 | 
						|
	set_priority();
 | 
						|
	init_queue(&printqueue);
 | 
						|
	start_printthread();
 | 
						|
	tracing_loop();
 | 
						|
	return 0;
 | 
						|
}
 |