Skip to content

Commit d9d860f

Browse files
authored
Discuss new NetTrace V6 format (#2170)
1 parent e6d509a commit d9d860f

File tree

12 files changed

+5255
-1512
lines changed

12 files changed

+5255
-1512
lines changed

src/TraceEvent/DynamicTraceEventParser.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -626,9 +626,27 @@ private object GetPayloadValueAt(ref PayloadFetch payloadFetch, int offset, int
626626
case TypeCode.UInt32:
627627
return (UInt32)GetInt32At(offset);
628628
case TypeCode.Int64:
629-
return GetInt64At(offset);
629+
{
630+
if (payloadFetch.Size == VARINT)
631+
{
632+
return GetVarIntAt(offset);
633+
}
634+
else
635+
{
636+
return GetInt64At(offset);
637+
}
638+
}
630639
case TypeCode.UInt64:
631-
return (UInt64)GetInt64At(offset);
640+
{
641+
if (payloadFetch.Size == VARINT)
642+
{
643+
return GetVarUIntAt(offset);
644+
}
645+
else
646+
{
647+
return (UInt64)GetInt64At(offset);
648+
}
649+
}
632650
case TypeCode.Single:
633651
return GetSingleAt(offset);
634652
case TypeCode.Double:
@@ -1171,6 +1189,10 @@ internal int OffsetOfNextField(ref PayloadFetch payloadFetch, int offset, int pa
11711189

11721190
return offset + elemSize;
11731191
}
1192+
else if (size == VARINT)
1193+
{
1194+
return SkipVarInt(offset);
1195+
}
11741196
else
11751197
{
11761198
return ushort.MaxValue; // Something sure to fail
@@ -1238,8 +1260,9 @@ internal static ushort SizeOfType(Type type)
12381260
// Size 0xFFEF is NULL_TERMINATED | IS_ANSI
12391261
internal const ushort NULL_TERMINATED = 0xFFEE; // value is a null terminated string.
12401262

1241-
internal const ushort POINTER_SIZE = 0xFFED; // It is the pointer size of the target machine.
1242-
internal const ushort UNKNOWN_SIZE = 0xFFEC; // Generic unknown.
1263+
internal const ushort POINTER_SIZE = 0xFFED; // It is the pointer size of the target machine.
1264+
internal const ushort VARINT = 0xFFEC;
1265+
internal const ushort UNKNOWN_SIZE = 0xFFEB; // Generic unknown.
12431266
internal const ushort SPECIAL_SIZES = UNKNOWN_SIZE; // This is always the smallest size as an unsiged number.
12441267

12451268
internal struct PayloadFetch

src/TraceEvent/EventPipe/EventCache.cs

Lines changed: 117 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -12,44 +12,46 @@ namespace Microsoft.Diagnostics.Tracing.EventPipe
1212

1313
internal class EventCache
1414
{
15+
public EventCache(EventPipeEventSource source, ThreadCache threads)
16+
{
17+
_source = source;
18+
_threads = threads;
19+
}
20+
1521
public event ParseBufferItemFunction OnEvent;
1622
public event Action<int> OnEventsDropped;
1723

18-
public unsafe void ProcessEventBlock(byte[] eventBlockData)
24+
public void ProcessEventBlock(byte[] eventBlockData, long streamOffset)
1925
{
26+
PinnedBuffer buffer = new PinnedBuffer(eventBlockData);
27+
SpanReader reader = new SpanReader(eventBlockData, streamOffset);
28+
2029
// parse the header
21-
if(eventBlockData.Length < 20)
30+
if (eventBlockData.Length < 20)
2231
{
23-
Debug.Assert(false, "Expected EventBlock of at least 20 bytes");
24-
return;
32+
throw new FormatException("Expected EventBlock of at least 20 bytes");
2533
}
26-
ushort headerSize = BitConverter.ToUInt16(eventBlockData, 0);
34+
ushort headerSize = reader.ReadUInt16();
2735
if(headerSize < 20 || headerSize > eventBlockData.Length)
2836
{
29-
Debug.Assert(false, "Invalid EventBlock header size");
30-
return;
37+
throw new FormatException("Invalid EventBlock header size");
3138
}
32-
ushort flags = BitConverter.ToUInt16(eventBlockData, 2);
39+
ushort flags = reader.ReadUInt16();
3340
bool useHeaderCompression = (flags & (ushort)EventBlockFlags.HeaderCompression) != 0;
3441

42+
// skip the rest of the header
43+
reader.ReadBytes(headerSize - 4);
44+
3545
// parse the events
36-
PinnedBuffer buffer = new PinnedBuffer(eventBlockData);
37-
byte* cursor = (byte*)buffer.PinningHandle.AddrOfPinnedObject();
38-
byte* end = cursor + eventBlockData.Length;
39-
cursor += headerSize;
4046
EventMarker eventMarker = new EventMarker(buffer);
4147
long timestamp = 0;
42-
EventPipeEventHeader.ReadFromFormatV4(cursor, useHeaderCompression, ref eventMarker.Header);
43-
if (!_threads.TryGetValue(eventMarker.Header.CaptureThreadId, out EventCacheThread thread))
44-
{
45-
thread = new EventCacheThread();
46-
thread.SequenceNumber = eventMarker.Header.SequenceNumber - 1;
47-
AddThread(eventMarker.Header.CaptureThreadId, thread);
48-
}
48+
SpanReader tempReader = reader;
49+
_source.ReadEventHeader(ref tempReader, useHeaderCompression, ref eventMarker.Header);
50+
EventPipeThread thread = _threads.GetOrAddThread(eventMarker.Header.CaptureThreadIndexOrId, eventMarker.Header.SequenceNumber - 1);
4951
eventMarker = new EventMarker(buffer);
50-
while (cursor < end)
52+
while (reader.RemainingBytes.Length > 0)
5153
{
52-
EventPipeEventHeader.ReadFromFormatV4(cursor, useHeaderCompression, ref eventMarker.Header);
54+
_source.ReadEventHeader(ref reader, useHeaderCompression, ref eventMarker.Header);
5355
bool isSortedEvent = eventMarker.Header.IsSorted;
5456
timestamp = eventMarker.Header.TimeStamp;
5557
int sequenceNumber = eventMarker.Header.SequenceNumber;
@@ -58,26 +60,10 @@ public unsafe void ProcessEventBlock(byte[] eventBlockData)
5860
thread.LastCachedEventTimestamp = timestamp;
5961

6062
// sorted events are the only time the captureThreadId should change
61-
long captureThreadId = eventMarker.Header.CaptureThreadId;
62-
if (!_threads.TryGetValue(captureThreadId, out thread))
63-
{
64-
thread = new EventCacheThread();
65-
thread.SequenceNumber = sequenceNumber - 1;
66-
AddThread(captureThreadId, thread);
67-
}
68-
}
69-
70-
int droppedEvents = (int)Math.Min(int.MaxValue, sequenceNumber - thread.SequenceNumber - 1);
71-
if(droppedEvents > 0)
72-
{
73-
OnEventsDropped?.Invoke(droppedEvents);
74-
}
75-
else
76-
{
77-
// When a thread id is recycled the sequenceNumber can abruptly reset to 1 which
78-
// makes droppedEvents go negative
79-
Debug.Assert(droppedEvents == 0 || sequenceNumber == 1);
63+
long captureThreadId = eventMarker.Header.CaptureThreadIndexOrId;
64+
thread = _threads.GetOrAddThread(captureThreadId, sequenceNumber - 1);
8065
}
66+
NotifyDroppedEventsIfNeeded(thread.SequenceNumber, sequenceNumber - 1);
8167
thread.SequenceNumber = sequenceNumber;
8268

8369
if(isSortedEvent)
@@ -91,81 +77,88 @@ public unsafe void ProcessEventBlock(byte[] eventBlockData)
9177

9278
}
9379

94-
cursor += eventMarker.Header.TotalNonHeaderSize + eventMarker.Header.HeaderSize;
80+
reader.ReadBytes(eventMarker.Header.PayloadSize);
9581
EventMarker lastEvent = eventMarker;
9682
eventMarker = new EventMarker(buffer);
9783
eventMarker.Header = lastEvent.Header;
9884
}
9985
thread.LastCachedEventTimestamp = timestamp;
10086
}
10187

102-
public unsafe void ProcessSequencePointBlock(byte[] sequencePointBytes)
88+
public void ProcessSequencePointBlockV5OrLess(byte[] sequencePointBytes, long streamOffset)
10389
{
104-
const int SizeOfTimestampAndThreadCount = 12;
105-
const int SizeOfThreadIdAndSequenceNumber = 12;
106-
if(sequencePointBytes.Length < SizeOfTimestampAndThreadCount)
90+
SpanReader reader = new SpanReader(sequencePointBytes, streamOffset);
91+
long timestamp = (long)reader.ReadUInt64();
92+
int threadCount = (int)reader.ReadUInt32();
93+
Flush(timestamp);
94+
TrimEventsAfterSequencePoint();
95+
for (int i = 0; i < threadCount; i++)
10796
{
108-
Debug.Assert(false, "Bad sequence point block length");
109-
return;
97+
long captureThreadId = (long)reader.ReadUInt64();
98+
int sequenceNumber = (int)reader.ReadUInt32();
99+
CheckpointThread(captureThreadId, sequenceNumber);
110100
}
111-
long timestamp = BitConverter.ToInt64(sequencePointBytes, 0);
112-
int threadCount = BitConverter.ToInt32(sequencePointBytes, 8);
113-
if(sequencePointBytes.Length < SizeOfTimestampAndThreadCount + threadCount*SizeOfThreadIdAndSequenceNumber)
101+
}
102+
103+
enum SequencePointFlags : uint
104+
{
105+
FlushThreads = 1
106+
}
107+
108+
public void ProcessSequencePointBlockV6OrGreater(byte[] sequencePointBytes, long streamOffset)
109+
{
110+
SpanReader reader = new SpanReader(sequencePointBytes, streamOffset);
111+
long timestamp = (long)reader.ReadUInt64();
112+
uint flags = reader.ReadUInt32();
113+
int threadCount = (int)reader.ReadUInt32();
114+
Flush(timestamp);
115+
TrimEventsAfterSequencePoint();
116+
for (int i = 0; i < threadCount; i++)
114117
{
115-
Debug.Assert(false, "Bad sequence point block length");
116-
return;
118+
long captureThreadIndex = (long)reader.ReadVarUInt64();
119+
int sequenceNumber = (int)reader.ReadVarUInt32();
120+
CheckpointThread(captureThreadIndex, sequenceNumber);
117121
}
122+
123+
if((flags & (uint)SequencePointFlags.FlushThreads) != 0)
124+
{
125+
_threads.Flush();
126+
}
127+
}
128+
129+
/// <summary>
130+
/// After all events have been parsed we could have some straglers that weren't
131+
/// earlier than any sorted event. Sort and dispatch those now.
132+
/// </summary>
133+
public void Flush() => Flush(long.MaxValue);
134+
135+
private void Flush(long timestamp)
136+
{
118137
SortAndDispatch(timestamp);
119-
foreach(EventCacheThread thread in _threads.Values)
138+
CheckForPendingThreadRemoval();
139+
}
140+
141+
private void TrimEventsAfterSequencePoint()
142+
{
143+
foreach (EventPipeThread thread in _threads.Values)
120144
{
121145
Debug.Assert(thread.Events.Count == 0, "There shouldn't be any pending events after a sequence point");
122146
thread.Events.Clear();
123147
thread.Events.TrimExcess();
124148
}
149+
}
125150

126-
int cursor = SizeOfTimestampAndThreadCount;
127-
for(int i = 0; i < threadCount; i++)
151+
private void CheckForPendingThreadRemoval()
152+
{
153+
foreach (var thread in _threads.Values)
128154
{
129-
long captureThreadId = BitConverter.ToInt64(sequencePointBytes, cursor);
130-
int sequenceNumber = BitConverter.ToInt32(sequencePointBytes, cursor + 8);
131-
if (!_threads.TryGetValue(captureThreadId, out EventCacheThread thread))
155+
if (thread.RemovalPending && thread.Events.Count == 0)
132156
{
133-
if(sequenceNumber > 0)
134-
{
135-
OnEventsDropped?.Invoke(sequenceNumber);
136-
}
137-
thread = new EventCacheThread();
138-
thread.SequenceNumber = sequenceNumber;
139-
AddThread(captureThreadId, thread);
140-
}
141-
else
142-
{
143-
int droppedEvents = unchecked(sequenceNumber - thread.SequenceNumber);
144-
if (droppedEvents > 0)
145-
{
146-
OnEventsDropped?.Invoke(droppedEvents);
147-
}
148-
else
149-
{
150-
// When a thread id is recycled the sequenceNumber can abruptly reset to 1 which
151-
// makes droppedEvents go negative
152-
Debug.Assert(droppedEvents == 0 || sequenceNumber == 1);
153-
}
154-
thread.SequenceNumber = sequenceNumber;
157+
_threads.RemoveThread(thread.ThreadId);
155158
}
156-
cursor += SizeOfThreadIdAndSequenceNumber;
157159
}
158160
}
159161

160-
/// <summary>
161-
/// After all events have been parsed we could have some straglers that weren't
162-
/// earlier than any sorted event. Sort and dispatch those now.
163-
/// </summary>
164-
public void Flush()
165-
{
166-
SortAndDispatch(long.MaxValue);
167-
}
168-
169162
private unsafe void SortAndDispatch(long stopTimestamp)
170163
{
171164
// This sort could be made faster by using a min-heap but this is a simple place to start
@@ -203,7 +196,7 @@ private unsafe void SortAndDispatch(long stopTimestamp)
203196
// from the cache or memory usage will grow unbounded. AddThread handles the
204197
// the thread objects but the storage for the queue elements also does not shrink
205198
// below the high water mark unless we free it explicitly.
206-
foreach(Queue<EventMarker> q in threadQueues)
199+
foreach (Queue<EventMarker> q in threadQueues)
207200
{
208201
if(q.Count == 0)
209202
{
@@ -212,45 +205,46 @@ private unsafe void SortAndDispatch(long stopTimestamp)
212205
}
213206
}
214207

215-
private void AddThread(long captureThreadId, EventCacheThread thread)
208+
public void CheckpointThreadAndPendRemoval(long threadIndex, int sequenceNumber)
209+
{
210+
EventPipeThread thread = _threads.GetThread(threadIndex);
211+
CheckpointThread(thread, sequenceNumber);
212+
thread.RemovalPending = true;
213+
}
214+
215+
public void CheckpointThread(long threadIndex, int sequenceNumber)
216+
{
217+
EventPipeThread thread = _threads.GetOrAddThread(threadIndex, sequenceNumber);
218+
CheckpointThread(thread, sequenceNumber);
219+
}
220+
221+
private void CheckpointThread(EventPipeThread thread, int sequenceNumber)
222+
{
223+
NotifyDroppedEventsIfNeeded(thread.SequenceNumber, sequenceNumber);
224+
thread.SequenceNumber = sequenceNumber;
225+
}
226+
227+
private void NotifyDroppedEventsIfNeeded(int sequenceNumber, int expectedSequenceNumber)
216228
{
217-
// To ensure we don't have unbounded growth we evict old threads to make room
218-
// for new ones. Evicted threads can always be re-added later if they log again
219-
// but there are two consequences:
220-
// a) We won't detect lost events on that thread after eviction
221-
// b) If the thread still had events pending dispatch they will be lost
222-
// We pick the thread that has gone the longest since it last logged an event
223-
// under the presumption that it is probably dead, has no events, and won't
224-
// log again.
225-
//
226-
// In the future if we had explicit thread death notification events we could keep
227-
// this cache leaner.
228-
if(_threads.Count >= 5000)
229+
// Either events were dropped or the sequence number was reset because the thread ID was recycled.
230+
// V6 format never recycles thread indexes but prior formats do. We assume heuristically that if an event or sequence
231+
// point implies the last sequenceNumber was zero then the thread was recycled.
232+
if (_source.FileFormatVersionNumber >= 6 || sequenceNumber != 0)
229233
{
230-
long oldestThreadCaptureId = -1;
231-
long smallestTimestamp = long.MaxValue;
232-
foreach(var kv in _threads)
234+
int droppedEvents = unchecked(expectedSequenceNumber - sequenceNumber);
235+
if (droppedEvents < 0)
233236
{
234-
if(kv.Value.LastCachedEventTimestamp < smallestTimestamp)
235-
{
236-
smallestTimestamp = kv.Value.LastCachedEventTimestamp;
237-
oldestThreadCaptureId = kv.Key;
238-
}
237+
droppedEvents = int.MaxValue;
238+
}
239+
if (droppedEvents > 0)
240+
{
241+
OnEventsDropped?.Invoke(droppedEvents);
239242
}
240-
Debug.Assert(oldestThreadCaptureId != -1);
241-
_threads.Remove(oldestThreadCaptureId);
242243
}
243-
_threads[captureThreadId] = thread;
244244
}
245245

246-
Dictionary<long, EventCacheThread> _threads = new Dictionary<long, EventCacheThread>();
247-
}
248-
249-
internal class EventCacheThread
250-
{
251-
public Queue<EventMarker> Events = new Queue<EventMarker>();
252-
public int SequenceNumber;
253-
public long LastCachedEventTimestamp;
246+
EventPipeEventSource _source;
247+
ThreadCache _threads;
254248
}
255249

256250
internal class EventMarker

0 commit comments

Comments
 (0)