Skip to content

Commit

Permalink
WIP: Support emitting array in dynamic event
Browse files Browse the repository at this point in the history
  • Loading branch information
cshung committed Aug 5, 2024
1 parent 9d374b5 commit ca9cbbc
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -319,5 +319,57 @@ public void TestReportingUnknownEvent()
};
test.Should().Throw<Exception>();
}

[TestMethod]
public void TestArray()
{
List<DynamicEventSchema> arraySchema = new List<DynamicEventSchema>
{
new DynamicEventSchema
{
DynamicEventName = "ArrayEventName",
Fields = new List<KeyValuePair<string, Type>>
{
new KeyValuePair<string, Type>("version", typeof(ushort)),
new KeyValuePair<string, Type>("Array1", typeof(ushort[])),
new KeyValuePair<string, Type>("Number1", typeof(ulong)),
new KeyValuePair<string, Type>("Array2", typeof(byte[])),
new KeyValuePair<string, Type>("Number2", typeof(ulong)),
}
},
};
DynamicEventSchema.Set(arraySchema, false);
List<GCDynamicEvent> dynamicEvents = new List<GCDynamicEvent>
{
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,94 @@ public sealed class DynamicEventSchema

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

internal static bool TryComputeOffset(byte[] payload, List<int> unadjustedArrayLengthOffsets, List<int> 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<int> unadjustedArrayLengthOffsets, List<int> 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))
Expand All @@ -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<int> unadjustedArrayLengthOffsets = new List<int>();
List<int> arrayElementSizes = new List<int>();
int offset = 0;
foreach (KeyValuePair<string, Type> field in dynamicEventSchema.Fields)
{
Expand All @@ -69,39 +168,42 @@ 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<byte[], object>? 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
{
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;
}

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

internal sealed class DynamicEventField
{
public required int FieldOffset { get; init; }
public required Type FieldType { get; init; }
public required Func<byte[], object> FieldFetcher;
}

internal sealed class CompiledSchema : Dictionary<string, DynamicEventField>
{
public int MinOccurrence { get; set; }
public int MaxOccurrence { get; set; }
public int Size { get; set; }
public Func<byte[], bool> SizeValidator { get; set; }
}

internal sealed class DynamicIndex : DynamicObject
Expand Down Expand Up @@ -212,44 +313,13 @@ public DynamicEventObject(GCDynamicEvent dynamicEvent, CompiledSchema schema)
{
this.name = dynamicEvent.Name;
this.fieldValues = new Dictionary<string, object>();
if (dynamicEvent.Payload.Length != schema.Size)
if (!schema.SizeValidator(dynamicEvent.Payload))
{
throw new Exception($"Event {dynamicEvent.Name} does not have matching size");
}
foreach (KeyValuePair<string, DynamicEventField> 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);
Expand Down

0 comments on commit ca9cbbc

Please sign in to comment.