private unsafe void SortAndDispatch(long stopTimestamp) { // This sort could be made faster by using a min-heap but this is a simple place to start List <Queue <EventMarker> > threadQueues = new List <Queue <EventMarker> >(_threads.Values.Select(t => t.Events)); while (true) { long lowestTimestamp = stopTimestamp; Queue <EventMarker> oldestEventQueue = null; foreach (Queue <EventMarker> threadQueue in threadQueues) { if (threadQueue.Count == 0) { continue; } long eventTimestamp = threadQueue.Peek().Header.TimeStamp; if (eventTimestamp < lowestTimestamp) { oldestEventQueue = threadQueue; lowestTimestamp = eventTimestamp; } } if (oldestEventQueue == null) { break; } else { EventMarker eventMarker = oldestEventQueue.Dequeue(); OnEvent?.Invoke(ref eventMarker.Header); } } // If the app creates and destroys threads over time we need to flush old threads // from the cache or memory usage will grow unbounded. AddThread handles the // the thread objects but the storage for the queue elements also does not shrink // below the high water mark unless we free it explicitly. Although not unbounded // growth our current runtime policy reads ahead up to 10,000 events ahead per-thread // * 5000 threads * 24 bytes = ~1GB. It would be nice not to leave that much memory // laying around probably mostly unused. foreach (Queue <EventMarker> q in threadQueues) { if (q.Count == 0) { q.TrimExcess(); } } }
public unsafe void ProcessEventBlock(byte[] eventBlockData) { // parse the header if (eventBlockData.Length < 20) { Debug.Assert(false, "Expected EventBlock of at least 20 bytes"); return; } ushort headerSize = BitConverter.ToUInt16(eventBlockData, 0); if (headerSize < 20 || headerSize > eventBlockData.Length) { Debug.Assert(false, "Invalid EventBlock header size"); return; } ushort flags = BitConverter.ToUInt16(eventBlockData, 2); bool useHeaderCompression = (flags & (ushort)EventBlockFlags.HeaderCompression) != 0; // parse the events PinnedBuffer buffer = new PinnedBuffer(eventBlockData); byte * cursor = (byte *)buffer.PinningHandle.AddrOfPinnedObject(); byte * end = cursor + eventBlockData.Length; cursor += headerSize; EventMarker eventMarker = new EventMarker(buffer); long timestamp = 0; EventPipeEventHeader.ReadFromFormatV4(cursor, useHeaderCompression, ref eventMarker.Header); if (!_threads.TryGetValue(eventMarker.Header.CaptureThreadId, out EventCacheThread thread)) { thread = new EventCacheThread(); thread.SequenceNumber = eventMarker.Header.SequenceNumber - 1; AddThread(eventMarker.Header.CaptureThreadId, thread); } eventMarker = new EventMarker(buffer); while (cursor < end) { EventPipeEventHeader.ReadFromFormatV4(cursor, useHeaderCompression, ref eventMarker.Header); bool isSortedEvent = eventMarker.Header.IsSorted; timestamp = eventMarker.Header.TimeStamp; int sequenceNumber = eventMarker.Header.SequenceNumber; if (isSortedEvent) { thread.LastCachedEventTimestamp = timestamp; // sorted events are the only time the captureThreadId should change long captureThreadId = eventMarker.Header.CaptureThreadId; if (!_threads.TryGetValue(captureThreadId, out thread)) { thread = new EventCacheThread(); thread.SequenceNumber = sequenceNumber - 1; AddThread(captureThreadId, thread); } } int droppedEvents = (int)Math.Min(int.MaxValue, sequenceNumber - thread.SequenceNumber - 1); if (droppedEvents > 0) { OnEventsDropped?.Invoke(droppedEvents); } else { // When a thread id is recycled the sequenceNumber can abruptly reset to 1 which // makes droppedEvents go negative Debug.Assert(droppedEvents == 0 || sequenceNumber == 1); } thread.SequenceNumber = sequenceNumber; if (isSortedEvent) { SortAndDispatch(timestamp); OnEvent?.Invoke(ref eventMarker.Header); } else { thread.Events.Enqueue(eventMarker); } cursor += eventMarker.Header.TotalNonHeaderSize + eventMarker.Header.HeaderSize; EventMarker lastEvent = eventMarker; eventMarker = new EventMarker(buffer); eventMarker.Header = lastEvent.Header; } thread.LastCachedEventTimestamp = timestamp; }