Skip to content
Permalink
Browse files
tracing: Add the osnoise tracer
In the context of high-performance computing (HPC), the Operating System
Noise (osnoise) refers to the interference experienced by an application
due to activities inside the operating system. In the context of Linux,
NMIs, IRQs, SoftIRQs, and any other system thread can cause noise to the
system. Moreover, hardware-related jobs can also cause noise, for example,
via SMIs.

hwlat_detector is one of the tools used to identify the most complex
source of noise: hardware noise.

In a nutshell, the hwlat_detector creates a thread that runs
periodically for a given period. At the beginning of a period, the thread
disables interrupt and starts sampling. While running, the hwlatd
thread reads the time in a loop. As interrupts are disabled, threads,
IRQs, and SoftIRQs cannot interfere with the hwlatd thread. Hence, the
cause of any gap between two different reads of the time roots either on
NMI or in the hardware itself. At the end of the period, hwlatd enables
interrupts and reports the max observed gap between the reads. It also
prints an NMI occurrence counter. If the output does not report NMI
executions, the user can conclude that the hardware is the culprit for
the latency. The hwlat detects the NMI execution by observing
the entry and exit of an NMI.

The osnoise tracer leverages the hwlat_detector by running a
similar loop with preemption, SoftIRQs and IRQs enabled, thus allowing
all the sources of osnoise during its execution. Using the same approach
of hwlat, osnoise takes note of the entry and exit point of any
source of interferences, increasing a per-cpu interference counter. The
osnoise tracer also saves an interference counter for each source of
interference. The interference counter for NMI, IRQs, SoftIRQs, and
threads is increased anytime the tool observes these interferences' entry
events. When a noise happens without any interference from the operating
system level, the hardware noise counter increases, pointing to a
hardware-related noise. In this way, osnoise can account for any
source of interference. At the end of the period, the osnoise tracer
prints the sum of all noise, the max single noise, the percentage of CPU
available for the thread, and the counters for the noise sources.

Usage

Write the ASCII text osnoise into the current_tracer file of the
tracing system (generally mounted at /sys/kernel/tracing or
/sys/kernel/debug/tracing).

For example::

        [root@f32 ~]# cd /sys/kernel/tracing/
        [root@f32 tracing]# echo osnoise > current_tracer

It is possible to follow the trace by reading the trace trace file::

        [root@f32 tracing]# cat trace
        # tracer: osnoise
        #
        #                                _-----=> irqs-off
        #                               / _----=> need-resched
        #                              | / _---=> hardirq/softirq
        #                              || / _--=> preempt-depth                            MAX
        #                              || /                                             SINGLE     Interference counters:
        #                              ||||               RUNTIME      NOISE   % OF CPU  NOISE    +-----------------------------+
        #           TASK-PID      CPU# ||||   TIMESTAMP    IN US       IN US  AVAILABLE  IN US     HW    NMI    IRQ   SIRQ THREAD
        #              | |         |   ||||      |           |             |    |            |      |      |      |      |      |
                   <...>-859     [000] ....    81.637220: 1000000        190  99.98100       9     18      0   1007     18      1
                   <...>-860     [001] ....    81.638154: 1000000        656  99.93440      74     23      0   1006     16      3
                   <...>-861     [002] ....    81.638193: 1000000       5675  99.43250     202      6      0   1013     25     21
                   <...>-862     [003] ....    81.638242: 1000000        125  99.98750      45      1      0   1011     23      0
                   <...>-863     [004] ....    81.638260: 1000000       1721  99.82790     168      7      0   1002     49     41
                   <...>-864     [005] ....    81.638286: 1000000        263  99.97370      57      6      0   1006     26      2
                   <...>-865     [006] ....    81.638302: 1000000        109  99.98910      21      3      0   1006     18      1
                   <...>-866     [007] ....    81.638326: 1000000       7816  99.21840     107      8      0   1016     39     19

In addition to the regular trace fields (from TASK-PID to TIMESTAMP), the
tracer prints a message at the end of each period for each CPU that is
running an osnoise/ thread. The osnoise specific fields report:

 - The RUNTIME IN USE reports the amount of time in microseconds that
   the osnoise thread kept looping reading the time.
 - The NOISE IN US reports the sum of noise in microseconds observed
   by the osnoise tracer during the associated runtime.
 - The % OF CPU AVAILABLE reports the percentage of CPU available for
   the osnoise thread during the runtime window.
 - The MAX SINGLE NOISE IN US reports the maximum single noise observed
   during the runtime window.
 - The Interference counters display how many each of the respective
   interference happened during the runtime window.

Note that the example above shows a high number of HW noise samples.
The reason being is that this sample was taken on a virtual machine,
and the host interference is detected as a hardware interference.

Tracer options

The tracer has a set of options inside the osnoise directory, they are:

 - cpus: CPUs at which a osnoise thread will execute.
 - period_us: the period of the osnoise thread.
 - runtime_us: how long an osnoise thread will look for noise.
 - stop_tracing_single_us: stop the system tracing of a single noise
   higher than the configured value is happens. Writing 0 disables this
   option.
 - stop_tracing_total_us: stop the system tracing of a NOISE IN USE
   higher than the configured value is happens. Writing 0 disables this
   option.
 - tolerance_ns: the minimum delta between two time() reads to be
   considered as noise.

Additional Tracing

In addition to the tracer, a set of tracepoints were added to
facilitate the identification of the osnoise source.

 - osnoise:sample_threshold: printed anytime a noise is higher than
   the configurable tolerance_ns.
 - osnoise:nmi_noise: noise from NMI, including the duration.
 - osnoise:irq_noise: noise from an IRQ, including the duration.
 - osnoise:softirq_noise: noise from a SoftIRQ, including the
   duration.
 - osnoise:thread_noise: noise from a thread, including the duration.

Note that a all the values are net values. This means that a thread
duration will not contain the duration of the IRQs that happened during
its execution, for example. The same is valid for all duration values.

Here is one example of the usage of these tracepoints::

       osnoise/8-961     [008] d.h.  5789.857532: irq_noise: local_timer:236 start 5789.857529929 duration 1845 ns
       osnoise/8-961     [008] dNh.  5789.858408: irq_noise: local_timer:236 start 5789.858404871 duration 2848 ns
     migration/8-54      [008] d...  5789.858413: thread_noise: migration/8:54 start 5789.858409300 duration 3068 ns
       osnoise/8-961     [008] ....  5789.858413: sample_threshold: start 5789.858404555 duration 8 us interferences 2

In this example, a noise sample of 8 microseconds was reported in the last
fine, pointing to two interferences. Looking backward in the trace, the
two previous entries were about the migration thread running after
a timer IRQ execution. The first event is not part of the noise because
it took place one millisecond before.

It is worth noticing that the sum of the duration reported in the
tracepoints is smaller than eight us reported in the
sample_threshold. The reason roots in the tracing overhead and in
the overhead of the entry and exit code that happens before and after
any interference execution. This justifies the dual approach: measuring
thread and tracing.

Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Alexandre Chartre <alexandre.chartre@oracle.com>
Cc: Clark Willaims <williams@redhat.com>
Cc: John Kacur <jkacur@redhat.com>
Cc: Juri Lelli <juri.lelli@redhat.com>
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Daniel Bristot de Oliveira <bristot@redhat.com>
  • Loading branch information
Daniel Bristot de Oliveira authored and intel-lab-lkp committed Apr 8, 2021
1 parent ee74580 commit 8fc23c7171dc536d3ce8c17fb66cfe6fcdbc5fb6
Show file tree
Hide file tree
Showing 9 changed files with 2,159 additions and 4 deletions.
@@ -0,0 +1,149 @@
==============
OSNOISE Tracer
==============

In the context of high-performance computing (HPC), the Operating System
Noise (*osnoise*) refers to the interference experienced by an application
due to activities inside the operating system. In the context of Linux,
NMIs, IRQs, SoftIRQs, and any other system thread can cause noise to the
system. Moreover, hardware-related jobs can also cause noise, for example,
via SMIs.

``hwlat_detector`` is one of the tools used to identify the most complex
source of noise: *hardware noise*.

In a nutshell, the ``hwlat_detector`` creates a thread that runs
periodically for a given period. At the beginning of a period, the thread
disables interrupt and starts sampling. While running, the ``hwlatd``
thread reads the time in a loop. As interrupts are disabled, threads,
IRQs, and SoftIRQs cannot interfere with the ``hwlatd`` thread. Hence, the
cause of any gap between two different reads of the time roots either on
NMI or in the hardware itself. At the end of the period, ``hwlatd`` enables
interrupts and reports the max observed gap between the reads. It also
prints a NMI occurrence counter. If the output does not report NMI
executions, the user can conclude that the hardware is the culprit for
the latency. The ``hwlat`` detects the NMI execution by observing
the entry and exit of a NMI.

The ``osnoise`` tracer leverages the ``hwlat_detector`` by running a
similar loop with preemption, SoftIRQs and IRQs enabled, thus allowing
all the sources of *osnoise* during its execution. Using the same approach
of ``hwlat``, ``osnoise`` takes note of the entry and exit point of any
source of interferences, increasing a per-cpu interference counter. The
``osnoise`` tracer also saves an interference counter for each source of
interference. The interference counter for NMI, IRQs, SoftIRQs, and
threads is increased anytime the tool observes these interferences' entry
events. When a noise happens without any interference from the operating
system level, the hardware noise counter increases, pointing to a
hardware-related noise. In this way, ``osnoise`` can account for any
source of interference. At the end of the period, the ``osnoise`` tracer
prints the sum of all noise, the max single noise, the percentage of CPU
available for the thread, and the counters for the noise sources.

Usage
-----

Write the ASCII text ``osnoise`` into the ``current_tracer`` file of the
tracing system (generally mounted at ``/sys/kernel/tracing`` or
``/sys/kernel/debug/tracing``).

For example::

[root@f32 ~]# cd /sys/kernel/tracing/
[root@f32 tracing]# echo osnoise > current_tracer

It is possible to follow the trace by reading the ``trace`` trace file::

[root@f32 tracing]# cat trace
# tracer: osnoise
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth MAX
# || / SINGLE Interference counters:
# |||| RUNTIME NOISE % OF CPU NOISE +-----------------------------+
# TASK-PID CPU# |||| TIMESTAMP IN US IN US AVAILABLE IN US HW NMI IRQ SIRQ THREAD
# | | | |||| | | | | | | | | | |
<...>-859 [000] .... 81.637220: 1000000 190 99.98100 9 18 0 1007 18 1
<...>-860 [001] .... 81.638154: 1000000 656 99.93440 74 23 0 1006 16 3
<...>-861 [002] .... 81.638193: 1000000 5675 99.43250 202 6 0 1013 25 21
<...>-862 [003] .... 81.638242: 1000000 125 99.98750 45 1 0 1011 23 0
<...>-863 [004] .... 81.638260: 1000000 1721 99.82790 168 7 0 1002 49 41
<...>-864 [005] .... 81.638286: 1000000 263 99.97370 57 6 0 1006 26 2
<...>-865 [006] .... 81.638302: 1000000 109 99.98910 21 3 0 1006 18 1
<...>-866 [007] .... 81.638326: 1000000 7816 99.21840 107 8 0 1016 39 19

In addition to the regular trace fields (from TASK-PID to TIMESTAMP), the
tracer prints a message at the end of each period for each CPU that is
running an ``osnoise/`` thread. The osnoise specific fields report:

- The ``RUNTIME IN USE`` reports the amount of time in microseconds that
the ``osnoise`` thread kept looping reading the time.
- The ``NOISE IN US`` reports the sum of noise in microseconds observed
by the osnoise tracer during the associated runtime.
- The ``% OF CPU AVAILABLE`` reports the percentage of CPU available for
the ``osnoise`` thread during the ``runtime`` window.
- The ``MAX SINGLE NOISE IN US`` reports the maximum single noise observed
during the ``runtime`` window.
- The ``Interference counters`` display how many each of the respective
interference happened during the ``runtime`` window.

Note that the example above shows a high number of ``HW noise`` samples.
The reason being is that this sample was taken on a virtual machine,
and the host interference is detected as a hardware interference.

Tracer options
---------------------

The tracer has a set of options inside the ``osnoise`` directory, they are:

- ``cpus``: CPUs at which a ``osnoise`` thread will execute.
- ``period_us``: the period of the ``osnoise`` thread.
- ``runtime_us``: how long an ``osnoise`` thread will look for noise.
- ``stop_tracing_single_us``: stop the system tracing of a single noise
higher than the configured value is happens. Writing ``0`` disables this
option.
- ``stop_tracing_total_us``: stop the system tracing of a ``NOISE IN USE``
higher than the configured value is happens. Writing ``0`` disables this
option.
- ``tolerance_ns``: the minimum delta between two time() reads to be
considered as noise.

Additional Tracing
------------------

In addition to the tracer, a set of ``tracepoints`` were added to
facilitate the identification of the osnoise source.

- ``osnoise:sample_threshold``: printed anytime a noise is higher than
the configurable ``tolerance_ns``.
- ``osnoise:nmi_noise``: noise from NMI, including the duration.
- ``osnoise:irq_noise``: noise from an IRQ, including the duration.
- ``osnoise:softirq_noise``: noise from a SoftIRQ, including the
duration.
- ``osnoise:thread_noise``: noise from a thread, including the duration.

Note that a all the values are *net values*. This means that a *thread*
duration will not contain the duration of the *IRQs* that happened during
its execution, for example. The same is valid for all duration values.

Here is one example of the usage of these ``tracepoints``::

osnoise/8-961 [008] d.h. 5789.857532: irq_noise: local_timer:236 start 5789.857529929 duration 1845 ns
osnoise/8-961 [008] dNh. 5789.858408: irq_noise: local_timer:236 start 5789.858404871 duration 2848 ns
migration/8-54 [008] d... 5789.858413: thread_noise: migration/8:54 start 5789.858409300 duration 3068 ns
osnoise/8-961 [008] .... 5789.858413: sample_threshold: start 5789.858404555 duration 8 us interferences 2

In this example, a noise sample of 8 microseconds was reported in the last
fine, pointing to two interferences. Looking backward in the trace, the
two previous entries were about the ``migration`` thread running after
a timer IRQ execution. The first event is not part of the noise because
it took place one millisecond before.

It is worth noticing that the sum of the duration reported in the
``tracepoints`` is smaller than eight us reported in the
``sample_threshold``. The reason roots in the tracing overhead and in
the overhead of the entry and exit code that happens before and after
any interference execution. This justifies the dual approach: measuring
thread and tracing.
@@ -7,12 +7,24 @@ extern bool trace_hwlat_callback_enabled;
extern void trace_hwlat_callback(bool enter);
#endif

/*
* XXX: Make it generic
*/
#ifdef CONFIG_OSNOISE_TRACER
extern bool trace_osnoise_callback_enabled;
extern void trace_osnoise_callback(bool enter);
#endif

static inline void ftrace_nmi_enter(void)
{
#ifdef CONFIG_HWLAT_TRACER
if (trace_hwlat_callback_enabled)
trace_hwlat_callback(true);
#endif
#ifdef CONFIG_OSNOISE_TRACER
if (trace_osnoise_callback_enabled)
trace_osnoise_callback(true);
#endif
}

static inline void ftrace_nmi_exit(void)
@@ -21,6 +33,10 @@ static inline void ftrace_nmi_exit(void)
if (trace_hwlat_callback_enabled)
trace_hwlat_callback(false);
#endif
#ifdef CONFIG_OSNOISE_TRACER
if (trace_osnoise_callback_enabled)
trace_osnoise_callback(false);
#endif
}

#endif /* _LINUX_FTRACE_IRQ_H */
@@ -0,0 +1,141 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM osnoise

#if !defined(_OSNOISE_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _OSNOISE_TRACE_H

#include <linux/tracepoint.h>
TRACE_EVENT(thread_noise,

TP_PROTO(struct task_struct *t, u64 start, u64 duration),

TP_ARGS(t, start, duration),

TP_STRUCT__entry(
__array( char, comm, TASK_COMM_LEN)
__field( pid_t, pid )
__field( u64, start )
__field( u64, duration)
),

TP_fast_assign(
memcpy(__entry->comm, t->comm, TASK_COMM_LEN);
__entry->pid = t->pid;
__entry->start = start;
__entry->duration = duration;
),

TP_printk("%8s:%d start %llu.%09u duration %llu ns",
__entry->comm,
__entry->pid,
__print_ns_to_secs(__entry->start),
__print_ns_without_secs(__entry->start),
__entry->duration)
);

TRACE_EVENT(softirq_noise,

TP_PROTO(int vector, u64 start, u64 duration),

TP_ARGS(vector, start, duration),

TP_STRUCT__entry(
__field( int, vector )
__field( u64, start )
__field( u64, duration)
),

TP_fast_assign(
__entry->vector = vector;
__entry->start = start;
__entry->duration = duration;
),

TP_printk("%8s:%d start %llu.%09u duration %llu ns",
show_softirq_name(__entry->vector),
__entry->vector,
__print_ns_to_secs(__entry->start),
__print_ns_without_secs(__entry->start),
__entry->duration)
);

TRACE_EVENT(irq_noise,

TP_PROTO(int vector, const char *desc, u64 start, u64 duration),

TP_ARGS(vector, desc, start, duration),

TP_STRUCT__entry(
__string( desc, desc )
__field( int, vector )
__field( u64, start )
__field( u64, duration)
),

TP_fast_assign(
__assign_str(desc, desc);
__entry->vector = vector;
__entry->start = start;
__entry->duration = duration;
),

TP_printk("%s:%d start %llu.%09u duration %llu ns",
__get_str(desc),
__entry->vector,
__print_ns_to_secs(__entry->start),
__print_ns_without_secs(__entry->start),
__entry->duration)
);

TRACE_EVENT(nmi_noise,

TP_PROTO(u64 start, u64 duration),

TP_ARGS(start, duration),

TP_STRUCT__entry(
__field( u64, start )
__field( u64, duration)
),

TP_fast_assign(
__entry->start = start;
__entry->duration = duration;
),

TP_printk("start %llu.%09u duration %llu ns",
__print_ns_to_secs(__entry->start),
__print_ns_without_secs(__entry->start),
__entry->duration)
);

TRACE_EVENT(sample_threshold,

TP_PROTO(u64 start, u64 duration, u64 interference),

TP_ARGS(start, duration, interference),

TP_STRUCT__entry(
__field( u64, start )
__field( u64, duration)
__field( u64, interference)
),

TP_fast_assign(
__entry->start = start;
__entry->duration = duration;
__entry->interference = interference;
),

TP_printk("start %llu.%09u duration %llu us interferences %llu",
__print_ns_to_secs(__entry->start),
__print_ns_without_secs(__entry->start),
__entry->duration,
__entry->interference)
);

#endif /* _TRACE_OSNOISE_H */

/* This part must be outside protection */
#include <trace/define_trace.h>
@@ -356,6 +356,40 @@ config HWLAT_TRACER
file. Every time a latency is greater than tracing_thresh, it will
be recorded into the ring buffer.

config OSNOISE_TRACER
bool "OS Noise tracer"
select GENERIC_TRACER
help
In the context of high-performance computing (HPC), the Operating
System Noise (osnoise) refers to the interference experienced by an
application due to activities inside the operating system. In the
context of Linux, NMIs, IRQs, SoftIRQs, and any other system thread
can cause noise to the system. Moreover, hardware-related jobs can
also cause noise, for example, via SMIs.

The osnoise tracer leverages the hwlat_detector by running a similar
loop with preemption, SoftIRQs and IRQs enabled, thus allowing all
the sources of osnoise during its execution. The osnoise tracer takes
note of the entry and exit point of any source of interferences,
increasing a per-cpu interference counter. It saves an interference
counter for each source of interference. The interference counter for
NMI, IRQs, SoftIRQs, and threads is increased anytime the tool
observes these interferences' entry events. When a noise happens
without any interference from the operating system level, the
hardware noise counter increases, pointing to a hardware-related
noise. In this way, osnoise can account for any source of
interference. At the end of the period, the osnoise tracer prints
the sum of all noise, the max single noise, the percentage of CPU
available for the thread, and the counters for the noise sources.

In addition to the tracer, a set of tracepoints were added to
facilitate the identification of the osnoise source.

The output will appear in the trace and trace_pipe files.

To enable this tracer, echo in "osnoise" into the current_tracer
file.

config MMIOTRACE
bool "Memory mapped IO tracing"
depends on HAVE_MMIOTRACE_SUPPORT && PCI
@@ -58,6 +58,7 @@ obj-$(CONFIG_IRQSOFF_TRACER) += trace_irqsoff.o
obj-$(CONFIG_PREEMPT_TRACER) += trace_irqsoff.o
obj-$(CONFIG_SCHED_TRACER) += trace_sched_wakeup.o
obj-$(CONFIG_HWLAT_TRACER) += trace_hwlat.o
obj-$(CONFIG_OSNOISE_TRACER) += trace_osnoise.o
obj-$(CONFIG_NOP_TRACER) += trace_nop.o
obj-$(CONFIG_STACK_TRACER) += trace_stack.o
obj-$(CONFIG_MMIOTRACE) += trace_mmiotrace.o

0 comments on commit 8fc23c7

Please sign in to comment.