Re: [PATCH] tracing/osnoise: Force quiescent states while tracing

From: Daniel Bristot de Oliveira
Date: Fri Mar 04 2022 - 09:51:29 EST


>> Hey Nicolas,
>>
>> While testing this patch with rtla osnoise on the 5.17.0-rc6-rt10+, when I hit
>> ^c on osnoise top, the system freezes :-/.
>>
>> Could you try that on your system?
> Yes of course, I'll get a build going.


also, could you try this?

it is an RFC I was thinking to send, as I mentioned before (and on IRC).

It works fine, I see nohz_full and rcu behaving like if osnoise was a
user-space tool. It is more invasive on osnoise, but the behavior does not
change - like, run cyclictest on top of osnoise and you will see that
the system is still preemptive with low latency even with osnoise with
"preempt_disabled."

do you mind having a look to see if it behaves as expected in your scenario?

[ note, there are things to cleanup in this patch, like adding a static key ]
[ in is_osnoise_cur(), it was a real RFC. ]

-- Daniel

tracing/osnoise: Pretend to be in user-space for RCU

To simulate an user-space workload, osnoise informs RCU that it
is going to user-space by calling rcu_user_enter(). However,
osnoise never actually goes to user-space. It keeps running
in an intermediate stage.

This stage runs with preemption disabled, like the idle thread
does. Likewise idle, osnoise will continuously check for need
resched, allowing its preemption, simulating a fully preemptive
mode.

Anytime a kernel function needs to be called, the rcu_user_enter()
needs to be called.

Any change on rcu_user_enter/exit needs to be tested with
CONFIG_RCU_EQS_DEBUG=y.

Signed-off-by: Daniel Bristot de Oliveira <bristot@xxxxxxxxxx>
---
include/linux/trace.h | 9 +++
kernel/rcu/tree.c | 7 +-
kernel/trace/trace_osnoise.c | 129 +++++++++++++++++++++++++++++++++--
3 files changed, 136 insertions(+), 9 deletions(-)

diff --git a/include/linux/trace.h b/include/linux/trace.h
index bf169612ffe1..970d66f79cee 100644
--- a/include/linux/trace.h
+++ b/include/linux/trace.h
@@ -48,6 +48,15 @@ void osnoise_arch_unregister(void);
void osnoise_trace_irq_entry(int id);
void osnoise_trace_irq_exit(int id, const char *desc);

+#ifdef CONFIG_OSNOISE_TRACER
+extern bool is_osnoise_curr(void);
+#else
+static __always_inline bool is_osnoise_curr(void)
+{
+ return false;
+}
+#endif /* CONFIG_OSNOISE_TRACER */
+
#endif /* CONFIG_TRACING */

#endif /* _LINUX_TRACE_H */
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index a4c25a6283b0..ede0c468e75f 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -62,6 +62,7 @@
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/kasan.h>
+#include <linux/trace.h>
#include "../time/tick-internal.h"

#include "tree.h"
@@ -442,9 +443,11 @@ static int rcu_is_cpu_rrupt_from_idle(void)
return false;

/*
- * If we're not in an interrupt, we must be in the idle task!
+ * If we're not in an interrupt, we must be in the idle task or osnoise.
+ * The osnoise thread is an special case. It is a kernel thread that
+ * pretends to be in user-space. See kernel/trace/trace_osnoise.c.
*/
- WARN_ON_ONCE(!nesting && !is_idle_task(current));
+ WARN_ON_ONCE(!nesting && !(is_idle_task(current) || is_osnoise_curr()));

/* Does CPU appear to be idle from an RCU standpoint? */
return __this_cpu_read(rcu_data.dynticks_nesting) == 0;
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index cfddb30e65ab..d52ef290c884 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -448,6 +448,31 @@ static void print_osnoise_headers(struct seq_file *s)
}
#endif /* CONFIG_PREEMPT_RT */

+static inline void osnoise_user_enter(void)
+{
+ local_irq_disable();
+ rcu_user_enter();
+ local_irq_enable();
+}
+
+static inline void osnoise_user_exit(void)
+{
+ local_irq_disable();
+ rcu_user_exit();
+ local_irq_enable();
+}
+
+static inline void osnoise_cond_resched(void)
+{
+ if (need_resched()) {
+ osnoise_user_exit();
+ preempt_enable();
+ cond_resched();
+ preempt_disable();
+ osnoise_user_enter();
+ }
+}
+
/*
* osnoise_taint - report an osnoise error.
*/
@@ -464,6 +489,14 @@ static void print_osnoise_headers(struct seq_file *s)
osnoise_data.tainted = true; \
})

+#define osnoise_taint_user(msg) ({ \
+ local_irq_disable(); \
+ rcu_user_exit(); \
+ osnoise_taint(msg); \
+ rcu_user_enter(); \
+ local_irq_enable(); \
+})
+
/*
* Record an osnoise_sample into the tracer buffer.
*/
@@ -819,6 +852,43 @@ set_int_safe_time(struct osnoise_variables *osn_var, u64 *time)
return int_counter;
}

+/*
+ * set_int_safe_time_thread - Save the current time on *time, aware of interference
+ *
+ * Get the time, taking into consideration a possible interference from
+ * higher priority interrupts and threads.
+ *
+ * See get_int_safe_duration() for an explanation.
+ */
+static u64
+set_int_safe_time_thread(struct osnoise_variables *osn_var, u64 *time)
+{
+ u64 int_counter;
+
+ do {
+ int_counter = local_read(&osn_var->int_counter);
+
+ /* let any thread awakened by an interrupt to run */
+ osnoise_cond_resched();
+
+ /* synchronize with interrupts */
+ barrier();
+
+ *time = time_get();
+
+ /* synchronize with interrupts */
+ barrier();
+
+ } while (int_counter != local_read(&osn_var->int_counter));
+
+ /*
+ * At this point, the time accounts all the interference from to
+ * consecutive get time that did not get interfered.
+ */
+ return int_counter;
+}
+
+
#ifdef CONFIG_TIMERLAT_TRACER
/*
* copy_int_safe_time - Copy *src into *desc aware of interference
@@ -1337,11 +1407,31 @@ static int run_osnoise(void)
*/
last_int_count = set_int_safe_time(osn_var, &last_sample);

+ /*
+ * to simulate an user-space workload, osnoise informs RCU that it
+ * is going to user-space by calling rcu_user_enter(). However,
+ * osnoise never actually goes to user-space. It keeps running
+ * in an intermediate stage.
+ *
+ * This stage runs with preemption disabled, like the idle thread
+ * does. Likewise idle, osnoise will continuously check for need
+ * resched, allowing its preemption, simulating a fully preemptive
+ * mode.
+ *
+ * Anytime a kernel function needs to be called, the rcu_user_enter()
+ * needs to be called.
+ *
+ * Any change on rcu_user_enter/exit needs to be tested with
+ * CONFIG_RCU_EQS_DEBUG=y.
+ */
+ preempt_disable();
+ osnoise_user_enter();
+
do {
/*
* Get sample!
*/
- int_count = set_int_safe_time(osn_var, &sample);
+ int_count = set_int_safe_time_thread(osn_var, &sample);

noise = time_sub(sample, last_sample);

@@ -1349,7 +1439,7 @@ static int run_osnoise(void)
* This shouldn't happen.
*/
if (noise < 0) {
- osnoise_taint("negative noise!");
+ osnoise_taint_user("negative noise!");
goto out;
}

@@ -1362,7 +1452,7 @@ static int run_osnoise(void)
* Check for possible overflows.
*/
if (total < last_total) {
- osnoise_taint("total overflow!");
+ osnoise_taint_user("total overflow!");
break;
}

@@ -1379,24 +1469,41 @@ static int run_osnoise(void)

sum_noise += noise;

+ /*
+ * osnoise is in fake user-space. Leave this mode to call
+ * the tracepoint. Interrupts are kept disabled to avoid
+ * having the overhead of enabling/disabling around
+ * rcu_user_exit/enter again. This does not change the
+ * behavior of osnoise.
+ */
+ local_irq_disable();
+ rcu_user_exit();
trace_sample_threshold(last_sample, noise, interference);
+ rcu_user_enter();
+ local_irq_enable();

if (osnoise_data.stop_tracing)
- if (noise > stop_in)
+ if (noise > stop_in) {
+ osnoise_user_exit();
osnoise_stop_tracing();
+ osnoise_user_enter();
+ }
}

+
/*
- * For the non-preemptive kernel config: let threads runs, if
- * they so wish.
+ * Let threads to interfere with osnoise.
*/
- cond_resched();
+ osnoise_cond_resched();

last_sample = sample;
last_int_count = int_count;

} while (total < runtime && !kthread_should_stop());

+ osnoise_user_exit();
+ preempt_enable();
+
/*
* Finish the above in the view for interrupts.
*/
@@ -2387,3 +2494,11 @@ __init static int init_osnoise_tracer(void)
return 0;
}
late_initcall(init_osnoise_tracer);
+
+bool is_osnoise_curr(void)
+{
+ struct osnoise_variables *osn_var = this_cpu_osn_var();
+ if (osn_var->kthread == current)
+ return 1;
+ return 0;
+}
--
2.34.1