From ca9cbbcf464d0396be5c55e60ea0e861e8e8a3d2 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Fri, 2 Aug 2024 11:13:32 -0700 Subject: [PATCH] WIP: Support emitting array in dynamic event --- .../DynamicEventTests.cs | 52 +++++ .../DynamicEvents/DynamicEvents.cs | 184 ++++++++++++------ 2 files changed, 179 insertions(+), 57 deletions(-) diff --git a/src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API.UnitTests/DynamicEventTests.cs b/src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API.UnitTests/DynamicEventTests.cs index 3e1c59681b5..6478f74e12c 100644 --- a/src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API.UnitTests/DynamicEventTests.cs +++ b/src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API.UnitTests/DynamicEventTests.cs @@ -319,5 +319,57 @@ public void TestReportingUnknownEvent() }; test.Should().Throw(); } + + [TestMethod] + public void TestArray() + { + List arraySchema = new List + { + new DynamicEventSchema + { + DynamicEventName = "ArrayEventName", + Fields = new List> + { + new KeyValuePair("version", typeof(ushort)), + new KeyValuePair("Array1", typeof(ushort[])), + new KeyValuePair("Number1", typeof(ulong)), + new KeyValuePair("Array2", typeof(byte[])), + new KeyValuePair("Number2", typeof(ulong)), + } + }, + }; + DynamicEventSchema.Set(arraySchema, false); + List dynamicEvents = new List + { + new GCDynamicEvent( + "ArrayEventName", + DateTime.Now, + // ver [ Array 1 ] [ Number 1 ] [ Array 2 ] [ Number 2 ] + new byte[] { 1, 0, 5, 1, 0, 0, 0, 0, 0, 8, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 8, 2, 8, 9, 6, 3, 0, 3, 5, 2, 0, 0, 0, 0, 0, 0, 0 } + ) + }; + dynamic index = new DynamicIndex(dynamicEvents); + ushort[] array1 = (ushort[])index.ArrayEventName.Array1; + ulong number1 = (ulong)index.ArrayEventName.Number1; + byte[] array2 = (byte[])index.ArrayEventName.Array2; + ulong number2 = (ulong)index.ArrayEventName.Number2; + array1.Length.Should().Be(5); + array2.Length.Should().Be(8); + array1[0].Should().Be(1); + array1[1].Should().Be(0); + array1[2].Should().Be(0); + array1[3].Should().Be(8); + array1[4].Should().Be(6); + number1.Should().Be(1); + array2[0].Should().Be(2); + array2[1].Should().Be(8); + array2[2].Should().Be(9); + array2[3].Should().Be(6); + array2[4].Should().Be(3); + array2[5].Should().Be(0); + array2[6].Should().Be(3); + array2[7].Should().Be(5); + number2.Should().Be(2); + } } } diff --git a/src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API/DynamicEvents/DynamicEvents.cs b/src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API/DynamicEvents/DynamicEvents.cs index d827d6138b3..b918f16d15a 100644 --- a/src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API/DynamicEvents/DynamicEvents.cs +++ b/src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API/DynamicEvents/DynamicEvents.cs @@ -44,6 +44,94 @@ public sealed class DynamicEventSchema public int MaxOccurrence { get; init; } = 1; + internal static bool TryComputeOffset(byte[] payload, List unadjustedArrayLengthOffsets, List arrayElementSizes, int unadjustedOffset, out int adjustedOffset) + { + // + // Any offsets on or before the first array content will have their + // actual offset equals to the unadjusted offsets. In particular, + // the offset to the first array's length is never adjusted. So we can + // find the length of the first array. + // + int adjustment = 0; + for (int i = 0; i < unadjustedArrayLengthOffsets.Count; i++) + { + int unadjustedArrayLengthOffset = unadjustedArrayLengthOffsets[i]; + if (unadjustedOffset > unadjustedArrayLengthOffset) + { + if (payload.Length <= unadjustedArrayLengthOffset) + { + adjustedOffset = 0; + return false; + } + + // + // If we had a second array, the second arrays unadjusted offsets will + // be earlier than its actual offset, but we know how to adjust it. So + // we can get the actual offset to the second array's length. + // + byte arrayLength = payload[unadjustedArrayLengthOffset + adjustment]; + adjustment += arrayLength * arrayElementSizes[i]; + } + else + { + // + // If the offset are are looking for is not after the kth array length + // Then we should stop computing the adjustment + // + break; + } + } + + adjustedOffset = unadjustedOffset + adjustment; + return true; + } + + internal static int ComputeOffset(byte[] payload, List unadjustedArrayLengthOffsets, List arrayElementSizes, int unadjustedOffset) + { + if (TryComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, unadjustedOffset, out int adjustedOffset)) + { + return adjustedOffset; + } + else + { + throw new Exception("Fail to compute offset, this should not happen"); + } + } + + internal static bool IsSupportedPrimitiveType(Type type) + { + return + (type == typeof(ushort)) || + (type == typeof(uint)) || + (type == typeof(float)) || + (type == typeof(ulong)) || + (type == typeof(byte)) || + (type == typeof(bool)) || + false; + } + + internal static int Size(Type type) + { + if (type == typeof(ushort)) { return 2; } + else if (type == typeof(uint)) { return 4; } + else if (type == typeof(float)) { return 8; } + else if (type == typeof(ulong)) { return 8; } + else if (type == typeof(byte)) { return 1; } + else if (type == typeof(bool)) { return 1; } + else { throw new Exception("Wrong type"); } + } + + internal static object Decode(Type type, byte[] payload, int offset) + { + if (type == typeof(ushort)) { return BitConverter.ToUInt16(payload, offset); } + else if (type == typeof(uint)) { return BitConverter.ToUInt32(payload, offset); } + else if (type == typeof(float)) { return BitConverter.ToSingle(payload, offset); } + else if (type == typeof(ulong)) { return BitConverter.ToUInt64(payload, offset); } + else if (type == typeof(byte)) { return payload[offset]; } + else if (type == typeof(bool)) { return BitConverter.ToBoolean(payload, offset); } + else { throw new Exception("Wrong type"); } + } + internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bool allowPartialSchema = true) { if (DynamicEventSchemas != null && DynamicEventSchemas.ContainsKey(dynamicEventSchema.DynamicEventName)) @@ -61,6 +149,17 @@ internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bo } schema.MinOccurrence = dynamicEventSchema.MinOccurrence; schema.MaxOccurrence = dynamicEventSchema.MaxOccurrence; + + // + // With array, a field in an event no longer have a fixed offset. + // Unadjusted offsets are offsets as if all the arrays are empty + // This list will store the unadjusted offsets to array lengths + // + // This is sufficient to get to the actual offsets, once we have + // the payload, see TryComputeOffset for more details. + // + List unadjustedArrayLengthOffsets = new List(); + List arrayElementSizes = new List(); int offset = 0; foreach (KeyValuePair field in dynamicEventSchema.Fields) { @@ -69,30 +168,32 @@ internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bo DynamicEventSchemas?.Clear(); throw new Exception($"Provided event named {dynamicEventSchema.DynamicEventName} has a duplicated field named {field.Key}"); } - schema.Add(field.Key, new DynamicEventField { FieldOffset = offset, FieldType = field.Value }); + Func? fieldFetcher = null; - if (field.Value == typeof(ushort)) - { - offset += 2; - } - else if (field.Value == typeof(uint)) - { - offset += 4; - } - else if (field.Value == typeof(float)) - { - offset += 4; - } - else if (field.Value == typeof(ulong)) - { - offset += 8; - } - else if (field.Value == typeof(byte)) + // The local variable makes sure we capture the value of the offset variable in the lambdas + int currentOffset = offset; + if (IsSupportedPrimitiveType(field.Value)) { - offset += 1; + fieldFetcher = (payload) => Decode(field.Value, payload, ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, currentOffset)); + offset += Size(field.Value); } - else if (field.Value == typeof(bool)) + else if (field.Value.IsArray && IsSupportedPrimitiveType(field.Value.GetElementType())) { + Type elementType = field.Value.GetElementType(); + int elementSize = Size(elementType); + fieldFetcher = (payload) => + { + int unadjustedArrayLengthOffset = ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, currentOffset); + int length = (int)payload[unadjustedArrayLengthOffset]; + Array result = Array.CreateInstance(elementType, length); + for (int i = 0; i < length; i++) + { + result.SetValue(Decode(elementType, payload, unadjustedArrayLengthOffset + 1 + elementSize * i), i); + } + return result; + }; + unadjustedArrayLengthOffsets.Add(offset); + arrayElementSizes.Add(elementSize); offset += 1; } else @@ -100,8 +201,9 @@ internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bo DynamicEventSchemas?.Clear(); throw new Exception($"Provided event named {dynamicEventSchema.DynamicEventName} has a field named {field.Key} using an unsupported type {field.Value}"); } + schema.Add(field.Key, new DynamicEventField { FieldFetcher = fieldFetcher }); } - schema.Size = offset; + schema.SizeValidator = (payload) => payload.Length == ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, offset); return schema; } @@ -124,15 +226,14 @@ public static void Set(List dynamicEventSchemas, bool allowP internal sealed class DynamicEventField { - public required int FieldOffset { get; init; } - public required Type FieldType { get; init; } + public required Func FieldFetcher; } internal sealed class CompiledSchema : Dictionary { public int MinOccurrence { get; set; } public int MaxOccurrence { get; set; } - public int Size { get; set; } + public Func SizeValidator { get; set; } } internal sealed class DynamicIndex : DynamicObject @@ -212,44 +313,13 @@ public DynamicEventObject(GCDynamicEvent dynamicEvent, CompiledSchema schema) { this.name = dynamicEvent.Name; this.fieldValues = new Dictionary(); - if (dynamicEvent.Payload.Length != schema.Size) + if (!schema.SizeValidator(dynamicEvent.Payload)) { throw new Exception($"Event {dynamicEvent.Name} does not have matching size"); } foreach (KeyValuePair field in schema) { - object? value = null; - int fieldOffset = field.Value.FieldOffset; - Type fieldType = field.Value.FieldType; - - if (fieldType == typeof(ushort)) - { - value = BitConverter.ToUInt16(dynamicEvent.Payload, fieldOffset); - } - else if (fieldType == typeof(uint)) - { - value = BitConverter.ToUInt32(dynamicEvent.Payload, fieldOffset); - } - else if (fieldType == typeof(float)) - { - value = BitConverter.ToSingle(dynamicEvent.Payload, fieldOffset); - } - else if (fieldType == typeof(ulong)) - { - value = BitConverter.ToUInt64(dynamicEvent.Payload, fieldOffset); - } - else if (fieldType == typeof(byte)) - { - value = dynamicEvent.Payload[fieldOffset]; - } - else if (fieldType == typeof(bool)) - { - value = BitConverter.ToBoolean(dynamicEvent.Payload, fieldOffset); - } - else - { - throw new Exception($"Provided schema has a field named {field.Key} using an unsupported type {fieldType}"); - } + object value = field.Value.FieldFetcher(dynamicEvent.Payload); this.fieldValues.Add(field.Key, value); } this.fieldValues.Add("TimeStamp", dynamicEvent.TimeStamp);