| // 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(); |
| exit(0); |
| } |
| 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; |
| } |