Skip to contentSkip to conversation

ring-buffer: More precise time stamps for nested writes

PATCH In review Suresh E. Warrier 3 participants 6 messages Mar 24, 2015 – Mar 27, 2015 1 message not in the archive

Subsystems (2)
lore
Share & tools
lore

A message that is not in the archive (sent off-list or not yet received): [email protected]

#1 lore raw

PATCH 1/1 ring-buffer: More precise time stamps for nested writes

When tracing the behavior of multiple fio jobs running in parallel our performance team observed that some scsi_dispatch_cmd_done events appeared to occur earlier, often several microseconds earlier, than their associated scsi_dispatch_cmd_start event in the trace records. Other interrupt events were also observed to have earlier timestamps than the events that caused them.

This incorrect chronological ordering of trace records occurs because ALL nested writes have the same time stamp as the first writer, that is the first writer in the stack that was preempted. In workloads where interrupts occur frequently, the first writer can stay preempted across multiple interrupts and nested trace events can record time stamps that are many microseconds earlier than their actual value resulting in the wrong ordering.

For example, a scsi_dispatch_cmd_start on CPU A occurs, say at time t1, and the corresponding scsi_dispatch_cmd_done occurs on another CPU B, say at time t2. However, CPU B is in the middle of a nested write with the first writer having recorded its event at time t0, where t0 < t1 < t2. Eventually, we get out of all the nested writes and the first writer commits its trace event with a time of t0 and the the nested scsi_dispatch_cmd_done gets assigned the same timestamp t0. In the trace report, the scsi_dispatch_cmd_done thus appears to have occurred before the scsi_dispatch_cmd_start.

In some cases, on larger systems with multiple fio jobs running and interrupts being sent to a single CPU, we have noticed more than 400 trace events all with the same timestamp.

The reason all nested writes have the same time stamp is because all nested trace events are assigned a time delta of 0. A correct time delta cannot be computed for them because the first interrupted write has not been committed yet and so the commit timestamp in the CPU buffer does not record the time stamp of the just prior event.

One way of fixing this is to keep a timestamp of the last event we reserved space for in the ring buffer, so that we can compute the correct time delta for each event. We also need a short critical section where we cannot be interrupted, in which we:
1. Read the current time
2. Compute the time delta for this event from the last time stamp
3. Update the value of the last time stamp
4. Reserve space in the ring buffer for the event,

Although the ring buffer design is coded to avoid disabling interrupts, in this case there does not appear to be a practical way to solve this problem without disabling interrupts for a short time. To accommodate those architectures where disabling interrupts is expensive, we introduce a new kernel configuration option called CONFIG_RING_BUFFER_NESTED_WRITE_TIMESTAMP_UPDATE. On architectures where local_irq_save/restore is cheap, this should be set to Y, in order to get accurate timestamps. Other architectures can set it to N to avoid the overhead of local_irq_save/restore at the cost of getting inaccurate timestamps for nested writes.

---
 kernel/trace/Kconfig       | 15 ++++++++++++
 kernel/trace/ring_buffer.c | 59 +++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 71 insertions(+), 3 deletions(-)
kernel/trace/Kconfig +15 -0
index 015f85a..8a8e185 100644
@@ -573,6 +573,21 @@ config RING_BUFFER_STARTUP_TEST
 
 	 If unsure, say N
 
+config RING_BUFFER_NESTED_WRITE_TIMESTAMP_UPDATE
+	bool "Ring buffer update timestamps for nested writes"
+	depends on RING_BUFFER
+	default y if PPC64
+	help
+	  This option allows nested writes to have updated time stamps
+	  to correspond to the time that the event occurred instead of
+	  having the same time stamp as the first write that was
+	  interrupted.
+
+	  The option requires a fast IRQ enable/disable mechanism
+	  to work efficiently.
+
+	  If unsure, say N
+
 endif # FTRACE
 
 endif # TRACING_SUPPORT
kernel/trace/ring_buffer.c +56 -3

110 lines not on this page · raw message

Signature · 1 line
1.8.3.4
maintainer
#2 lore raw

On Tue, 24 Mar 2015 18:10:05 -0500

Quoted 10 lines

Although the ring buffer design is coded to avoid disabling interrupts, in this case there does not appear to be a practical way to solve this problem without disabling interrupts for a short time. To accommodate those architectures where disabling interrupts is expensive, we introduce a new kernel configuration option called CONFIG_RING_BUFFER_NESTED_WRITE_TIMESTAMP_UPDATE. On architectures where local_irq_save/restore is cheap, this should be set to Y, in order to get accurate timestamps. Other architectures can set it to N to avoid the overhead of local_irq_save/restore at the cost of getting inaccurate timestamps for nested writes.

There is no architecture where disabling interrupts is cheap. Actually, enabling them is the killer. Doing function tracing shows the impact of this rather well, as it would disable and enable interrupts for every function call, which can get rather expensive in the sum of things, and it does skew the timings and can make it more difficult to debug heissenbugs.

That said, I feel your pain. I've had some ideas about doing this without disabling interrupts. But for now, what can be done is to have a flag that is set that will implement this or not. Using static_branch() to implement it such that when its off it has no effect.

In the mean time, I can go and revisit trying to have better timings with nested writes.

-- Steve

#3 lore raw
Quoted 2 lines · Steven Rostedt

On Tue, 24 Mar 2015 18:10:05 -0500

..
..

Quoted 7 lines

There is no architecture where disabling interrupts is cheap. Actually, enabling them is the killer. Doing function tracing shows the impact of this rather well, as it would disable and enable interrupts for every function call, which can get rather expensive in the sum of things, and it does skew the timings and can make it more difficult to debug heissenbugs.

On PPC64, enabling and disabling soft IRQs is simply a matter of setting/clearing a per-CPU global variable (in the per-CPU PACA structure), so it shouldn't be expensive unless an interrupt is pending when we re-enable, which should be rare. I ran a series of tests on a Power8 box and even with a cold cache, both the local_irq_save and local_irq_restore routines only took a few nanoseconds, although the restore was a little more expensive than the save. (The restore took about 80-160 ns on a cold cache and 20-30 ns on a warm one.)

Quoted 2 lines

That said, I feel your pain. I've had some ideas about doing this without disabling interrupts.

That is excellent!

Quoted 4 lines

But for now, what can be done is to have
a flag that is set that will implement this or not. Using static_branch() to implement it such that when its off it has no effect.

Are you recommending that for now I use a static_branch() instead of a CONFIG option to fix this? I could do that but the resulting code will either be messier to read (with several if condition checks) or will require some duplication of code. My assumption is that the new CONFIG option when disabled should have negligible impact since the compiler inlines the functions.

-suresh

Quoted 5 lines

In the mean time, I can go and revisit trying to have better timings with nested writes.

-- Steve

maintainer
#4 lore raw
Quoted 11 lines · On Fri, 27 Mar 2015 00:38:43 -0500 "Suresh E. Warrier"…
Quoted 4 lines

But for now, what can be done is to have
a flag that is set that will implement this or not. Using static_branch() to implement it such that when its off it has no effect.

Are you recommending that for now I use a static_branch() instead of a CONFIG option to fix this? I could do that but the resulting code will either be messier to read (with several if condition checks) or will require some duplication of code. My assumption is that the new CONFIG option when disabled should have negligible impact since the compiler inlines the functions.

It can be done cleanly if you encapsulate it properly.

Too bad I'm not going on any trips soon. This is a project I would work on on the plane.

-- Steve

#5 lore raw
Quoted 8 lines · Steven Rostedt
Quoted 5 lines · On Fri, 27 Mar 2015 00:38:43 -0500 "Suresh E. Warrier"…
Quoted 4 lines

But for now, what can be done is to have
a flag that is set that will implement this or not. Using static_branch() to implement it such that when its off it has no effect.

Quoted 8 lines
Quoted 6 lines

Are you recommending that for now I use a static_branch() instead of a CONFIG option to fix this? I could do that but the resulting code will either be messier to read (with several if condition checks) or will require some duplication of code. My assumption is that the new CONFIG option when disabled should have negligible impact since the compiler inlines the functions.

It can be done cleanly if you encapsulate it properly.

Sure, but what is the advantage to using a static branch? When would you ever want a single kernel image that could run either way depending on what machine it was running on?

Quoted 2 lines

Too bad I'm not going on any trips soon. This is a project I would work on on the plane.

:)

Paul.

maintainer
#6 lore raw
Quoted 5 lines · On Sat, 28 Mar 2015 09:14:30 +1100 Paul Mackerras <[email protected]> wrote:
Quoted 1 line

It can be done cleanly if you encapsulate it properly.

Sure, but what is the advantage to using a static branch? When would you ever want a single kernel image that could run either way depending on what machine it was running on?

For x86 you don't want it, unless you want to see interrupts serialized, where some times you do.

It's not just an arch thing.

-- Steve