コード例 #1
0
        public override bool Process()
        {
            if (_fileFormatVersionNumber >= 3)
            {
                // loop through the stream until we hit a null object.  Deserialization of
                // EventPipeEventBlocks will cause dispatch to happen.
                // ReadObject uses registered factories and recognizes types by names, then derserializes them with FromStream
                while (_deserializer.ReadObject() != null)
                {
                }
            }
#if SUPPORT_V1_V2
            else
            {
                PinnedStreamReader deserializerReader = (PinnedStreamReader)_deserializer.Reader;
                while (deserializerReader.Current < _endOfEventStream)
                {
                    TraceEventNativeMethods.EVENT_RECORD *eventRecord = ReadEvent(deserializerReader);
                    if (eventRecord != null)
                    {
                        // in the code below we set sessionEndTimeQPC to be the timestamp of the last event.
                        // Thus the new timestamp should be later, and not more than 1 day later.
                        Debug.Assert(sessionEndTimeQPC <= eventRecord->EventHeader.TimeStamp);
                        Debug.Assert(sessionEndTimeQPC == 0 || eventRecord->EventHeader.TimeStamp - sessionEndTimeQPC < _QPCFreq * 24 * 3600);

                        var traceEvent = Lookup(eventRecord);
                        Dispatch(traceEvent);
                        sessionEndTimeQPC = eventRecord->EventHeader.TimeStamp;
                    }
                }
            }
#endif
            return(true);
        }
コード例 #2
0
        private TraceEventNativeMethods.EVENT_RECORD *ReadEvent(PinnedStreamReader reader)
        {
            // Guess that the event is < 1000 bytes or whatever is left in the stream.
            int eventSizeGuess = Math.Min(1000, _endOfEventStream.Sub(reader.Current));
            EventPipeEventHeader *eventData = (EventPipeEventHeader *)reader.GetPointer(eventSizeGuess);

            // Basic sanity checks.  Are the timestamps and sizes sane.
            Debug.Assert(sessionEndTimeQPC <= eventData->TimeStamp);
            Debug.Assert(sessionEndTimeQPC == 0 || eventData->TimeStamp - sessionEndTimeQPC < _QPCFreq * 24 * 3600);
            Debug.Assert(0 <= eventData->PayloadSize && eventData->PayloadSize <= eventData->TotalEventSize);
            Debug.Assert(eventData->MetaDataId <= reader.Current);                              // IDs are the location in the of of the data, so it comes before
            Debug.Assert(0 < eventData->TotalEventSize && eventData->TotalEventSize < 0x20000); // TODO really should be 64K but BulkSurvivingObjectRanges needs fixing.

            if (eventSizeGuess < eventData->TotalEventSize)
            {
                eventData = (EventPipeEventHeader *)reader.GetPointer(eventData->TotalEventSize);
            }

            Debug.Assert(0 <= EventPipeEventHeader.StackBytesSize(eventData) && EventPipeEventHeader.StackBytesSize(eventData) <= eventData->TotalEventSize);
            // This asserts that the header size + payload + stackSize field + StackSize == TotalEventSize;
            Debug.Assert(eventData->PayloadSize + EventPipeEventHeader.HeaderSize + sizeof(int) + EventPipeEventHeader.StackBytesSize(eventData) == eventData->TotalEventSize);

            TraceEventNativeMethods.EVENT_RECORD *ret = null;
            EventPipeEventMetaData metaData;

            if (eventData->MetaDataId == 0)     // Is this a Meta-data event?
            {
                int         totalEventSize       = eventData->TotalEventSize;
                int         payloadSize          = eventData->PayloadSize;
                StreamLabel metaDataStreamOffset = reader.Current;  // Used as the 'id' for the meta-data
                // Note that this skip invalidates the eventData pointer, so it is important to pull any fields out we need first.
                reader.Skip(EventPipeEventHeader.HeaderSize);
                metaData = new EventPipeEventMetaData(reader, payloadSize, _fileFormatVersionNumber, PointerSize, _processId);
                _eventMetadataDictionary.Add(metaDataStreamOffset, metaData);
                _eventParser.AddTemplate(metaData);
                int stackBytes = reader.ReadInt32();        // Meta-data events should always have a empty stack.
                Debug.Assert(stackBytes == 0);

                // We have read all the bytes in the event
                Debug.Assert(reader.Current == metaDataStreamOffset.Add(totalEventSize));
            }
            else
            {
                if (_eventMetadataDictionary.TryGetValue(eventData->MetaDataId, out metaData))
                {
                    ret = metaData.GetEventRecordForEventData(eventData);
                }
                else
                {
                    Debug.Assert(false, "Warning can't find metaData for ID " + eventData->MetaDataId.ToString("x"));
                }
                reader.Skip(eventData->TotalEventSize);
            }

            return(ret);
        }
コード例 #3
0
        internal TraceEventNativeMethods.EVENT_RECORD *ReadEvent(PinnedStreamReader reader)
        {
            EventPipeEventHeader *eventData = (EventPipeEventHeader *)reader.GetPointer(EventPipeEventHeader.HeaderSize);

            eventData = (EventPipeEventHeader *)reader.GetPointer(eventData->TotalEventSize); // now we now the real size and get read entire event

            // Basic sanity checks.  Are the timestamps and sizes sane.
            Debug.Assert(sessionEndTimeQPC <= eventData->TimeStamp);
            Debug.Assert(sessionEndTimeQPC == 0 || eventData->TimeStamp - sessionEndTimeQPC < _QPCFreq * 24 * 3600);
            Debug.Assert(0 <= eventData->PayloadSize && eventData->PayloadSize <= eventData->TotalEventSize);
            Debug.Assert(0 < eventData->TotalEventSize && eventData->TotalEventSize < 0x20000);                               // TODO really should be 64K but BulkSurvivingObjectRanges needs fixing.
            Debug.Assert(_fileFormatVersionNumber < 3 ||
                         ((int)EventPipeEventHeader.PayloadBytes(eventData) % 4 == 0 && eventData->TotalEventSize % 4 == 0)); // ensure 4 byte alignment

            StreamLabel eventDataEnd = reader.Current.Add(eventData->TotalEventSize);

            Debug.Assert(0 <= EventPipeEventHeader.StackBytesSize(eventData) && EventPipeEventHeader.StackBytesSize(eventData) <= eventData->TotalEventSize);

            TraceEventNativeMethods.EVENT_RECORD *ret = null;
            if (eventData->IsMetadata())
            {
                int totalEventSize = eventData->TotalEventSize;
                int payloadSize    = eventData->PayloadSize;

                // Note that this skip invalidates the eventData pointer, so it is important to pull any fields out we need first.
                reader.Skip(EventPipeEventHeader.HeaderSize);

                StreamLabel metaDataEnd = reader.Current.Add(payloadSize);

                // Read in the header (The header does not include payload parameter information)
                var metaDataHeader = new EventPipeEventMetaDataHeader(reader, payloadSize, _fileFormatVersionNumber, PointerSize, _processId);
                _eventMetadataDictionary.Add(metaDataHeader.MetaDataId, metaDataHeader);

                // Tell the parser about this new event
                _eventParser.OnNewEventPipeEventDefinition(metaDataHeader, reader);
                Debug.Assert(reader.Current == metaDataEnd);    // We should have read all the meta-data.

                int stackBytes = reader.ReadInt32();
                Debug.Assert(stackBytes == 0, "Meta-data events should always have a empty stack");
            }
            else
            {
                if (_eventMetadataDictionary.TryGetValue(eventData->MetaDataId, out var metaData))
                {
                    ret = metaData.GetEventRecordForEventData(eventData);
                }
                else
                {
                    Debug.Assert(false, "Warning can't find metaData for ID " + eventData->MetaDataId.ToString("x"));
                }
            }

            reader.Goto(eventDataEnd);

            return(ret);
        }
コード例 #4
0
        internal TraceEventNativeMethods.EVENT_RECORD *ReadEvent(PinnedStreamReader reader)
        {
            EventPipeEventHeader *eventData = (EventPipeEventHeader *)reader.GetPointer(EventPipeEventHeader.HeaderSize);

            eventData = (EventPipeEventHeader *)reader.GetPointer(eventData->TotalEventSize); // now we now the real size and get read entire event

            // Basic sanity checks.  Are the timestamps and sizes sane.
            Debug.Assert(sessionEndTimeQPC <= eventData->TimeStamp);
            Debug.Assert(sessionEndTimeQPC == 0 || eventData->TimeStamp - sessionEndTimeQPC < _QPCFreq * 24 * 3600);
            Debug.Assert(0 <= eventData->PayloadSize && eventData->PayloadSize <= eventData->TotalEventSize);
            Debug.Assert(0 < eventData->TotalEventSize && eventData->TotalEventSize < 0x20000);                               // TODO really should be 64K but BulkSurvivingObjectRanges needs fixing.
            Debug.Assert(_fileFormatVersionNumber < 3 ||
                         ((int)EventPipeEventHeader.PayloadBytes(eventData) % 4 == 0 && eventData->TotalEventSize % 4 == 0)); // ensure 4 byte alignment

            StreamLabel eventDataEnd = reader.Current.Add(eventData->TotalEventSize);

            Debug.Assert(0 <= EventPipeEventHeader.StackBytesSize(eventData) && EventPipeEventHeader.StackBytesSize(eventData) <= eventData->TotalEventSize);

            TraceEventNativeMethods.EVENT_RECORD *ret = null;;
            if (eventData->IsMetadata())
            {
                int totalEventSize = eventData->TotalEventSize;
                int payloadSize    = eventData->PayloadSize;

                // Note that this skip invalidates the eventData pointer, so it is important to pull any fields out we need first.
                reader.Skip(EventPipeEventHeader.HeaderSize);

                var metaData = new EventPipeEventMetaData(reader, payloadSize, _fileFormatVersionNumber, PointerSize, _processId);
                _eventMetadataDictionary.Add(metaData.MetaDataId, metaData);

                _eventParser.AddTemplate(metaData); // if we don't add the templates to this parse, we are going to have unhadled events (see https://github.com/Microsoft/perfview/issues/461)

                int stackBytes = reader.ReadInt32();
                Debug.Assert(stackBytes == 0, "Meta-data events should always have a empty stack");
            }
            else
            {
                if (_eventMetadataDictionary.TryGetValue(eventData->MetaDataId, out var metaData))
                {
                    ret = metaData.GetEventRecordForEventData(eventData);
                }
                else
                {
                    Debug.Assert(false, "Warning can't find metaData for ID " + eventData->MetaDataId.ToString("x"));
                }
            }

            reader.Goto(eventDataEnd);

            return(ret);
        }
コード例 #5
0
        public override bool Process()
        {
            PinnedStreamReader deserializerReader = (PinnedStreamReader)_deserializer.Reader;

            deserializerReader.Goto(_startEventOfStream);
            while (deserializerReader.Current < _endOfEventStream)
            {
                TraceEventNativeMethods.EVENT_RECORD *eventRecord = ReadEvent(deserializerReader);
                if (eventRecord != null)
                {
                    // in the code below we set sessionEndTimeQPC to be the timestamp of the last event.
                    // Thus the new timestamp should be later, and not more than 1 day later.
                    Debug.Assert(sessionEndTimeQPC <= eventRecord->EventHeader.TimeStamp);
                    Debug.Assert(sessionEndTimeQPC == 0 || eventRecord->EventHeader.TimeStamp - sessionEndTimeQPC < _QPCFreq * 24 * 3600);

                    TraceEvent event_ = Lookup(eventRecord);
                    Dispatch(event_);
                    sessionEndTimeQPC = eventRecord->EventHeader.TimeStamp;
                }
            }

            return(true);
        }
コード例 #6
0
        private DynamicTraceEventData.PayloadFetchClassInfo ParseFields(PinnedStreamReader reader, int numFields)
        {
            string[] fieldNames = new string[numFields];
            DynamicTraceEventData.PayloadFetch[] fieldFetches = new DynamicTraceEventData.PayloadFetch[numFields];

            ushort offset = 0;

            for (int fieldIndex = 0; fieldIndex < numFields; fieldIndex++)
            {
                DynamicTraceEventData.PayloadFetch payloadFetch = new DynamicTraceEventData.PayloadFetch();

                // Read the TypeCode for the current field.
                TypeCode typeCode = (TypeCode)reader.ReadInt32();

                // Fill out the payload fetch object based on the TypeCode.
                switch (typeCode)
                {
                case TypeCode.Boolean:
                {
                    payloadFetch.Type   = typeof(bool);
                    payloadFetch.Size   = 4;       // We follow windows conventions and use 4 bytes for bool.
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Char:
                {
                    payloadFetch.Type   = typeof(char);
                    payloadFetch.Size   = sizeof(char);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.SByte:
                {
                    payloadFetch.Type   = typeof(SByte);
                    payloadFetch.Size   = sizeof(SByte);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Byte:
                {
                    payloadFetch.Type   = typeof(byte);
                    payloadFetch.Size   = sizeof(byte);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Int16:
                {
                    payloadFetch.Type   = typeof(Int16);
                    payloadFetch.Size   = sizeof(Int16);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.UInt16:
                {
                    payloadFetch.Type   = typeof(UInt16);
                    payloadFetch.Size   = sizeof(UInt16);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Int32:
                {
                    payloadFetch.Type   = typeof(Int32);
                    payloadFetch.Size   = sizeof(Int32);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.UInt32:
                {
                    payloadFetch.Type   = typeof(UInt32);
                    payloadFetch.Size   = sizeof(UInt32);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Int64:
                {
                    payloadFetch.Type   = typeof(Int64);
                    payloadFetch.Size   = sizeof(Int64);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.UInt64:
                {
                    payloadFetch.Type   = typeof(UInt64);
                    payloadFetch.Size   = sizeof(UInt64);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Single:
                {
                    payloadFetch.Type   = typeof(Single);
                    payloadFetch.Size   = sizeof(Single);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Double:
                {
                    payloadFetch.Type   = typeof(Double);
                    payloadFetch.Size   = sizeof(Double);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Decimal:
                {
                    payloadFetch.Type   = typeof(Decimal);
                    payloadFetch.Size   = sizeof(Decimal);
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.DateTime:
                {
                    payloadFetch.Type   = typeof(DateTime);
                    payloadFetch.Size   = 8;
                    payloadFetch.Offset = offset;
                    break;
                }

                case EventPipeEventSource.GuidTypeCode:
                {
                    payloadFetch.Type   = typeof(Guid);
                    payloadFetch.Size   = 16;
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.String:
                {
                    payloadFetch.Type   = typeof(String);
                    payloadFetch.Size   = DynamicTraceEventData.NULL_TERMINATED;
                    payloadFetch.Offset = offset;
                    break;
                }

                case TypeCode.Object:
                {
                    // TypeCode.Object represents an embedded struct.

                    // Read the number of fields in the struct.  Each of these fields could be an embedded struct,
                    // but these embedded structs are still counted as single fields.  They will be expanded when they are handled.
                    int structFieldCount = reader.ReadInt32();
                    DynamicTraceEventData.PayloadFetchClassInfo embeddedStructClassInfo = ParseFields(reader, structFieldCount);
                    if (embeddedStructClassInfo == null)
                    {
                        throw new Exception("Unable to parse metadata for embedded struct.");
                    }
                    payloadFetch = DynamicTraceEventData.PayloadFetch.StructPayloadFetch(offset, embeddedStructClassInfo);
                    break;
                }

                default:
                {
                    throw new NotSupportedException($"{typeCode} is not supported.");
                }
                }

                // Read the string name of the event payload field.
                fieldNames[fieldIndex] = reader.ReadNullTerminatedUnicodeString();

                // Update the offset into the event for the next payload fetch.
                if (payloadFetch.Size >= DynamicTraceEventData.SPECIAL_SIZES || offset == ushort.MaxValue)
                {
                    offset = ushort.MaxValue;           // Indicate that the offset must be computed at run time.
                }
                else
                {
                    offset += payloadFetch.Size;
                }

                // Save the current payload fetch.
                fieldFetches[fieldIndex] = payloadFetch;
            }

            return(new DynamicTraceEventData.PayloadFetchClassInfo()
            {
                FieldNames = fieldNames,
                FieldFetches = fieldFetches
            });
        }
コード例 #7
0
        /// <summary>
        /// Given the EventPipe metaData header and a stream pointing at the serialized meta-data for the parameters for the
        /// event, create a new  DynamicTraceEventData that knows how to parse that event.
        /// ReaderForParameters.Current is advanced past the parameter information.
        /// </summary>
        private DynamicTraceEventData ReadEventParametersAndBuildTemplate(EventPipeEventMetaDataHeader eventMetaDataHeader, PinnedStreamReader readerForParameters)
        {
            int    opcode;
            string opcodeName;

            GetOpcodeFromEventName(eventMetaDataHeader.EventName, out opcode, out opcodeName);

            DynamicTraceEventData.PayloadFetchClassInfo classInfo = null;
            DynamicTraceEventData template = new DynamicTraceEventData(null, eventMetaDataHeader.EventId, 0, eventMetaDataHeader.EventName, Guid.Empty, opcode, opcodeName, eventMetaDataHeader.ProviderId, eventMetaDataHeader.ProviderName);

            // If the metadata contains no parameter metadata, don't attempt to read it.
            if (!eventMetaDataHeader.ContainsParameterMetadata)
            {
                template.payloadNames   = new string[0];
                template.payloadFetches = new DynamicTraceEventData.PayloadFetch[0];
                return(template);
            }

            // Read the count of event payload fields.
            int fieldCount = readerForParameters.ReadInt32();

            Debug.Assert(0 <= fieldCount && fieldCount < 0x4000);

            if (fieldCount > 0)
            {
                // Recursively parse the metadata, building up a list of payload names and payload field fetch objects.
                classInfo = ParseFields(readerForParameters, fieldCount);
            }
            else
            {
                classInfo = new DynamicTraceEventData.PayloadFetchClassInfo()
                {
                    FieldNames   = new string[0],
                    FieldFetches = new DynamicTraceEventData.PayloadFetch[0]
                };
            }

            template.payloadNames   = classInfo.FieldNames;
            template.payloadFetches = classInfo.FieldFetches;

            return(template);
        }
コード例 #8
0
        /// <summary>
        /// Give meta-data for an event, passed as a EventPipeEventMetaDataHeader and readerForParameters
        /// which is a StreamReader that points at serialized parameter information, decode the meta-data
        /// and record a template associated with this source. The readerForParameters is advanced beyond
        /// the event parameters information.
        /// </summary>
        internal void OnNewEventPipeEventDefinition(EventPipeEventMetaDataHeader eventMetaDataHeader, PinnedStreamReader readerForParameters)
        {
            // Convert the EventPipe data into a DynamicTraceEventData, which is how TraceEvent does dynamic event parsing.
            DynamicTraceEventData template = ReadEventParametersAndBuildTemplate(eventMetaDataHeader, readerForParameters);

            _metadataTemplates[template] = template;
        }
コード例 #9
0
        /// <summary>
        /// Give meta-data for an event, passed as a EventPipeEventMetaDataHeader and readerForParameters
        /// which is a StreamReader that points at serialized parameter information, decode the meta-data
        /// and register the meta-data with the TraceEventParser infrastruture.   The readerForParameters
        /// is advanced beyond the event parameters information.
        /// </summary>
        internal void OnNewEventPipeEventDefinition(EventPipeEventMetaDataHeader eventMetaDataHeader, PinnedStreamReader readerForParameters)
        {
            // Convert the EventPipe data into a DynamicTraceEventData, which is how TraceEvent does dynamic event parsing.
            DynamicTraceEventData template = ReadEventParametersAndBuildTemplate(eventMetaDataHeader, readerForParameters);

            OnNewEventDefintion(template, mayHaveExistedBefore: true);
        }
コード例 #10
0
        /// <summary>
        /// Creates a new MetaData instance from the serialized data at the current position of 'reader'
        /// of length 'length'.   This typically points at the PAYLOAD AREA of a meta-data events)
        /// 'fileFormatVersionNumber' is the version number of the file as a whole
        /// (since that affects the parsing of this data) and 'processID' is the process ID for the
        /// whole stream (since it needs to be put into the EVENT_RECORD.
        ///
        /// When this constructor returns the reader has read all data given to it (thus it has
        /// move the read pointer by 'length')
        /// </summary>
        public EventPipeEventMetaData(PinnedStreamReader reader, int length, int fileFormatVersionNumber, int pointerSize, int processId)
        {
            StreamLabel eventDataEnd = reader.Current.Add(length);

            _eventRecord = (TraceEventNativeMethods.EVENT_RECORD *)Marshal.AllocHGlobal(sizeof(TraceEventNativeMethods.EVENT_RECORD));
            ClearMemory(_eventRecord, sizeof(TraceEventNativeMethods.EVENT_RECORD));

            if (pointerSize == 4)
            {
                _eventRecord->EventHeader.Flags = TraceEventNativeMethods.EVENT_HEADER_FLAG_32_BIT_HEADER;
            }
            else
            {
                _eventRecord->EventHeader.Flags = TraceEventNativeMethods.EVENT_HEADER_FLAG_64_BIT_HEADER;
            }

            _eventRecord->EventHeader.ProcessId = processId;

            StreamLabel metaDataStart = reader.Current;

            if (fileFormatVersionNumber == 1)
            {
                _eventRecord->EventHeader.ProviderId = reader.ReadGuid();
            }
            else
            {
                ProviderName = reader.ReadNullTerminatedUnicodeString();
                _eventRecord->EventHeader.ProviderId = GetProviderGuidFromProviderName(ProviderName);
            }

            var eventId = (ushort)reader.ReadInt32();

            _eventRecord->EventHeader.Id = eventId;
            Debug.Assert(_eventRecord->EventHeader.Id == eventId);  // No truncation

            var version = reader.ReadInt32();

            _eventRecord->EventHeader.Version = (byte)version;
            Debug.Assert(_eventRecord->EventHeader.Version == version);  // No truncation

            if (fileFormatVersionNumber >= 3)
            {
                long keywords = reader.ReadInt64();
                _eventRecord->EventHeader.Keyword = (ulong)keywords;
            }

            int metadataLength = reader.ReadInt32();

            Debug.Assert(0 <= metadataLength && metadataLength < length);
            if (0 < metadataLength)
            {
                // TODO why do we repeat the event number it is redundant.
                eventId = (ushort)reader.ReadInt32();
                Debug.Assert(_eventRecord->EventHeader.Id == eventId);  // No truncation
                EventName = reader.ReadNullTerminatedUnicodeString();
                Debug.Assert(EventName.Length < length / 2);

                // Deduce the opcode from the name.
                if (EventName.EndsWith("Start", StringComparison.OrdinalIgnoreCase))
                {
                    _eventRecord->EventHeader.Opcode = (byte)TraceEventOpcode.Start;
                }
                else if (EventName.EndsWith("Stop", StringComparison.OrdinalIgnoreCase))
                {
                    _eventRecord->EventHeader.Opcode = (byte)TraceEventOpcode.Stop;
                }

                _eventRecord->EventHeader.Keyword = (ulong)reader.ReadInt64();

                // TODO why do we repeat the event number it is redundant.
                version = reader.ReadInt32();
                Debug.Assert(_eventRecord->EventHeader.Version == version);     // No truncation

                _eventRecord->EventHeader.Level = (byte)reader.ReadInt32();
                Debug.Assert(_eventRecord->EventHeader.Level <= 5);

                // Fetch the parameter information
                int parameterCount = reader.ReadInt32();
                Debug.Assert(0 <= parameterCount && parameterCount < length / 8); // Each parameter takes at least 8 bytes.
                if (parameterCount > 0)
                {
                    ParameterDefinitions = new Tuple <TypeCode, string> [parameterCount];
                    for (int i = 0; i < parameterCount; i++)
                    {
                        var type = (TypeCode)reader.ReadInt32();
                        Debug.Assert((uint)type < 24);      // There only a handful of type codes.
                        var name = reader.ReadNullTerminatedUnicodeString();
                        ParameterDefinitions[i] = new Tuple <TypeCode, string>(type, name);
                        Debug.Assert(reader.Current <= eventDataEnd);
                    }
                }
            }
            Debug.Assert(reader.Current == eventDataEnd);
        }