Skip to content

Commit ca9cbbc

Browse files
committed
WIP: Support emitting array in dynamic event
1 parent 9d374b5 commit ca9cbbc

File tree

2 files changed

+179
-57
lines changed

2 files changed

+179
-57
lines changed

src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API.UnitTests/DynamicEventTests.cs

+52
Original file line numberDiff line numberDiff line change
@@ -319,5 +319,57 @@ public void TestReportingUnknownEvent()
319319
};
320320
test.Should().Throw<Exception>();
321321
}
322+
323+
[TestMethod]
324+
public void TestArray()
325+
{
326+
List<DynamicEventSchema> arraySchema = new List<DynamicEventSchema>
327+
{
328+
new DynamicEventSchema
329+
{
330+
DynamicEventName = "ArrayEventName",
331+
Fields = new List<KeyValuePair<string, Type>>
332+
{
333+
new KeyValuePair<string, Type>("version", typeof(ushort)),
334+
new KeyValuePair<string, Type>("Array1", typeof(ushort[])),
335+
new KeyValuePair<string, Type>("Number1", typeof(ulong)),
336+
new KeyValuePair<string, Type>("Array2", typeof(byte[])),
337+
new KeyValuePair<string, Type>("Number2", typeof(ulong)),
338+
}
339+
},
340+
};
341+
DynamicEventSchema.Set(arraySchema, false);
342+
List<GCDynamicEvent> dynamicEvents = new List<GCDynamicEvent>
343+
{
344+
new GCDynamicEvent(
345+
"ArrayEventName",
346+
DateTime.Now,
347+
// ver [ Array 1 ] [ Number 1 ] [ Array 2 ] [ Number 2 ]
348+
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 }
349+
)
350+
};
351+
dynamic index = new DynamicIndex(dynamicEvents);
352+
ushort[] array1 = (ushort[])index.ArrayEventName.Array1;
353+
ulong number1 = (ulong)index.ArrayEventName.Number1;
354+
byte[] array2 = (byte[])index.ArrayEventName.Array2;
355+
ulong number2 = (ulong)index.ArrayEventName.Number2;
356+
array1.Length.Should().Be(5);
357+
array2.Length.Should().Be(8);
358+
array1[0].Should().Be(1);
359+
array1[1].Should().Be(0);
360+
array1[2].Should().Be(0);
361+
array1[3].Should().Be(8);
362+
array1[4].Should().Be(6);
363+
number1.Should().Be(1);
364+
array2[0].Should().Be(2);
365+
array2[1].Should().Be(8);
366+
array2[2].Should().Be(9);
367+
array2[3].Should().Be(6);
368+
array2[4].Should().Be(3);
369+
array2[5].Should().Be(0);
370+
array2[6].Should().Be(3);
371+
array2[7].Should().Be(5);
372+
number2.Should().Be(2);
373+
}
322374
}
323375
}

src/benchmarks/gc/GC.Infrastructure/GC.Analysis.API/DynamicEvents/DynamicEvents.cs

+127-57
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,94 @@ public sealed class DynamicEventSchema
4444

4545
public int MaxOccurrence { get; init; } = 1;
4646

47+
internal static bool TryComputeOffset(byte[] payload, List<int> unadjustedArrayLengthOffsets, List<int> arrayElementSizes, int unadjustedOffset, out int adjustedOffset)
48+
{
49+
//
50+
// Any offsets on or before the first array content will have their
51+
// actual offset equals to the unadjusted offsets. In particular,
52+
// the offset to the first array's length is never adjusted. So we can
53+
// find the length of the first array.
54+
//
55+
int adjustment = 0;
56+
for (int i = 0; i < unadjustedArrayLengthOffsets.Count; i++)
57+
{
58+
int unadjustedArrayLengthOffset = unadjustedArrayLengthOffsets[i];
59+
if (unadjustedOffset > unadjustedArrayLengthOffset)
60+
{
61+
if (payload.Length <= unadjustedArrayLengthOffset)
62+
{
63+
adjustedOffset = 0;
64+
return false;
65+
}
66+
67+
//
68+
// If we had a second array, the second arrays unadjusted offsets will
69+
// be earlier than its actual offset, but we know how to adjust it. So
70+
// we can get the actual offset to the second array's length.
71+
//
72+
byte arrayLength = payload[unadjustedArrayLengthOffset + adjustment];
73+
adjustment += arrayLength * arrayElementSizes[i];
74+
}
75+
else
76+
{
77+
//
78+
// If the offset are are looking for is not after the kth array length
79+
// Then we should stop computing the adjustment
80+
//
81+
break;
82+
}
83+
}
84+
85+
adjustedOffset = unadjustedOffset + adjustment;
86+
return true;
87+
}
88+
89+
internal static int ComputeOffset(byte[] payload, List<int> unadjustedArrayLengthOffsets, List<int> arrayElementSizes, int unadjustedOffset)
90+
{
91+
if (TryComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, unadjustedOffset, out int adjustedOffset))
92+
{
93+
return adjustedOffset;
94+
}
95+
else
96+
{
97+
throw new Exception("Fail to compute offset, this should not happen");
98+
}
99+
}
100+
101+
internal static bool IsSupportedPrimitiveType(Type type)
102+
{
103+
return
104+
(type == typeof(ushort)) ||
105+
(type == typeof(uint)) ||
106+
(type == typeof(float)) ||
107+
(type == typeof(ulong)) ||
108+
(type == typeof(byte)) ||
109+
(type == typeof(bool)) ||
110+
false;
111+
}
112+
113+
internal static int Size(Type type)
114+
{
115+
if (type == typeof(ushort)) { return 2; }
116+
else if (type == typeof(uint)) { return 4; }
117+
else if (type == typeof(float)) { return 8; }
118+
else if (type == typeof(ulong)) { return 8; }
119+
else if (type == typeof(byte)) { return 1; }
120+
else if (type == typeof(bool)) { return 1; }
121+
else { throw new Exception("Wrong type"); }
122+
}
123+
124+
internal static object Decode(Type type, byte[] payload, int offset)
125+
{
126+
if (type == typeof(ushort)) { return BitConverter.ToUInt16(payload, offset); }
127+
else if (type == typeof(uint)) { return BitConverter.ToUInt32(payload, offset); }
128+
else if (type == typeof(float)) { return BitConverter.ToSingle(payload, offset); }
129+
else if (type == typeof(ulong)) { return BitConverter.ToUInt64(payload, offset); }
130+
else if (type == typeof(byte)) { return payload[offset]; }
131+
else if (type == typeof(bool)) { return BitConverter.ToBoolean(payload, offset); }
132+
else { throw new Exception("Wrong type"); }
133+
}
134+
47135
internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bool allowPartialSchema = true)
48136
{
49137
if (DynamicEventSchemas != null && DynamicEventSchemas.ContainsKey(dynamicEventSchema.DynamicEventName))
@@ -61,6 +149,17 @@ internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bo
61149
}
62150
schema.MinOccurrence = dynamicEventSchema.MinOccurrence;
63151
schema.MaxOccurrence = dynamicEventSchema.MaxOccurrence;
152+
153+
//
154+
// With array, a field in an event no longer have a fixed offset.
155+
// Unadjusted offsets are offsets as if all the arrays are empty
156+
// This list will store the unadjusted offsets to array lengths
157+
//
158+
// This is sufficient to get to the actual offsets, once we have
159+
// the payload, see TryComputeOffset for more details.
160+
//
161+
List<int> unadjustedArrayLengthOffsets = new List<int>();
162+
List<int> arrayElementSizes = new List<int>();
64163
int offset = 0;
65164
foreach (KeyValuePair<string, Type> field in dynamicEventSchema.Fields)
66165
{
@@ -69,39 +168,42 @@ internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bo
69168
DynamicEventSchemas?.Clear();
70169
throw new Exception($"Provided event named {dynamicEventSchema.DynamicEventName} has a duplicated field named {field.Key}");
71170
}
72-
schema.Add(field.Key, new DynamicEventField { FieldOffset = offset, FieldType = field.Value });
171+
Func<byte[], object>? fieldFetcher = null;
73172

74-
if (field.Value == typeof(ushort))
75-
{
76-
offset += 2;
77-
}
78-
else if (field.Value == typeof(uint))
79-
{
80-
offset += 4;
81-
}
82-
else if (field.Value == typeof(float))
83-
{
84-
offset += 4;
85-
}
86-
else if (field.Value == typeof(ulong))
87-
{
88-
offset += 8;
89-
}
90-
else if (field.Value == typeof(byte))
173+
// The local variable makes sure we capture the value of the offset variable in the lambdas
174+
int currentOffset = offset;
175+
if (IsSupportedPrimitiveType(field.Value))
91176
{
92-
offset += 1;
177+
fieldFetcher = (payload) => Decode(field.Value, payload, ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, currentOffset));
178+
offset += Size(field.Value);
93179
}
94-
else if (field.Value == typeof(bool))
180+
else if (field.Value.IsArray && IsSupportedPrimitiveType(field.Value.GetElementType()))
95181
{
182+
Type elementType = field.Value.GetElementType();
183+
int elementSize = Size(elementType);
184+
fieldFetcher = (payload) =>
185+
{
186+
int unadjustedArrayLengthOffset = ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, currentOffset);
187+
int length = (int)payload[unadjustedArrayLengthOffset];
188+
Array result = Array.CreateInstance(elementType, length);
189+
for (int i = 0; i < length; i++)
190+
{
191+
result.SetValue(Decode(elementType, payload, unadjustedArrayLengthOffset + 1 + elementSize * i), i);
192+
}
193+
return result;
194+
};
195+
unadjustedArrayLengthOffsets.Add(offset);
196+
arrayElementSizes.Add(elementSize);
96197
offset += 1;
97198
}
98199
else
99200
{
100201
DynamicEventSchemas?.Clear();
101202
throw new Exception($"Provided event named {dynamicEventSchema.DynamicEventName} has a field named {field.Key} using an unsupported type {field.Value}");
102203
}
204+
schema.Add(field.Key, new DynamicEventField { FieldFetcher = fieldFetcher });
103205
}
104-
schema.Size = offset;
206+
schema.SizeValidator = (payload) => payload.Length == ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, offset);
105207
return schema;
106208
}
107209

@@ -124,15 +226,14 @@ public static void Set(List<DynamicEventSchema> dynamicEventSchemas, bool allowP
124226

125227
internal sealed class DynamicEventField
126228
{
127-
public required int FieldOffset { get; init; }
128-
public required Type FieldType { get; init; }
229+
public required Func<byte[], object> FieldFetcher;
129230
}
130231

131232
internal sealed class CompiledSchema : Dictionary<string, DynamicEventField>
132233
{
133234
public int MinOccurrence { get; set; }
134235
public int MaxOccurrence { get; set; }
135-
public int Size { get; set; }
236+
public Func<byte[], bool> SizeValidator { get; set; }
136237
}
137238

138239
internal sealed class DynamicIndex : DynamicObject
@@ -212,44 +313,13 @@ public DynamicEventObject(GCDynamicEvent dynamicEvent, CompiledSchema schema)
212313
{
213314
this.name = dynamicEvent.Name;
214315
this.fieldValues = new Dictionary<string, object>();
215-
if (dynamicEvent.Payload.Length != schema.Size)
316+
if (!schema.SizeValidator(dynamicEvent.Payload))
216317
{
217318
throw new Exception($"Event {dynamicEvent.Name} does not have matching size");
218319
}
219320
foreach (KeyValuePair<string, DynamicEventField> field in schema)
220321
{
221-
object? value = null;
222-
int fieldOffset = field.Value.FieldOffset;
223-
Type fieldType = field.Value.FieldType;
224-
225-
if (fieldType == typeof(ushort))
226-
{
227-
value = BitConverter.ToUInt16(dynamicEvent.Payload, fieldOffset);
228-
}
229-
else if (fieldType == typeof(uint))
230-
{
231-
value = BitConverter.ToUInt32(dynamicEvent.Payload, fieldOffset);
232-
}
233-
else if (fieldType == typeof(float))
234-
{
235-
value = BitConverter.ToSingle(dynamicEvent.Payload, fieldOffset);
236-
}
237-
else if (fieldType == typeof(ulong))
238-
{
239-
value = BitConverter.ToUInt64(dynamicEvent.Payload, fieldOffset);
240-
}
241-
else if (fieldType == typeof(byte))
242-
{
243-
value = dynamicEvent.Payload[fieldOffset];
244-
}
245-
else if (fieldType == typeof(bool))
246-
{
247-
value = BitConverter.ToBoolean(dynamicEvent.Payload, fieldOffset);
248-
}
249-
else
250-
{
251-
throw new Exception($"Provided schema has a field named {field.Key} using an unsupported type {fieldType}");
252-
}
322+
object value = field.Value.FieldFetcher(dynamicEvent.Payload);
253323
this.fieldValues.Add(field.Key, value);
254324
}
255325
this.fieldValues.Add("TimeStamp", dynamicEvent.TimeStamp);

0 commit comments

Comments
 (0)