/* * Information about the thread running when the IRQ * arrived. * * This can be blocking or interference, depending on the * priority of the thread. Assuming timerlat is the highest * prio, it is blocking. If timerlat has a lower prio, it is * interference. * note: "unsigned long long" because they are fetch using tep_get_field_val();
*/ unsignedlonglong run_thread_pid; char run_thread_comm[MAX_COMM]; unsignedlonglong thread_blocking_duration; unsignedlonglong max_exit_idle_latency;
/* Information about the timerlat timer irq */ unsignedlonglong timer_irq_start_time; unsignedlonglong timer_irq_start_delay; unsignedlonglong timer_irq_duration; unsignedlonglong timer_exit_from_idle;
/* * Information about the last IRQ before the timerlat irq * arrived. * * If now - timestamp is <= latency, it might have influenced * in the timerlat irq latency. Otherwise, ignore it.
*/ unsignedlonglong prev_irq_duration; unsignedlonglong prev_irq_timstamp;
/* * Current thread.
*/ char current_comm[MAX_COMM]; unsignedlonglong current_pid;
/* * Is the system running a kworker?
*/ unsignedlonglong kworker; unsignedlonglong kworker_func;
};
/* * The analysis context and system wide view
*/ struct timerlat_aa_context { int nr_cpus; int dump_tasks;
/* per CPU data */ struct timerlat_aa_data *taa_data;
/* * required to translate function names and register * events.
*/ struct osnoise_tool *tool;
};
/* * The data is stored as a local variable, but accessed via a helper function. * * It could be stored inside the trace context. But every access would * require container_of() + a series of pointers. Do we need it? Not sure. * * For now keep it simple. If needed, store it in the tool, add the *context * as a parameter in timerlat_aa_get_ctx() and do the magic there.
*/ staticstruct timerlat_aa_context *__timerlat_aa_ctx;
/* * timerlat_aa_get_data - Get the per-cpu data from the timerlat context
*/ staticstruct timerlat_aa_data
*timerlat_aa_get_data(struct timerlat_aa_context *taa_ctx, int cpu)
{ return &taa_ctx->taa_data[cpu];
}
/* * timerlat_aa_irq_latency - Handles timerlat IRQ event
*/ staticint timerlat_aa_irq_latency(struct timerlat_aa_data *taa_data, struct trace_seq *s, struct tep_record *record, struct tep_event *event)
{ /* * For interference, we start now looking for things that can delay * the thread.
*/
taa_data->curr_state = TIMERLAT_WAITING_THREAD;
taa_data->tlat_irq_timstamp = record->ts;
/* The thread that can cause blocking */
tep_get_common_field_val(s, event, "common_pid", record, &taa_data->run_thread_pid, 1);
/* * Get exit from idle case. * * If it is not idle thread:
*/ if (taa_data->run_thread_pid) return 0;
/* * if the latency is shorter than the known exit from idle:
*/ if (taa_data->tlat_irq_latency < taa_data->max_exit_idle_latency) return 0;
/* * To be safe, ignore the cases in which an IRQ/NMI could have * interfered with the timerlat IRQ.
*/ if (taa_data->tlat_irq_timstamp - taa_data->tlat_irq_latency
< taa_data->prev_irq_timstamp + taa_data->prev_irq_duration) return 0;
/* * timerlat_aa_thread_latency - Handles timerlat thread event
*/ staticint timerlat_aa_thread_latency(struct timerlat_aa_data *taa_data, struct trace_seq *s, struct tep_record *record, struct tep_event *event)
{ /* * For interference, we start now looking for things that can delay * the IRQ of the next cycle.
*/
taa_data->curr_state = TIMERLAT_WAITING_IRQ;
taa_data->tlat_thread_timstamp = record->ts;
/* * timerlat_aa_irq_handler - Handles IRQ noise * * It is used to collect information about interferences from IRQ. It is * hooked to the osnoise:irq_noise event. * * It is a little bit more complex than the other because it measures: * - The IRQs that can delay the timer IRQ before it happened. * - The Timerlat IRQ handler * - The IRQs that happened between the timerlat IRQ and the timerlat thread * (IRQ interference).
*/ staticint timerlat_aa_irq_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context)
{ struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx(); struct timerlat_aa_data *taa_data = timerlat_aa_get_data(taa_ctx, record->cpu); unsignedlonglong expected_start; unsignedlonglong duration; unsignedlonglong vector; unsignedlonglong start; char *desc; int val;
/* * The timerlat IRQ: taa_data->timer_irq_start_time is zeroed at * the timerlat irq handler.
*/ if (!taa_data->timer_irq_start_time) {
expected_start = taa_data->tlat_irq_timstamp - taa_data->tlat_irq_latency;
/* * We are dealing with two different clock sources: the * external clock source that timerlat uses as a reference * and the clock used by the tracer. There are also two * moments: the time reading the clock and the timer in * which the event is placed in the buffer (the trace * event timestamp). If the processor is slow or there * is some hardware noise, the difference between the * timestamp and the external clock read can be longer * than the IRQ handler delay, resulting in a negative * time. If so, set IRQ start delay as 0. In the end, * it is less relevant than the noise.
*/ if (expected_start < taa_data->timer_irq_start_time)
taa_data->timer_irq_start_delay = taa_data->timer_irq_start_time - expected_start; else
taa_data->timer_irq_start_delay = 0;
/* * not exit from idle.
*/ if (taa_data->run_thread_pid) return 0;
if (expected_start > taa_data->prev_irq_timstamp + taa_data->prev_irq_duration)
taa_data->timer_exit_from_idle = taa_data->timer_irq_start_delay;
/* * timerlat_aa_softirq_handler - Handles Softirq noise * * It is used to collect information about interferences from Softirq. It is * hooked to the osnoise:softirq_noise event. * * It is only printed in the non-rt kernel, as softirqs become thread on RT.
*/ staticint timerlat_aa_softirq_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context)
{ struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx(); struct timerlat_aa_data *taa_data = timerlat_aa_get_data(taa_ctx, record->cpu); unsignedlonglong duration; unsignedlonglong vector; unsignedlonglong start;
if (taa_data->curr_state == TIMERLAT_WAITING_IRQ) return 0;
/* * timerlat_aa_softirq_handler - Handles thread noise * * It is used to collect information about interferences from threads. It is * hooked to the osnoise:thread_noise event. * * Note: if you see thread noise, your timerlat thread was not the highest prio one.
*/ staticint timerlat_aa_thread_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context)
{ struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx(); struct timerlat_aa_data *taa_data = timerlat_aa_get_data(taa_ctx, record->cpu); unsignedlonglong duration; unsignedlonglong start; unsignedlonglong pid; constchar *comm; int val;
if (taa_data->curr_state == TIMERLAT_WAITING_IRQ) return 0;
trace_seq_printf(taa_data->stack_seq, " Blocking thread stack trace\n");
caller = tep_get_field_raw(s, event, "caller", record, &val, 1); if (caller) { for (i = 0; ; i++) {
function = tep_find_function(taa_ctx->tool->trace.tep, caller[i]); if (!function) break;
trace_seq_printf(taa_data->stack_seq, " %.*s -> %s\n",
14, spaces, function);
}
} return 0;
}
/* * timerlat_aa_sched_switch_handler - Tracks the current thread running on the CPU * * Handles the sched:sched_switch event to trace the current thread running on the * CPU. It is used to display the threads running on the other CPUs when the trace * stops.
*/ staticint timerlat_aa_sched_switch_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context)
{ struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx(); struct timerlat_aa_data *taa_data = timerlat_aa_get_data(taa_ctx, record->cpu); constchar *comm; int val;
/* * If this was a kworker, clean the last kworkers that ran.
*/
taa_data->kworker = 0;
taa_data->kworker_func = 0;
return 0;
}
/* * timerlat_aa_kworker_start_handler - Tracks a kworker running on the CPU * * Handles workqueue:workqueue_execute_start event, keeping track of * the job that a kworker could be doing in the CPU. * * We already catch problems of hardware related latencies caused by work queues * running driver code that causes hardware stall. For example, with DRM drivers.
*/ staticint timerlat_aa_kworker_start_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context)
{ struct timerlat_aa_context *taa_ctx = timerlat_aa_get_ctx(); struct timerlat_aa_data *taa_data = timerlat_aa_get_data(taa_ctx, record->cpu);
/* * timerlat_thread_analysis - Prints the analysis of a CPU that hit a stop tracing * * This is the core of the analysis.
*/ staticvoid timerlat_thread_analysis(struct timerlat_aa_data *taa_data, int cpu, int irq_thresh, int thread_thresh)
{ longlong exp_irq_ts; int total; int irq;
/* * IRQ latency or Thread latency?
*/ if (taa_data->tlat_irq_seqnum > taa_data->tlat_thread_seqnum) {
irq = 1;
total = taa_data->tlat_irq_latency;
} else {
irq = 0;
total = taa_data->tlat_thread_latency;
}
/* * Expected IRQ arrival time using the trace clock as the base. * * TODO: Add a list of previous IRQ, and then run the list backwards.
*/
exp_irq_ts = taa_data->timer_irq_start_time - taa_data->timer_irq_start_delay; if (exp_irq_ts < taa_data->prev_irq_timstamp + taa_data->prev_irq_duration) { if (taa_data->prev_irq_timstamp < taa_data->timer_irq_start_time)
printf(" Previous IRQ interference: %.*s up to %9.2f us\n",
16, spaces,
ns_to_usf(taa_data->prev_irq_duration));
}
/* * The delay that the IRQ suffered before starting.
*/
printf(" IRQ handler delay: %.*s %16s %9.2f us (%.2f %%)\n", 16, spaces,
(ns_to_usf(taa_data->timer_exit_from_idle) > 10) ? "(exit from idle)" : "",
ns_to_usf(taa_data->timer_irq_start_delay),
ns_to_per(total, taa_data->timer_irq_start_delay));
if (irq) { /* * If the trace stopped due to IRQ, the other events will not happen * because... the trace stopped :-). * * That is all folks, the stack trace was printed before the stop, * so it will be displayed, it is the key.
*/
printf(" Blocking thread:\n");
printf(" %.*s %24s:%-9llu\n", 6, spaces, taa_data->run_thread_comm,
taa_data->run_thread_pid);
} else { /* * The duration of the IRQ handler that handled the timerlat IRQ.
*/
printf(" Timerlat IRQ duration: %.*s %9.2f us (%.2f %%)\n",
30, spaces,
ns_to_usf(taa_data->timer_irq_duration),
ns_to_per(total, taa_data->timer_irq_duration));
/* * The amount of time that the current thread postponed the scheduler. * * Recalling that it is net from NMI/IRQ/Softirq interference, so there * is no need to compute values here.
*/
printf(" Blocking thread: %.*s %9.2f us (%.2f %%)\n", 36, spaces,
ns_to_usf(taa_data->thread_blocking_duration),
ns_to_per(total, taa_data->thread_blocking_duration));
/* * Print the stack trace!
*/
trace_seq_do_printf(taa_data->stack_seq);
/* * NMIs can happen during the IRQ, so they are always possible.
*/ if (taa_data->thread_nmi_sum)
printf(" NMI interference %.*s %9.2f us (%.2f %%)\n", 36, spaces,
ns_to_usf(taa_data->thread_nmi_sum),
ns_to_per(total, taa_data->thread_nmi_sum));
/* * If it is an IRQ latency, the other factors can be skipped.
*/ if (irq) goto print_total;
/* * Prints the interference caused by IRQs to the thread latency.
*/ if (taa_data->thread_irq_sum) {
printf(" IRQ interference %.*s %9.2f us (%.2f %%)\n", 36, spaces,
ns_to_usf(taa_data->thread_irq_sum),
ns_to_per(total, taa_data->thread_irq_sum));
trace_seq_do_printf(taa_data->irqs_seq);
}
/* * Prints the interference caused by Softirqs to the thread latency.
*/ if (taa_data->thread_softirq_sum) {
printf(" Softirq interference %.*s %9.2f us (%.2f %%)\n", 32, spaces,
ns_to_usf(taa_data->thread_softirq_sum),
ns_to_per(total, taa_data->thread_softirq_sum));
trace_seq_do_printf(taa_data->softirqs_seq);
}
/* * Prints the interference caused by other threads to the thread latency. * * If this happens, your timerlat is not the highest prio. OK, migration * thread can happen. But otherwise, you are not measuring the "scheduling * latency" only, and here is the difference from scheduling latency and * timer handling latency.
*/ if (taa_data->thread_thread_sum) {
printf(" Thread interference %.*s %9.2f us (%.2f %%)\n", 33, spaces,
ns_to_usf(taa_data->thread_thread_sum),
ns_to_per(total, taa_data->thread_thread_sum));
if (max_exit_from_idle) {
printf("\n");
printf("Max timerlat IRQ latency from idle: %.2f us in cpu %d\n",
ns_to_usf(max_exit_from_idle), max_exit_from_idle_cpu);
} if (!taa_ctx->dump_tasks) return;
printf("\n");
printf("Printing CPU tasks:\n"); for (cpu = 0; cpu < taa_ctx->nr_cpus; cpu++) {
taa_data = timerlat_aa_get_data(taa_ctx, cpu);
tep = taa_ctx->tool->trace.tep;
/* * timerlat_aa_destroy_seqs - Destroy seq files used to store parsed data
*/ staticvoid timerlat_aa_destroy_seqs(struct timerlat_aa_context *taa_ctx)
{ struct timerlat_aa_data *taa_data; int i;
if (!taa_ctx->taa_data) return;
for (i = 0; i < taa_ctx->nr_cpus; i++) {
taa_data = timerlat_aa_get_data(taa_ctx, i);
if (taa_data->prev_irqs_seq) {
trace_seq_destroy(taa_data->prev_irqs_seq);
free(taa_data->prev_irqs_seq);
}
if (taa_data->nmi_seq) {
trace_seq_destroy(taa_data->nmi_seq);
free(taa_data->nmi_seq);
}
if (taa_data->irqs_seq) {
trace_seq_destroy(taa_data->irqs_seq);
free(taa_data->irqs_seq);
}
if (taa_data->softirqs_seq) {
trace_seq_destroy(taa_data->softirqs_seq);
free(taa_data->softirqs_seq);
}
if (taa_data->threads_seq) {
trace_seq_destroy(taa_data->threads_seq);
free(taa_data->threads_seq);
}
if (taa_data->stack_seq) {
trace_seq_destroy(taa_data->stack_seq);
free(taa_data->stack_seq);
}
}
}
/* * timerlat_aa_init_seqs - Init seq files used to store parsed information * * Instead of keeping data structures to store raw data, use seq files to * store parsed data. * * Allocates and initialize seq files. * * Returns 0 on success, -1 otherwise.
*/ staticint timerlat_aa_init_seqs(struct timerlat_aa_context *taa_ctx)
{ struct timerlat_aa_data *taa_data; int i;
for (i = 0; i < taa_ctx->nr_cpus; i++) {
taa_data = timerlat_aa_get_data(taa_ctx, i);
taa_data->prev_irqs_seq = calloc(1, sizeof(*taa_data->prev_irqs_seq)); if (!taa_data->prev_irqs_seq) goto out_err;
trace_seq_init(taa_data->prev_irqs_seq);
taa_data->nmi_seq = calloc(1, sizeof(*taa_data->nmi_seq)); if (!taa_data->nmi_seq) goto out_err;
trace_seq_init(taa_data->nmi_seq);
taa_data->irqs_seq = calloc(1, sizeof(*taa_data->irqs_seq)); if (!taa_data->irqs_seq) goto out_err;
trace_seq_init(taa_data->irqs_seq);
taa_data->softirqs_seq = calloc(1, sizeof(*taa_data->softirqs_seq)); if (!taa_data->softirqs_seq) goto out_err;
trace_seq_init(taa_data->softirqs_seq);
taa_data->threads_seq = calloc(1, sizeof(*taa_data->threads_seq)); if (!taa_data->threads_seq) goto out_err;
trace_seq_init(taa_data->threads_seq);
taa_data->stack_seq = calloc(1, sizeof(*taa_data->stack_seq)); if (!taa_data->stack_seq) goto out_err;
/* * timerlat_aa_unregister_events - Unregister events used in the auto-analysis
*/ staticvoid timerlat_aa_unregister_events(struct osnoise_tool *tool, int dump_tasks)
{
/* * timerlat_aa_register_events - Register events used in the auto-analysis * * Returns 0 on success, -1 otherwise.
*/ staticint timerlat_aa_register_events(struct osnoise_tool *tool, int dump_tasks)
{ int retval;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.