osnoise/timerlat improvements
Subsystems (3)
v3 cover osnoise/timerlat improvements
These three patches are improvements for the osnoise/timerlat tracers, mainly to support rtla. The first one is a dependency for the -C <cgroup> command line option to be added to rtla. It is essential to allow timerlat/osnoise to measure the latencies from the container/cgroup point of view. The second improves rtla time for saving tracing after a stop tracing condition on osnoise/hwnoise.
The third patch adds the osnoise/per_cpu/cpu$id/timerlat_fd file where timerlat expects a pinned user thread to open and read from it. When the thread reads it, it actually activates timerlat latency measurement mechanism, so timerlat can be activated from an user-space thread. Timerlat is also expanded to report the return from user-space latency when read is called after the first call, so adding another metric to timerlat, that can either represent the kernel-to-user and user-to-kernel overhead, or to measure the execution time of any workload. Details are added to the kernel documentation.
Changes from V2:
- Moved the migration check to a function hooked to the sched:sched_migration_task tracepoint
- Fixed compilation errors with !CONFIG_TIMERLAT_TRACER (kernel test robot)
- Made osnoise_create_cpu_timerlat_fd static (kernel test robot)
- Link: https://lore.kernel.org/lkml/[email protected]/
Changes from V1:
- Added the user-space interface patch
- Link: https://lore.kernel.org/lkml/[email protected]/
Daniel Bristot de Oliveira (3):
tracing/osnoise: Switch from PF_NO_SETAFFINITY to migrate_disable tracing/osnoise: Skip running osnoise if all instances are off tracing/timerlat: Add user-space interface
Documentation/trace/timerlat-tracer.rst | 78 ++++ kernel/trace/trace_osnoise.c | 477 +++++++++++++++++++++++- kernel/trace/trace_output.c | 4 +- 3 files changed, 551 insertions(+), 8 deletions(-)
Signature · 1 line
2.38.1
PATCH v3 1/3 tracing/osnoise: Switch from PF_NO_SETAFFINITY to migrate_disable
Currently, osnoise/timerlat threads run with PF_NO_SETAFFINITY set. It works well, however, cgroups do not allow PF_NO_SETAFFINITY threads to be accepted, and this creates a limitation to osnoise/timerlat.
To avoid this limitation, disable migration of the threads as soon as they start to run, and then clean the PF_NO_SETAFFINITY flag (still) used during thread creation.
If for some reason a thread migration is requested, e.g., via sched_settafinity, the tracer thread will notice and exit.
--- kernel/trace/trace_osnoise.c | 68 ++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+)
kernel/trace/trace_osnoise.c +68 -0
@@ -1545,6 +1545,39 @@ static void osnoise_sleep(void) } } +/* +* osnoise_migration_pending - checks if the task needs to migrate +* +* osnoise/timerlat threads are per-cpu. If there is a pending request to +* migrate the thread away from the current CPU, something bad has happened. +* Play the good citizen and leave. +* +* Returns 0 if it is safe to continue, 1 otherwise. +*/ +static inline int osnoise_migration_pending (void ) +{ +if (!current->migration_pending) +return 0 ; + +/* +* If migration is pending, there is a task waiting for the +* tracer to enable migration. The tracer does not allow migration, +* thus: taint and leave to unblock the blocked thread. +*/ +osnoise_taint ("migration requested to osnoise threads, leaving." ); + +/* +* Unset this thread from the threads managed by the interface. +* The tracers are responsible for cleaning their env before +* exiting. +*/ +mutex_lock (&interface_lock); +this_cpu_osn_var ()->kthread =NULL ; +mutex_unlock (&interface_lock); + +return 1 ; +} +/* * osnoise_main - The osnoise detection kernel thread *
@@ -1553,12 +1586,29 @@ static void osnoise_sleep(void)*/ static int osnoise_main (void *data) { +unsigned long flags; + +/* +* This thread was created pinned to the CPU using PF_NO_SETAFFINITY. +* The problem is that cgroup does not allow PF_NO_SETAFFINITY thread. +* +* To work around this limitation, disable migration and remove the +* flag. +*/ +migrate_disable (); +raw_spin_lock_irqsave (¤t->pi_lock, flags); + current->flags &= ~(PF_NO_SETAFFINITY ); +raw_spin_unlock_irqrestore (¤t->pi_lock, flags);while (!kthread_should_stop ()) { +if (osnoise_migration_pending ()) +break ; +run_osnoise ();osnoise_sleep (); } +migrate_enable ();return 0 ; }
@@ -1706,6 +1756,7 @@ static int timerlat_main(void *data)struct timerlat_variables *tlat =this_cpu_tmr_var ();struct timerlat_sample s;struct sched_param sp; +unsigned long flags;u64 now, diff;/*
@@ -1714,6 +1765,18 @@ static int timerlat_main(void *data) sp.sched_priority =DEFAULT_TIMERLAT_PRIO ;sched_setscheduler_nocheck (current,SCHED_FIFO , &sp); +/* +* This thread was created pinned to the CPU using PF_NO_SETAFFINITY. +* The problem is that cgroup does not allow PF_NO_SETAFFINITY thread. +* +* To work around this limitation, disable migration and remove the +* flag. +*/ +migrate_disable (); +raw_spin_lock_irqsave (¤t->pi_lock, flags); + current->flags &= ~(PF_NO_SETAFFINITY ); +raw_spin_unlock_irqrestore (¤t->pi_lock, flags); + tlat->count =0 ; tlat->tracing_thread = false;
@@ -1731,6 +1794,7 @@ static int timerlat_main(void *data) osn_var->sampling =1 ;while (!kthread_should_stop ()) { + now =ktime_to_ns (hrtimer_cb_get_time (&tlat->timer)); diff = now - tlat->abs_period;
@@ -1749,10 +1813,14 @@ static int timerlat_main(void *data)if (time_to_us (diff) >= osnoise_data.stop_tracing_total)osnoise_stop_tracing (); +if (osnoise_migration_pending ()) +break ; + wait_next_period(tlat); }hrtimer_cancel (&tlat->timer); +migrate_enable ();return 0 ; }#else /* CONFIG_TIMERLAT_TRACER */
Signature · 1 line
2.38.1
PATCH v3 2/3 tracing/osnoise: Skip running osnoise if all instances are off
In reply to Daniel Bristot de OliveiraIn the case of all tracing instances being off, sleep for the entire period.
Q: Why not kill all threads so?
A: It is valid and useful to start the threads with tracing off.
For example, rtla disables tracing, starts the tracer, applies the scheduling setup to the threads, e.g., sched priority and cgroup, and then begin tracing with all set.
Skipping the period helps to speed up rtla setup and save the trace after a stop tracing.
--- kernel/trace/trace_osnoise.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-)
kernel/trace/trace_osnoise.c +28 -3
@@ -1285,6 +1285,22 @@ static __always_inline void osnoise_stop_tracing(void)rcu_read_unlock (); } +/* +* osnoise_has_tracing_on - Check if there is at least one instance on +*/ +static __always_inline int osnoise_has_tracing_on(void) +{ +struct osnoise_instance *inst; +int trace_is_on =0 ; + +rcu_read_lock (); +list_for_each_entry_rcu (inst, &osnoise_instances, list) + trace_is_on +=tracer_tracing_is_on (inst->tr); +rcu_read_unlock (); + +return trace_is_on; +} +/* * notify_new_max_latency - Notify a new max latency via fsnotify interface. */
@@ -1517,13 +1533,16 @@ static struct cpumask save_cpumask;/* * osnoise_sleep - sleep until the next period */ -static void osnoise_sleep (void ) +static void osnoise_sleep (bool skip_period) {u64 interval;ktime_t wake_time;mutex_lock (&interface_lock); - interval = osnoise_data.sample_period - osnoise_data.sample_runtime; +if (skip_period) + interval = osnoise_data.sample_period; +else + interval = osnoise_data.sample_period - osnoise_data.sample_runtime;mutex_unlock (&interface_lock);/*
@@ -1604,8 +1623,14 @@ static int osnoise_main(void *data)if (osnoise_migration_pending ())break ; +/* skip a period if tracing is off on all instances */ +if (!osnoise_has_tracing_on ()) { +osnoise_sleep (true); +continue ; + } +run_osnoise (); -osnoise_sleep (); +osnoise_sleep (false); }migrate_enable ();
Signature · 1 line
2.38.1
PATCH v3 3/3 tracing/timerlat: Add user-space interface
In reply to Daniel Bristot de OliveiraGoing a step further, we propose a way to use any user-space workload as the task waiting for the timerlat timer. This is done via a per-CPU file named osnoise/cpu$id/timerlat_fd file.
The tracef_fd allows a task to open at a time. When a task reads the file, the timerlat timer is armed for future osnoise/timerlat_period_us time. When the timer fires, it prints the IRQ latency and
wakes up the user-space thread waiting in the timerlat_fd.
The thread then starts to run, executes the timerlat measurement, prints the thread scheduling latency and returns to user-space.
When the thread rereads the timerlat_fd, the tracer will print the user-ret(urn) latency, which is an additional metric.
This additional metric is also traced by the tracer and can be used, for example of measuring the context switch overhead from kernel-to-user and user-to-kernel, or the response time for an arbitrary execution in user-space.
The tracer supports one thread per CPU, the thread must be pinned to the CPU, and it cannot migrate while holding the timerlat_fd. The reason is that the tracer is per CPU (nothing prohibits the tracer from allowing migrations in the future). The tracer monitors the migration of the thread and disables the tracer if detected.
The timerlat_fd is only available for opening/reading when timerlat tracer is enabled, and NO_OSNOISE_WORKLOAD is set.
The simplest way to activate this feature from user-space is:
-------------------------------- %< ----------------------------------- int main(void)
{
char buffer[1024];
int timerlat_fd;
int retval;
long cpu = 0; /* place in CPU 0 */
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
if (sched_setaffinity(gettid(), sizeof(set), &set) == -1)
return 1;
snprintf(buffer, sizeof(buffer),
"/sys/kernel/tracing/osnoise/per_cpu/cpu%ld/timerlat_fd",
cpu);
timerlat_fd = open(buffer, O_RDONLY);
if (timerlat_fd < 0) {
printf("error opening %s: %s\n", buffer, strerror(errno));
exit(1);
}
for (;;) {
retval = read(timerlat_fd, buffer, 1024);
if (retval < 0)
break;
}
close(timerlat_fd);
exit(0);
}-------------------------------- >% -----------------------------------
When disabling timerlat, if there is a workload holding the timerlat_fd, the SIGKILL will be sent to the thread.
--- Documentation/trace/timerlat-tracer.rst | 78 +++++ kernel/trace/trace_osnoise.c | 378 +++++++++++++++++++++++- kernel/trace/trace_output.c | 4 +- 3 files changed, 455 insertions(+), 5 deletions(-)
Documentation/trace/timerlat-tracer.rst +78 -0
@@ -180,3 +180,81 @@ dummy_load_1ms_pd_init, which had the following code (on purpose):: return 0; } + +User-space interface +--------------------------- + +Timerlat allows user-space threads to use timerlat infra-structure to +measure scheduling latency. This interface is accessible via a per-CPU +file descriptor inside $tracing_dir/osnoise/per_cpu/cpu$ID/timerlat_fd. + +This interface is accessible under the following conditions: + + - timerlat tracer is enable + - osnoise workload option is set to NO_OSNOISE_WORKLOAD + - The user-space thread is affined to a single processor + - The thread opens the file associated with its single processor + - Only one thread can access the file at a time + +The open() syscall will fail if any of these conditions are not met. +After opening the file descriptor, the user space can read from it. + +The read() system call will run a timerlat code that will arm the +timer in the future and wait for it as the regular kernel thread does. + +When the timer IRQ fires, the timerlat IRQ will execute, report the +IRQ latency and wake up the thread waiting in the read. The thread will be +scheduled and report the thread latency via tracer - as for the kernel +thread. + +The difference from the in-kernel timerlat is that, instead of re-arming +the timer, timerlat will return to the read() system call. At this point, +the user can run any code. + +If the application rereads the file timerlat file descriptor, the tracer +will report the return from user-space latency, which is the total +latency. If this is the end of the work, it can be interpreted as the +response time for the request. + +After reporting the total latency, timerlat will restart the cycle, arm +a timer, and go to sleep for the following activation. + +If at any time one of the conditions is broken, e.g., the thread migrates +while in user space, or the timerlat tracer is disabled, the SIG_KILL +signal will be sent to the user-space thread. + +Here is an basic example of user-space code for timerlat:: + + int main(void) + { + char buffer[1024]; + int timerlat_fd; + int retval; + long cpu = 0; /* place in CPU 0 */ + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + + if (sched_setaffinity(gettid(), sizeof(set), &set) == -1) + return 1; + + snprintf(buffer, sizeof(buffer), + "/sys/kernel/tracing/osnoise/per_cpu/cpu%ld/timerlat_fd", + cpu); + + timerlat_fd = open(buffer, O_RDONLY); + if (timerlat_fd < 0) { + printf("error opening %s: %s\n", buffer, strerror(errno)); + exit(1); + } + + for (;;) { + retval = read(timerlat_fd, buffer, 1024); + if (retval < 0) + break; + } + + close(timerlat_fd); + exit(0); + }
kernel/trace/trace_osnoise.c +374 -4
469 lines not on this page · raw message
kernel/trace/trace_output.c +3 -1
@@ -1446,6 +1446,8 @@ static struct trace_event trace_osnoise_event = { };/* TRACE_TIMERLAT */ + +static char *timerlat_lat_context[] = {"irq" ,"thread" ,"user-ret" };static enum print_line_t trace_timerlat_print (struct trace_iterator *iter,int flags,struct trace_event *event)
@@ -1458,7 +1460,7 @@ trace_timerlat_print(struct trace_iterator *iter, int flags,trace_seq_printf (s,"#%-5u context %6s timer_latency %9llu ns\n" , field->seqnum, - field->context ?"thread" :"irq" , + timerlat_lat_context[field->context], field->timer_latency);return trace_handle_return (s);
Signature · 1 line
2.38.1