From a6bac6273b3e3a5c61c0bdf82f076fd89e508a07 Mon Sep 17 00:00:00 2001 From: Simon Hornby Date: Mon, 9 Feb 2026 16:15:57 +0200 Subject: [PATCH 1/6] feat: implement counter, gauge and histogram --- dotnet-engine/Yggdrasil.Engine/Flat.cs | 96 +++ dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs | 111 ++- .../yggdrasil/messaging/DefineGauge.cs | 67 ++ .../yggdrasil/messaging/DefineHistogram.cs | 84 +++ .../yggdrasil/messaging/IncCounter.cs | 73 ++ .../yggdrasil/messaging/ObserveHistogram.cs | 73 ++ .../yggdrasil/messaging/SetGauge.cs | 73 ++ flat-buffer-defs/enabled-message.fbs | 31 +- .../src/flat/enabled-message_generated.rs | 638 ++++++++++++++++++ yggdrasilffi/src/flat/mod.rs | 215 +++++- 10 files changed, 1457 insertions(+), 4 deletions(-) create mode 100644 dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/DefineGauge.cs create mode 100644 dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/DefineHistogram.cs create mode 100644 dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/IncCounter.cs create mode 100644 dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/ObserveHistogram.cs create mode 100644 dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/SetGauge.cs diff --git a/dotnet-engine/Yggdrasil.Engine/Flat.cs b/dotnet-engine/Yggdrasil.Engine/Flat.cs index 0debcee..2196f46 100644 --- a/dotnet-engine/Yggdrasil.Engine/Flat.cs +++ b/dotnet-engine/Yggdrasil.Engine/Flat.cs @@ -17,6 +17,11 @@ static Flat() built_in_strategies = Marshal.GetDelegateForFunctionPointer(NativeLibLoader.LoadFunctionPointer(_libHandle, "flat_built_in_strategies")); get_metrics = Marshal.GetDelegateForFunctionPointer(NativeLibLoader.LoadFunctionPointer(_libHandle, "flat_get_metrics")); define_counter = Marshal.GetDelegateForFunctionPointer(NativeLibLoader.LoadFunctionPointer(_libHandle, "flat_define_counter")); + inc_counter = Marshal.GetDelegateForFunctionPointer(NativeLibLoader.LoadFunctionPointer(_libHandle, "flat_inc_counter")); + define_gauge = Marshal.GetDelegateForFunctionPointer(NativeLibLoader.LoadFunctionPointer(_libHandle, "flat_define_gauge")); + set_gauge = Marshal.GetDelegateForFunctionPointer(NativeLibLoader.LoadFunctionPointer(_libHandle, "flat_set_gauge")); + define_histogram = Marshal.GetDelegateForFunctionPointer(NativeLibLoader.LoadFunctionPointer(_libHandle, "flat_define_histogram")); + observe_histogram = Marshal.GetDelegateForFunctionPointer(NativeLibLoader.LoadFunctionPointer(_libHandle, "flat_observe_histogram")); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -41,6 +46,16 @@ static Flat() [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate Buf DefineCounterDelegate(IntPtr enginePtr, IntPtr messagePtr, nuint messageLen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate Buf IncCounterDelegate(IntPtr enginePtr, IntPtr messagePtr, nuint messageLen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate Buf DefineGaugeDelegate(IntPtr enginePtr, IntPtr messagePtr, nuint messageLen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate Buf SetGaugeDelegate(IntPtr enginePtr, IntPtr messagePtr, nuint messageLen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate Buf DefineHistogramDelegate(IntPtr enginePtr, IntPtr messagePtr, nuint messageLen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate Buf ObserveHistogramDelegate(IntPtr enginePtr, IntPtr messagePtr, nuint messageLen); private static readonly TakeStateDelegate take_state; private static readonly FreeBufferDelegate free_buffer; @@ -51,6 +66,11 @@ static Flat() private static readonly GetMetricsDelegate get_metrics; private static readonly DefineCounterDelegate define_counter; + private static readonly IncCounterDelegate inc_counter; + private static readonly DefineGaugeDelegate define_gauge; + private static readonly SetGaugeDelegate set_gauge; + private static readonly DefineHistogramDelegate define_histogram; + private static readonly ObserveHistogramDelegate observe_histogram; public static Buf TakeState(IntPtr ptr, string json) { @@ -86,6 +106,7 @@ public static Buf CheckVariant(IntPtr ptr, byte[] message) handle.Free(); } } + public static Buf ListKnownToggles(IntPtr ptr) { return list_known_toggles(ptr); @@ -116,6 +137,81 @@ public static Buf DefineCounter(IntPtr ptr, byte[] message) } } + public static Buf IncCounter(IntPtr ptr, byte[] message) + { + nuint len = (nuint)message.Length; + GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned); + try + { + IntPtr msgPtr = handle.AddrOfPinnedObject(); + return inc_counter(ptr, msgPtr, len); + } + finally + { + handle.Free(); + } + } + + public static Buf DefineGauge(IntPtr ptr, byte[] message) + { + nuint len = (nuint)message.Length; + GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned); + try + { + IntPtr msgPtr = handle.AddrOfPinnedObject(); + return define_gauge(ptr, msgPtr, len); + } + finally + { + handle.Free(); + } + } + + public static Buf SetGauge(IntPtr ptr, byte[] message) + { + nuint len = (nuint)message.Length; + GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned); + try + { + IntPtr msgPtr = handle.AddrOfPinnedObject(); + return set_gauge(ptr, msgPtr, len); + } + finally + { + handle.Free(); + } + } + + public static Buf DefineHistogram(IntPtr ptr, byte[] message) + { + nuint len = (nuint)message.Length; + GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned); + try + { + IntPtr msgPtr = handle.AddrOfPinnedObject(); + return define_histogram(ptr, msgPtr, len); + } + finally + { + handle.Free(); + } + } + + public static Buf ObserveHistogram(IntPtr ptr, byte[] message) + { + nuint len = (nuint)message.Length; + GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned); + try + { + IntPtr msgPtr = handle.AddrOfPinnedObject(); + return observe_histogram(ptr, msgPtr, len); + } + finally + { + handle.Free(); + } + } + public static void FreeBuf(Buf buf) { free_buffer(buf); diff --git a/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs b/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs index db4dc33..83c399f 100644 --- a/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs +++ b/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs @@ -64,6 +64,115 @@ public static byte[] CreateDefineCounterBuffer(FlatBufferBuilder builder, string return builder.SizedByteArray(); } + public static byte[] CreateIncCounterBuffer(FlatBufferBuilder builder, string name, long value, IDictionary? labels = null) + { + var nameOffset = builder.CreateString(name); + var labelsOffset = CreateSampleLabelsVector(builder, labels); + + IncCounter.StartIncCounter(builder); + IncCounter.AddName(builder, nameOffset); + IncCounter.AddValue(builder, value); + if (labelsOffset.HasValue) + { + IncCounter.AddLabels(builder, labelsOffset.Value); + } + + var incCounterMessage = IncCounter.EndIncCounter(builder); + builder.Finish(incCounterMessage.Value); + return builder.SizedByteArray(); + } + + public static byte[] CreateDefineGaugeBuffer(FlatBufferBuilder builder, string name, string help) + { + var nameOffset = builder.CreateString(name); + var helpOffset = builder.CreateString(help); + + DefineGauge.StartDefineGauge(builder); + DefineGauge.AddName(builder, nameOffset); + DefineGauge.AddHelp(builder, helpOffset); + + var defineGaugeMessage = DefineGauge.EndDefineGauge(builder); + builder.Finish(defineGaugeMessage.Value); + return builder.SizedByteArray(); + } + + public static byte[] CreateSetGaugeBuffer(FlatBufferBuilder builder, string name, double value, IDictionary? labels = null) + { + var nameOffset = builder.CreateString(name); + var labelsOffset = CreateSampleLabelsVector(builder, labels); + + SetGauge.StartSetGauge(builder); + SetGauge.AddName(builder, nameOffset); + SetGauge.AddValue(builder, value); + if (labelsOffset.HasValue) + { + SetGauge.AddLabels(builder, labelsOffset.Value); + } + + var setGaugeMessage = SetGauge.EndSetGauge(builder); + builder.Finish(setGaugeMessage.Value); + return builder.SizedByteArray(); + } + + public static byte[] CreateDefineHistogramBuffer(FlatBufferBuilder builder, string name, string help, IEnumerable? buckets = null) + { + var nameOffset = builder.CreateString(name); + var helpOffset = builder.CreateString(help); + var bucketArray = (buckets ?? Enumerable.Empty()).ToArray(); + + DefineHistogram.StartDefineHistogram(builder); + DefineHistogram.AddName(builder, nameOffset); + DefineHistogram.AddHelp(builder, helpOffset); + if (bucketArray.Length > 0) + { + var bucketsOffset = DefineHistogram.CreateBucketsVector(builder, bucketArray); + DefineHistogram.AddBuckets(builder, bucketsOffset); + } + + var defineHistogramMessage = DefineHistogram.EndDefineHistogram(builder); + builder.Finish(defineHistogramMessage.Value); + return builder.SizedByteArray(); + } + + public static byte[] CreateObserveHistogramBuffer(FlatBufferBuilder builder, string name, double value, IDictionary? labels = null) + { + var nameOffset = builder.CreateString(name); + var labelsOffset = CreateSampleLabelsVector(builder, labels); + + ObserveHistogram.StartObserveHistogram(builder); + ObserveHistogram.AddName(builder, nameOffset); + ObserveHistogram.AddValue(builder, value); + if (labelsOffset.HasValue) + { + ObserveHistogram.AddLabels(builder, labelsOffset.Value); + } + + var observeHistogramMessage = ObserveHistogram.EndObserveHistogram(builder); + builder.Finish(observeHistogramMessage.Value); + return builder.SizedByteArray(); + } + + private static VectorOffset? CreateSampleLabelsVector(FlatBufferBuilder builder, IDictionary? labels) + { + if (labels == null || labels.Count == 0) + { + return null; + } + + var labelEntries = new Offset[labels.Count]; + for (var i = 0; i < labels.Count; i++) + { + var kvp = labels.ElementAt(i); + labelEntries[i] = SampleLabelEntry.CreateSampleLabelEntry( + builder, + builder.CreateString(kvp.Key), + builder.CreateString(kvp.Value) + ); + } + + return IncCounter.CreateLabelsVector(builder, labelEntries); + } + internal static VectorOffset CreatePropertiesVector(FlatBufferBuilder builder, Context context) { var propertyEntries = new Offset[context.Properties?.Count ?? 0]; @@ -238,4 +347,4 @@ private static Dictionary GetVariantCounts(ToggleStats stats) .ToDictionary(x => x.Key, v => v.Value); } -} \ No newline at end of file +} diff --git a/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/DefineGauge.cs b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/DefineGauge.cs new file mode 100644 index 0000000..8c32499 --- /dev/null +++ b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/DefineGauge.cs @@ -0,0 +1,67 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace yggdrasil.messaging +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct DefineGauge : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static DefineGauge GetRootAsDefineGauge(ByteBuffer _bb) { return GetRootAsDefineGauge(_bb, new DefineGauge()); } + public static DefineGauge GetRootAsDefineGauge(ByteBuffer _bb, DefineGauge obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public DefineGauge __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public string Name { get { int o = __p.__offset(4); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetNameBytes() { return __p.__vector_as_span(4, 1); } +#else + public ArraySegment? GetNameBytes() { return __p.__vector_as_arraysegment(4); } +#endif + public byte[] GetNameArray() { return __p.__vector_as_array(4); } + public string Help { get { int o = __p.__offset(6); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetHelpBytes() { return __p.__vector_as_span(6, 1); } +#else + public ArraySegment? GetHelpBytes() { return __p.__vector_as_arraysegment(6); } +#endif + public byte[] GetHelpArray() { return __p.__vector_as_array(6); } + + public static Offset CreateDefineGauge(FlatBufferBuilder builder, + StringOffset nameOffset = default(StringOffset), + StringOffset helpOffset = default(StringOffset)) { + builder.StartTable(2); + DefineGauge.AddHelp(builder, helpOffset); + DefineGauge.AddName(builder, nameOffset); + return DefineGauge.EndDefineGauge(builder); + } + + public static void StartDefineGauge(FlatBufferBuilder builder) { builder.StartTable(2); } + public static void AddName(FlatBufferBuilder builder, StringOffset nameOffset) { builder.AddOffset(0, nameOffset.Value, 0); } + public static void AddHelp(FlatBufferBuilder builder, StringOffset helpOffset) { builder.AddOffset(1, helpOffset.Value, 0); } + public static Offset EndDefineGauge(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class DefineGaugeVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyString(tablePos, 4 /*Name*/, false) + && verifier.VerifyString(tablePos, 6 /*Help*/, false) + && verifier.VerifyTableEnd(tablePos); + } +} + +} diff --git a/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/DefineHistogram.cs b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/DefineHistogram.cs new file mode 100644 index 0000000..abe398b --- /dev/null +++ b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/DefineHistogram.cs @@ -0,0 +1,84 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace yggdrasil.messaging +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct DefineHistogram : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static DefineHistogram GetRootAsDefineHistogram(ByteBuffer _bb) { return GetRootAsDefineHistogram(_bb, new DefineHistogram()); } + public static DefineHistogram GetRootAsDefineHistogram(ByteBuffer _bb, DefineHistogram obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public DefineHistogram __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public string Name { get { int o = __p.__offset(4); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetNameBytes() { return __p.__vector_as_span(4, 1); } +#else + public ArraySegment? GetNameBytes() { return __p.__vector_as_arraysegment(4); } +#endif + public byte[] GetNameArray() { return __p.__vector_as_array(4); } + public string Help { get { int o = __p.__offset(6); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetHelpBytes() { return __p.__vector_as_span(6, 1); } +#else + public ArraySegment? GetHelpBytes() { return __p.__vector_as_arraysegment(6); } +#endif + public byte[] GetHelpArray() { return __p.__vector_as_array(6); } + public double Buckets(int j) { int o = __p.__offset(8); return o != 0 ? __p.bb.GetDouble(__p.__vector(o) + j * 8) : (double)0; } + public int BucketsLength { get { int o = __p.__offset(8); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetBucketsBytes() { return __p.__vector_as_span(8, 8); } +#else + public ArraySegment? GetBucketsBytes() { return __p.__vector_as_arraysegment(8); } +#endif + public double[] GetBucketsArray() { return __p.__vector_as_array(8); } + + public static Offset CreateDefineHistogram(FlatBufferBuilder builder, + StringOffset nameOffset = default(StringOffset), + StringOffset helpOffset = default(StringOffset), + VectorOffset bucketsOffset = default(VectorOffset)) { + builder.StartTable(3); + DefineHistogram.AddBuckets(builder, bucketsOffset); + DefineHistogram.AddHelp(builder, helpOffset); + DefineHistogram.AddName(builder, nameOffset); + return DefineHistogram.EndDefineHistogram(builder); + } + + public static void StartDefineHistogram(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddName(FlatBufferBuilder builder, StringOffset nameOffset) { builder.AddOffset(0, nameOffset.Value, 0); } + public static void AddHelp(FlatBufferBuilder builder, StringOffset helpOffset) { builder.AddOffset(1, helpOffset.Value, 0); } + public static void AddBuckets(FlatBufferBuilder builder, VectorOffset bucketsOffset) { builder.AddOffset(2, bucketsOffset.Value, 0); } + public static VectorOffset CreateBucketsVector(FlatBufferBuilder builder, double[] data) { builder.StartVector(8, data.Length, 8); for (int i = data.Length - 1; i >= 0; i--) builder.AddDouble(data[i]); return builder.EndVector(); } + public static VectorOffset CreateBucketsVectorBlock(FlatBufferBuilder builder, double[] data) { builder.StartVector(8, data.Length, 8); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateBucketsVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(8, data.Count, 8); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateBucketsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartBucketsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(8, numElems, 8); } + public static Offset EndDefineHistogram(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class DefineHistogramVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyString(tablePos, 4 /*Name*/, false) + && verifier.VerifyString(tablePos, 6 /*Help*/, false) + && verifier.VerifyVectorOfData(tablePos, 8 /*Buckets*/, 8 /*double*/, false) + && verifier.VerifyTableEnd(tablePos); + } +} + +} diff --git a/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/IncCounter.cs b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/IncCounter.cs new file mode 100644 index 0000000..8342150 --- /dev/null +++ b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/IncCounter.cs @@ -0,0 +1,73 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace yggdrasil.messaging +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct IncCounter : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static IncCounter GetRootAsIncCounter(ByteBuffer _bb) { return GetRootAsIncCounter(_bb, new IncCounter()); } + public static IncCounter GetRootAsIncCounter(ByteBuffer _bb, IncCounter obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public IncCounter __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public string Name { get { int o = __p.__offset(4); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetNameBytes() { return __p.__vector_as_span(4, 1); } +#else + public ArraySegment? GetNameBytes() { return __p.__vector_as_arraysegment(4); } +#endif + public byte[] GetNameArray() { return __p.__vector_as_array(4); } + public long Value { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetLong(o + __p.bb_pos) : (long)0; } } + public yggdrasil.messaging.SampleLabelEntry? Labels(int j) { int o = __p.__offset(8); return o != 0 ? (yggdrasil.messaging.SampleLabelEntry?)(new yggdrasil.messaging.SampleLabelEntry()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int LabelsLength { get { int o = __p.__offset(8); return o != 0 ? __p.__vector_len(o) : 0; } } + public yggdrasil.messaging.SampleLabelEntry? LabelsByKey(string key) { int o = __p.__offset(8); return o != 0 ? yggdrasil.messaging.SampleLabelEntry.__lookup_by_key(__p.__vector(o), key, __p.bb) : null; } + + public static Offset CreateIncCounter(FlatBufferBuilder builder, + StringOffset nameOffset = default(StringOffset), + long value = 0, + VectorOffset labelsOffset = default(VectorOffset)) { + builder.StartTable(3); + IncCounter.AddValue(builder, value); + IncCounter.AddLabels(builder, labelsOffset); + IncCounter.AddName(builder, nameOffset); + return IncCounter.EndIncCounter(builder); + } + + public static void StartIncCounter(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddName(FlatBufferBuilder builder, StringOffset nameOffset) { builder.AddOffset(0, nameOffset.Value, 0); } + public static void AddValue(FlatBufferBuilder builder, long value) { builder.AddLong(1, value, 0); } + public static void AddLabels(FlatBufferBuilder builder, VectorOffset labelsOffset) { builder.AddOffset(2, labelsOffset.Value, 0); } + public static VectorOffset CreateLabelsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartLabelsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static Offset EndIncCounter(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class IncCounterVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyString(tablePos, 4 /*Name*/, false) + && verifier.VerifyField(tablePos, 6 /*Value*/, 8 /*long*/, 8, false) + && verifier.VerifyVectorOfTables(tablePos, 8 /*Labels*/, yggdrasil.messaging.SampleLabelEntryVerify.Verify, false) + && verifier.VerifyTableEnd(tablePos); + } +} + +} diff --git a/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/ObserveHistogram.cs b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/ObserveHistogram.cs new file mode 100644 index 0000000..e917b99 --- /dev/null +++ b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/ObserveHistogram.cs @@ -0,0 +1,73 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace yggdrasil.messaging +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct ObserveHistogram : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static ObserveHistogram GetRootAsObserveHistogram(ByteBuffer _bb) { return GetRootAsObserveHistogram(_bb, new ObserveHistogram()); } + public static ObserveHistogram GetRootAsObserveHistogram(ByteBuffer _bb, ObserveHistogram obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public ObserveHistogram __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public string Name { get { int o = __p.__offset(4); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetNameBytes() { return __p.__vector_as_span(4, 1); } +#else + public ArraySegment? GetNameBytes() { return __p.__vector_as_arraysegment(4); } +#endif + public byte[] GetNameArray() { return __p.__vector_as_array(4); } + public double Value { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetDouble(o + __p.bb_pos) : (double)0.0; } } + public yggdrasil.messaging.SampleLabelEntry? Labels(int j) { int o = __p.__offset(8); return o != 0 ? (yggdrasil.messaging.SampleLabelEntry?)(new yggdrasil.messaging.SampleLabelEntry()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int LabelsLength { get { int o = __p.__offset(8); return o != 0 ? __p.__vector_len(o) : 0; } } + public yggdrasil.messaging.SampleLabelEntry? LabelsByKey(string key) { int o = __p.__offset(8); return o != 0 ? yggdrasil.messaging.SampleLabelEntry.__lookup_by_key(__p.__vector(o), key, __p.bb) : null; } + + public static Offset CreateObserveHistogram(FlatBufferBuilder builder, + StringOffset nameOffset = default(StringOffset), + double value = 0.0, + VectorOffset labelsOffset = default(VectorOffset)) { + builder.StartTable(3); + ObserveHistogram.AddValue(builder, value); + ObserveHistogram.AddLabels(builder, labelsOffset); + ObserveHistogram.AddName(builder, nameOffset); + return ObserveHistogram.EndObserveHistogram(builder); + } + + public static void StartObserveHistogram(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddName(FlatBufferBuilder builder, StringOffset nameOffset) { builder.AddOffset(0, nameOffset.Value, 0); } + public static void AddValue(FlatBufferBuilder builder, double value) { builder.AddDouble(1, value, 0.0); } + public static void AddLabels(FlatBufferBuilder builder, VectorOffset labelsOffset) { builder.AddOffset(2, labelsOffset.Value, 0); } + public static VectorOffset CreateLabelsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartLabelsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static Offset EndObserveHistogram(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class ObserveHistogramVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyString(tablePos, 4 /*Name*/, false) + && verifier.VerifyField(tablePos, 6 /*Value*/, 8 /*double*/, 8, false) + && verifier.VerifyVectorOfTables(tablePos, 8 /*Labels*/, yggdrasil.messaging.SampleLabelEntryVerify.Verify, false) + && verifier.VerifyTableEnd(tablePos); + } +} + +} diff --git a/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/SetGauge.cs b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/SetGauge.cs new file mode 100644 index 0000000..25a0b7e --- /dev/null +++ b/dotnet-engine/Yggdrasil.Engine/yggdrasil/messaging/SetGauge.cs @@ -0,0 +1,73 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace yggdrasil.messaging +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct SetGauge : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static SetGauge GetRootAsSetGauge(ByteBuffer _bb) { return GetRootAsSetGauge(_bb, new SetGauge()); } + public static SetGauge GetRootAsSetGauge(ByteBuffer _bb, SetGauge obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public SetGauge __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public string Name { get { int o = __p.__offset(4); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetNameBytes() { return __p.__vector_as_span(4, 1); } +#else + public ArraySegment? GetNameBytes() { return __p.__vector_as_arraysegment(4); } +#endif + public byte[] GetNameArray() { return __p.__vector_as_array(4); } + public double Value { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetDouble(o + __p.bb_pos) : (double)0.0; } } + public yggdrasil.messaging.SampleLabelEntry? Labels(int j) { int o = __p.__offset(8); return o != 0 ? (yggdrasil.messaging.SampleLabelEntry?)(new yggdrasil.messaging.SampleLabelEntry()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int LabelsLength { get { int o = __p.__offset(8); return o != 0 ? __p.__vector_len(o) : 0; } } + public yggdrasil.messaging.SampleLabelEntry? LabelsByKey(string key) { int o = __p.__offset(8); return o != 0 ? yggdrasil.messaging.SampleLabelEntry.__lookup_by_key(__p.__vector(o), key, __p.bb) : null; } + + public static Offset CreateSetGauge(FlatBufferBuilder builder, + StringOffset nameOffset = default(StringOffset), + double value = 0.0, + VectorOffset labelsOffset = default(VectorOffset)) { + builder.StartTable(3); + SetGauge.AddValue(builder, value); + SetGauge.AddLabels(builder, labelsOffset); + SetGauge.AddName(builder, nameOffset); + return SetGauge.EndSetGauge(builder); + } + + public static void StartSetGauge(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddName(FlatBufferBuilder builder, StringOffset nameOffset) { builder.AddOffset(0, nameOffset.Value, 0); } + public static void AddValue(FlatBufferBuilder builder, double value) { builder.AddDouble(1, value, 0.0); } + public static void AddLabels(FlatBufferBuilder builder, VectorOffset labelsOffset) { builder.AddOffset(2, labelsOffset.Value, 0); } + public static VectorOffset CreateLabelsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateLabelsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartLabelsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static Offset EndSetGauge(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class SetGaugeVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyString(tablePos, 4 /*Name*/, false) + && verifier.VerifyField(tablePos, 6 /*Value*/, 8 /*double*/, 8, false) + && verifier.VerifyVectorOfTables(tablePos, 8 /*Labels*/, yggdrasil.messaging.SampleLabelEntryVerify.Verify, false) + && verifier.VerifyTableEnd(tablePos); + } +} + +} diff --git a/flat-buffer-defs/enabled-message.fbs b/flat-buffer-defs/enabled-message.fbs index 5b84031..db452c4 100644 --- a/flat-buffer-defs/enabled-message.fbs +++ b/flat-buffer-defs/enabled-message.fbs @@ -169,8 +169,37 @@ table DefineCounter { help: string; } +table IncCounter { + name: string; + value: long; + labels: [SampleLabelEntry]; +} + +table DefineGauge { + name: string; + help: string; +} + +table SetGauge { + name: string; + value: double; + labels: [SampleLabelEntry]; +} + +table DefineHistogram { + name: string; + help: string; + buckets: [double]; +} + +table ObserveHistogram { + name: string; + value: double; + labels: [SampleLabelEntry]; +} + root_type Response; root_type ContextMessage; root_type MetricsResponse; root_type TakeStateResponse; -root_type VoidResponse; \ No newline at end of file +root_type VoidResponse; diff --git a/yggdrasilffi/src/flat/enabled-message_generated.rs b/yggdrasilffi/src/flat/enabled-message_generated.rs index 3cd437d..0ca3673 100644 --- a/yggdrasilffi/src/flat/enabled-message_generated.rs +++ b/yggdrasilffi/src/flat/enabled-message_generated.rs @@ -3894,6 +3894,644 @@ impl core::fmt::Debug for DefineCounter<'_> { ds.finish() } } +pub enum IncCounterOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct IncCounter<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for IncCounter<'a> { + type Inner = IncCounter<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> IncCounter<'a> { + pub const VT_NAME: flatbuffers::VOffsetT = 4; + pub const VT_VALUE: flatbuffers::VOffsetT = 6; + pub const VT_LABELS: flatbuffers::VOffsetT = 8; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + IncCounter { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args IncCounterArgs<'args> + ) -> flatbuffers::WIPOffset> { + let mut builder = IncCounterBuilder::new(_fbb); + builder.add_value(args.value); + if let Some(x) = args.labels { builder.add_labels(x); } + if let Some(x) = args.name { builder.add_name(x); } + builder.finish() + } + + + #[inline] + pub fn name(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>(IncCounter::VT_NAME, None)} + } + #[inline] + pub fn value(&self) -> i64 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(IncCounter::VT_VALUE, Some(0)).unwrap()} + } + #[inline] + pub fn labels(&self) -> Option>>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>>>(IncCounter::VT_LABELS, None)} + } +} + +impl flatbuffers::Verifiable for IncCounter<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::>("name", Self::VT_NAME, false)? + .visit_field::("value", Self::VT_VALUE, false)? + .visit_field::>>>("labels", Self::VT_LABELS, false)? + .finish(); + Ok(()) + } +} +pub struct IncCounterArgs<'a> { + pub name: Option>, + pub value: i64, + pub labels: Option>>>>, +} +impl<'a> Default for IncCounterArgs<'a> { + #[inline] + fn default() -> Self { + IncCounterArgs { + name: None, + value: 0, + labels: None, + } + } +} + +pub struct IncCounterBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> IncCounterBuilder<'a, 'b, A> { + #[inline] + pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::>(IncCounter::VT_NAME, name); + } + #[inline] + pub fn add_value(&mut self, value: i64) { + self.fbb_.push_slot::(IncCounter::VT_VALUE, value, 0); + } + #[inline] + pub fn add_labels(&mut self, labels: flatbuffers::WIPOffset>>>) { + self.fbb_.push_slot_always::>(IncCounter::VT_LABELS, labels); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> IncCounterBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + IncCounterBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for IncCounter<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("IncCounter"); + ds.field("name", &self.name()); + ds.field("value", &self.value()); + ds.field("labels", &self.labels()); + ds.finish() + } +} +pub enum DefineGaugeOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct DefineGauge<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for DefineGauge<'a> { + type Inner = DefineGauge<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> DefineGauge<'a> { + pub const VT_NAME: flatbuffers::VOffsetT = 4; + pub const VT_HELP: flatbuffers::VOffsetT = 6; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + DefineGauge { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args DefineGaugeArgs<'args> + ) -> flatbuffers::WIPOffset> { + let mut builder = DefineGaugeBuilder::new(_fbb); + if let Some(x) = args.help { builder.add_help(x); } + if let Some(x) = args.name { builder.add_name(x); } + builder.finish() + } + + + #[inline] + pub fn name(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>(DefineGauge::VT_NAME, None)} + } + #[inline] + pub fn help(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>(DefineGauge::VT_HELP, None)} + } +} + +impl flatbuffers::Verifiable for DefineGauge<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::>("name", Self::VT_NAME, false)? + .visit_field::>("help", Self::VT_HELP, false)? + .finish(); + Ok(()) + } +} +pub struct DefineGaugeArgs<'a> { + pub name: Option>, + pub help: Option>, +} +impl<'a> Default for DefineGaugeArgs<'a> { + #[inline] + fn default() -> Self { + DefineGaugeArgs { + name: None, + help: None, + } + } +} + +pub struct DefineGaugeBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> DefineGaugeBuilder<'a, 'b, A> { + #[inline] + pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::>(DefineGauge::VT_NAME, name); + } + #[inline] + pub fn add_help(&mut self, help: flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::>(DefineGauge::VT_HELP, help); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> DefineGaugeBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + DefineGaugeBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for DefineGauge<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("DefineGauge"); + ds.field("name", &self.name()); + ds.field("help", &self.help()); + ds.finish() + } +} +pub enum SetGaugeOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct SetGauge<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for SetGauge<'a> { + type Inner = SetGauge<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> SetGauge<'a> { + pub const VT_NAME: flatbuffers::VOffsetT = 4; + pub const VT_VALUE: flatbuffers::VOffsetT = 6; + pub const VT_LABELS: flatbuffers::VOffsetT = 8; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + SetGauge { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args SetGaugeArgs<'args> + ) -> flatbuffers::WIPOffset> { + let mut builder = SetGaugeBuilder::new(_fbb); + builder.add_value(args.value); + if let Some(x) = args.labels { builder.add_labels(x); } + if let Some(x) = args.name { builder.add_name(x); } + builder.finish() + } + + + #[inline] + pub fn name(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>(SetGauge::VT_NAME, None)} + } + #[inline] + pub fn value(&self) -> f64 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(SetGauge::VT_VALUE, Some(0.0)).unwrap()} + } + #[inline] + pub fn labels(&self) -> Option>>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>>>(SetGauge::VT_LABELS, None)} + } +} + +impl flatbuffers::Verifiable for SetGauge<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::>("name", Self::VT_NAME, false)? + .visit_field::("value", Self::VT_VALUE, false)? + .visit_field::>>>("labels", Self::VT_LABELS, false)? + .finish(); + Ok(()) + } +} +pub struct SetGaugeArgs<'a> { + pub name: Option>, + pub value: f64, + pub labels: Option>>>>, +} +impl<'a> Default for SetGaugeArgs<'a> { + #[inline] + fn default() -> Self { + SetGaugeArgs { + name: None, + value: 0.0, + labels: None, + } + } +} + +pub struct SetGaugeBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> SetGaugeBuilder<'a, 'b, A> { + #[inline] + pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::>(SetGauge::VT_NAME, name); + } + #[inline] + pub fn add_value(&mut self, value: f64) { + self.fbb_.push_slot::(SetGauge::VT_VALUE, value, 0.0); + } + #[inline] + pub fn add_labels(&mut self, labels: flatbuffers::WIPOffset>>>) { + self.fbb_.push_slot_always::>(SetGauge::VT_LABELS, labels); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> SetGaugeBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + SetGaugeBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for SetGauge<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("SetGauge"); + ds.field("name", &self.name()); + ds.field("value", &self.value()); + ds.field("labels", &self.labels()); + ds.finish() + } +} +pub enum DefineHistogramOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct DefineHistogram<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for DefineHistogram<'a> { + type Inner = DefineHistogram<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> DefineHistogram<'a> { + pub const VT_NAME: flatbuffers::VOffsetT = 4; + pub const VT_HELP: flatbuffers::VOffsetT = 6; + pub const VT_BUCKETS: flatbuffers::VOffsetT = 8; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + DefineHistogram { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args DefineHistogramArgs<'args> + ) -> flatbuffers::WIPOffset> { + let mut builder = DefineHistogramBuilder::new(_fbb); + if let Some(x) = args.buckets { builder.add_buckets(x); } + if let Some(x) = args.help { builder.add_help(x); } + if let Some(x) = args.name { builder.add_name(x); } + builder.finish() + } + + + #[inline] + pub fn name(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>(DefineHistogram::VT_NAME, None)} + } + #[inline] + pub fn help(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>(DefineHistogram::VT_HELP, None)} + } + #[inline] + pub fn buckets(&self) -> Option> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>>(DefineHistogram::VT_BUCKETS, None)} + } +} + +impl flatbuffers::Verifiable for DefineHistogram<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::>("name", Self::VT_NAME, false)? + .visit_field::>("help", Self::VT_HELP, false)? + .visit_field::>>("buckets", Self::VT_BUCKETS, false)? + .finish(); + Ok(()) + } +} +pub struct DefineHistogramArgs<'a> { + pub name: Option>, + pub help: Option>, + pub buckets: Option>>, +} +impl<'a> Default for DefineHistogramArgs<'a> { + #[inline] + fn default() -> Self { + DefineHistogramArgs { + name: None, + help: None, + buckets: None, + } + } +} + +pub struct DefineHistogramBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> DefineHistogramBuilder<'a, 'b, A> { + #[inline] + pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::>(DefineHistogram::VT_NAME, name); + } + #[inline] + pub fn add_help(&mut self, help: flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::>(DefineHistogram::VT_HELP, help); + } + #[inline] + pub fn add_buckets(&mut self, buckets: flatbuffers::WIPOffset>) { + self.fbb_.push_slot_always::>(DefineHistogram::VT_BUCKETS, buckets); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> DefineHistogramBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + DefineHistogramBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for DefineHistogram<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("DefineHistogram"); + ds.field("name", &self.name()); + ds.field("help", &self.help()); + ds.field("buckets", &self.buckets()); + ds.finish() + } +} +pub enum ObserveHistogramOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct ObserveHistogram<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for ObserveHistogram<'a> { + type Inner = ObserveHistogram<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> ObserveHistogram<'a> { + pub const VT_NAME: flatbuffers::VOffsetT = 4; + pub const VT_VALUE: flatbuffers::VOffsetT = 6; + pub const VT_LABELS: flatbuffers::VOffsetT = 8; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + ObserveHistogram { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args ObserveHistogramArgs<'args> + ) -> flatbuffers::WIPOffset> { + let mut builder = ObserveHistogramBuilder::new(_fbb); + builder.add_value(args.value); + if let Some(x) = args.labels { builder.add_labels(x); } + if let Some(x) = args.name { builder.add_name(x); } + builder.finish() + } + + + #[inline] + pub fn name(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>(ObserveHistogram::VT_NAME, None)} + } + #[inline] + pub fn value(&self) -> f64 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(ObserveHistogram::VT_VALUE, Some(0.0)).unwrap()} + } + #[inline] + pub fn labels(&self) -> Option>>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>>>(ObserveHistogram::VT_LABELS, None)} + } +} + +impl flatbuffers::Verifiable for ObserveHistogram<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::>("name", Self::VT_NAME, false)? + .visit_field::("value", Self::VT_VALUE, false)? + .visit_field::>>>("labels", Self::VT_LABELS, false)? + .finish(); + Ok(()) + } +} +pub struct ObserveHistogramArgs<'a> { + pub name: Option>, + pub value: f64, + pub labels: Option>>>>, +} +impl<'a> Default for ObserveHistogramArgs<'a> { + #[inline] + fn default() -> Self { + ObserveHistogramArgs { + name: None, + value: 0.0, + labels: None, + } + } +} + +pub struct ObserveHistogramBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> ObserveHistogramBuilder<'a, 'b, A> { + #[inline] + pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::>(ObserveHistogram::VT_NAME, name); + } + #[inline] + pub fn add_value(&mut self, value: f64) { + self.fbb_.push_slot::(ObserveHistogram::VT_VALUE, value, 0.0); + } + #[inline] + pub fn add_labels(&mut self, labels: flatbuffers::WIPOffset>>>) { + self.fbb_.push_slot_always::>(ObserveHistogram::VT_LABELS, labels); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> ObserveHistogramBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + ObserveHistogramBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for ObserveHistogram<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("ObserveHistogram"); + ds.field("name", &self.name()); + ds.field("value", &self.value()); + ds.field("labels", &self.labels()); + ds.finish() + } +} #[inline] /// Verifies that a buffer of bytes contains a `VoidResponse` /// and returns it. diff --git a/yggdrasilffi/src/flat/mod.rs b/yggdrasilffi/src/flat/mod.rs index baabd9d..b530a17 100644 --- a/yggdrasilffi/src/flat/mod.rs +++ b/yggdrasilffi/src/flat/mod.rs @@ -9,9 +9,12 @@ use flatbuffers::root; use std::borrow::Cow; use std::ffi::{c_char, c_void}; -use unleash_yggdrasil::impact_metrics::MetricOptions; +use unleash_yggdrasil::impact_metrics::{BucketMetricOptions, MetricLabels, MetricOptions}; -use crate::flat::messaging::yggdrasil::messaging::{DefineCounter, VoidResponse}; +use crate::flat::messaging::yggdrasil::messaging::{ + DefineCounter, DefineGauge, DefineHistogram, IncCounter, ObserveHistogram, SetGauge, + VoidResponse, +}; use crate::flat::serialisation::{Buf, TakeStateResult}; use crate::{get_json, ManagedEngine, RawPointerDataType}; use chrono::Utc; @@ -343,6 +346,214 @@ pub unsafe extern "C" fn flat_define_counter( VoidResponse::build_response(result) } +fn parse_labels( + labels: Option< + flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset>>, + >, +) -> MetricLabels { + labels + .map(|entries| { + entries + .iter() + .map(|entry| (entry.key().to_owned(), entry.value().unwrap_or_default().to_owned())) + .collect() + }) + .unwrap_or_default() +} + +/// Increments a counter metric by a given value. +/// +/// # Safety +/// +/// passing an invalid engine_ptr, message_ptr, or improper message_len will cause UB +/// the returned Buf should be freed by calling flat_buf_free, otherwise you're leaking memory +#[no_mangle] +pub unsafe extern "C" fn flat_inc_counter( + engine_ptr: *mut c_void, + message_ptr: u64, + message_len: u64, +) -> Buf { + let result = guard_result::<(), _>(|| { + let bytes = + unsafe { std::slice::from_raw_parts(message_ptr as *const u8, message_len as usize) }; + let inc_counter_message = + root::(bytes).map_err(|e| FlatError::InvalidBuffer(e.to_string()))?; + + let guard = get_engine(engine_ptr)?; + let engine = recover_lock(&guard); + + let Some(name) = inc_counter_message.name() else { + return Err(FlatError::MissingRequiredParameter("name".to_owned())); + }; + + let value = inc_counter_message.value(); + let labels = parse_labels(inc_counter_message.labels()); + if labels.is_empty() { + engine.inc_counter_by(name, value); + } else { + engine.inc_counter_with_labels(name, value, &labels); + } + + Ok(Some(())) + }); + + VoidResponse::build_response(result) +} + +/// Defines a gauge metric with the given name and help text. +/// +/// # Safety +/// +/// passing an invalid engine_ptr, message_ptr, or improper message_len will cause UB +/// the returned Buf should be freed by calling flat_buf_free, otherwise you're leaking memory +#[no_mangle] +pub unsafe extern "C" fn flat_define_gauge( + engine_ptr: *mut c_void, + message_ptr: u64, + message_len: u64, +) -> Buf { + let result = guard_result::<(), _>(|| { + let bytes = + unsafe { std::slice::from_raw_parts(message_ptr as *const u8, message_len as usize) }; + let define_gauge_message = + root::(bytes).map_err(|e| FlatError::InvalidBuffer(e.to_string()))?; + + let guard = get_engine(engine_ptr)?; + let engine = recover_lock(&guard); + + let Some(name) = define_gauge_message.name() else { + return Err(FlatError::MissingRequiredParameter("name".to_owned())); + }; + + let Some(help) = define_gauge_message.help() else { + return Err(FlatError::MissingRequiredParameter("help".to_owned())); + }; + + engine.define_gauge(MetricOptions::new(name, help)); + Ok(Some(())) + }); + + VoidResponse::build_response(result) +} + +/// Sets a gauge metric to a given value. +/// +/// # Safety +/// +/// passing an invalid engine_ptr, message_ptr, or improper message_len will cause UB +/// the returned Buf should be freed by calling flat_buf_free, otherwise you're leaking memory +#[no_mangle] +pub unsafe extern "C" fn flat_set_gauge( + engine_ptr: *mut c_void, + message_ptr: u64, + message_len: u64, +) -> Buf { + let result = guard_result::<(), _>(|| { + let bytes = + unsafe { std::slice::from_raw_parts(message_ptr as *const u8, message_len as usize) }; + let set_gauge_message = + root::(bytes).map_err(|e| FlatError::InvalidBuffer(e.to_string()))?; + + let guard = get_engine(engine_ptr)?; + let engine = recover_lock(&guard); + + let Some(name) = set_gauge_message.name() else { + return Err(FlatError::MissingRequiredParameter("name".to_owned())); + }; + + let value = set_gauge_message.value(); + let labels = parse_labels(set_gauge_message.labels()); + if labels.is_empty() { + engine.set_gauge(name, value); + } else { + engine.set_gauge_with_labels(name, value, &labels); + } + + Ok(Some(())) + }); + + VoidResponse::build_response(result) +} + +/// Defines a histogram metric with optional buckets. +/// +/// # Safety +/// +/// passing an invalid engine_ptr, message_ptr, or improper message_len will cause UB +/// the returned Buf should be freed by calling flat_buf_free, otherwise you're leaking memory +#[no_mangle] +pub unsafe extern "C" fn flat_define_histogram( + engine_ptr: *mut c_void, + message_ptr: u64, + message_len: u64, +) -> Buf { + let result = guard_result::<(), _>(|| { + let bytes = + unsafe { std::slice::from_raw_parts(message_ptr as *const u8, message_len as usize) }; + let define_histogram_message = + root::(bytes).map_err(|e| FlatError::InvalidBuffer(e.to_string()))?; + + let guard = get_engine(engine_ptr)?; + let engine = recover_lock(&guard); + + let Some(name) = define_histogram_message.name() else { + return Err(FlatError::MissingRequiredParameter("name".to_owned())); + }; + + let Some(help) = define_histogram_message.help() else { + return Err(FlatError::MissingRequiredParameter("help".to_owned())); + }; + + let buckets = define_histogram_message + .buckets() + .map(|entries| entries.iter().collect::>()) + .unwrap_or_default(); + engine.define_histogram(BucketMetricOptions::new(name, help, buckets)); + Ok(Some(())) + }); + + VoidResponse::build_response(result) +} + +/// Observes a value on a histogram metric. +/// +/// # Safety +/// +/// passing an invalid engine_ptr, message_ptr, or improper message_len will cause UB +/// the returned Buf should be freed by calling flat_buf_free, otherwise you're leaking memory +#[no_mangle] +pub unsafe extern "C" fn flat_observe_histogram( + engine_ptr: *mut c_void, + message_ptr: u64, + message_len: u64, +) -> Buf { + let result = guard_result::<(), _>(|| { + let bytes = + unsafe { std::slice::from_raw_parts(message_ptr as *const u8, message_len as usize) }; + let observe_histogram_message = + root::(bytes).map_err(|e| FlatError::InvalidBuffer(e.to_string()))?; + + let guard = get_engine(engine_ptr)?; + let engine = recover_lock(&guard); + + let Some(name) = observe_histogram_message.name() else { + return Err(FlatError::MissingRequiredParameter("name".to_owned())); + }; + + let value = observe_histogram_message.value(); + let labels = parse_labels(observe_histogram_message.labels()); + if labels.is_empty() { + engine.observe_histogram(name, value); + } else { + engine.observe_histogram_with_labels(name, value, &labels); + } + + Ok(Some(())) + }); + + VoidResponse::build_response(result) +} + #[cfg(test)] mod tests { use super::*; From 6d27798148ccab6b8f56237ec4f65c75dd74e906 Mon Sep 17 00:00:00 2001 From: Simon Hornby Date: Mon, 9 Feb 2026 16:21:36 +0200 Subject: [PATCH 2/6] feat: wire up to public api and basic tests --- .../YggdrasilImpactMetricsTest.cs | 82 +++++++++++++++++-- .../Yggdrasil.Engine/YggdrasilEngine.cs | 40 +++++++++ 2 files changed, 114 insertions(+), 8 deletions(-) diff --git a/dotnet-engine/Yggdrasil.Engine.Tests/YggdrasilImpactMetricsTest.cs b/dotnet-engine/Yggdrasil.Engine.Tests/YggdrasilImpactMetricsTest.cs index d2aa4ca..7ee353a 100644 --- a/dotnet-engine/Yggdrasil.Engine.Tests/YggdrasilImpactMetricsTest.cs +++ b/dotnet-engine/Yggdrasil.Engine.Tests/YggdrasilImpactMetricsTest.cs @@ -1,16 +1,9 @@ -using System; -using System.Text.Json; +using System.Collections.Generic; using NUnit.Framework; using Yggdrasil; -using Yggdrasil.Test; public class YggdrasilImpactMetricsTest { - private JsonSerializerOptions options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - [Test] public void DefineCounter_Throws_Only_When_Invalid() { @@ -20,4 +13,77 @@ public void DefineCounter_Throws_Only_When_Invalid() Assert.Throws(() => yggdrasilEngine.DefineCounter(null!, "Measures time spent on server doing things")); Assert.Throws(() => yggdrasilEngine.DefineCounter(null!, null!)); } + + [Test] + public void IncCounter_Throws_Only_When_Invalid() + { + var yggdrasilEngine = new YggdrasilEngine(); + yggdrasilEngine.DefineCounter("requests_total", "Total requests"); + + Assert.DoesNotThrow(() => yggdrasilEngine.IncCounter("requests_total")); + Assert.DoesNotThrow(() => yggdrasilEngine.IncCounter("requests_total", 5)); + Assert.DoesNotThrow(() => yggdrasilEngine.IncCounter("requests_total", 3, new Dictionary { { "env", "test" } })); + + Assert.Throws(() => yggdrasilEngine.IncCounter(null!)); + } + + [Test] + public void DefineGauge_Throws_Only_When_Invalid() + { + var yggdrasilEngine = new YggdrasilEngine(); + Assert.DoesNotThrow(() => yggdrasilEngine.DefineGauge("cpu_usage", "CPU usage")); + Assert.Throws(() => yggdrasilEngine.DefineGauge("cpu_usage", null!)); + Assert.Throws(() => yggdrasilEngine.DefineGauge(null!, "CPU usage")); + Assert.Throws(() => yggdrasilEngine.DefineGauge(null!, null!)); + } + + [Test] + public void SetGauge_Throws_Only_When_Invalid() + { + var yggdrasilEngine = new YggdrasilEngine(); + yggdrasilEngine.DefineGauge("queue_depth", "Queue depth"); + + Assert.DoesNotThrow(() => yggdrasilEngine.SetGauge("queue_depth", 10.5)); + Assert.DoesNotThrow(() => yggdrasilEngine.SetGauge("queue_depth", 5.25, new Dictionary { { "env", "prod" } })); + Assert.Throws(() => yggdrasilEngine.SetGauge(null!, 1.0)); + } + + [Test] + public void DefineHistogram_Throws_Only_When_Invalid() + { + var yggdrasilEngine = new YggdrasilEngine(); + Assert.DoesNotThrow(() => yggdrasilEngine.DefineHistogram("request_duration", "Request duration")); + Assert.DoesNotThrow(() => yggdrasilEngine.DefineHistogram("request_duration_custom", "Request duration custom", new[] { 0.1, 0.5, 1.0, 5.0 })); + + Assert.Throws(() => yggdrasilEngine.DefineHistogram("request_duration", null!)); + Assert.Throws(() => yggdrasilEngine.DefineHistogram(null!, "Request duration")); + Assert.Throws(() => yggdrasilEngine.DefineHistogram(null!, null!)); + } + + [Test] + public void ObserveHistogram_Throws_Only_When_Invalid() + { + var yggdrasilEngine = new YggdrasilEngine(); + yggdrasilEngine.DefineHistogram("request_duration", "Request duration", new[] { 0.1, 0.5, 1.0, 5.0 }); + + Assert.DoesNotThrow(() => yggdrasilEngine.ObserveHistogram("request_duration", 0.05)); + Assert.DoesNotThrow(() => yggdrasilEngine.ObserveHistogram("request_duration", 0.75, new Dictionary { { "env", "test" } })); + Assert.Throws(() => yggdrasilEngine.ObserveHistogram(null!, 1.0)); + } + + [Test] + public void ImpactMetrics_Methods_Can_Be_Used_Together() + { + var yggdrasilEngine = new YggdrasilEngine(); + + Assert.DoesNotThrow(() => + { + yggdrasilEngine.DefineCounter("test_counter", "Test counter"); + yggdrasilEngine.IncCounter("test_counter", 10); + yggdrasilEngine.DefineGauge("test_gauge", "Test gauge"); + yggdrasilEngine.SetGauge("test_gauge", 42); + yggdrasilEngine.DefineHistogram("test_histogram", "Test histogram", new[] { 0.1, 0.5, 1.0 }); + yggdrasilEngine.ObserveHistogram("test_histogram", 0.25); + }); + } } diff --git a/dotnet-engine/Yggdrasil.Engine/YggdrasilEngine.cs b/dotnet-engine/Yggdrasil.Engine/YggdrasilEngine.cs index 5cd4b54..64a5782 100644 --- a/dotnet-engine/Yggdrasil.Engine/YggdrasilEngine.cs +++ b/dotnet-engine/Yggdrasil.Engine/YggdrasilEngine.cs @@ -90,6 +90,46 @@ public void DefineCounter(string name, string help) finally { Flat.FreeBuf(buf); } } + public void IncCounter(string name, long value = 1, IDictionary? labels = null) + { + var messageBuffer = Flatbuffers.CreateIncCounterBuffer(new FlatBufferBuilder(128), name, value, labels); + var buf = Flat.IncCounter(state, messageBuffer); + try { Flatbuffers.ParseVoidAndThrow(buf); } + finally { Flat.FreeBuf(buf); } + } + + public void DefineGauge(string name, string help) + { + var messageBuffer = Flatbuffers.CreateDefineGaugeBuffer(new FlatBufferBuilder(128), name, help); + var buf = Flat.DefineGauge(state, messageBuffer); + try { Flatbuffers.ParseVoidAndThrow(buf); } + finally { Flat.FreeBuf(buf); } + } + + public void SetGauge(string name, double value, IDictionary? labels = null) + { + var messageBuffer = Flatbuffers.CreateSetGaugeBuffer(new FlatBufferBuilder(128), name, value, labels); + var buf = Flat.SetGauge(state, messageBuffer); + try { Flatbuffers.ParseVoidAndThrow(buf); } + finally { Flat.FreeBuf(buf); } + } + + public void DefineHistogram(string name, string help, IEnumerable? buckets = null) + { + var messageBuffer = Flatbuffers.CreateDefineHistogramBuffer(new FlatBufferBuilder(128), name, help, buckets); + var buf = Flat.DefineHistogram(state, messageBuffer); + try { Flatbuffers.ParseVoidAndThrow(buf); } + finally { Flat.FreeBuf(buf); } + } + + public void ObserveHistogram(string name, double value, IDictionary? labels = null) + { + var messageBuffer = Flatbuffers.CreateObserveHistogramBuffer(new FlatBufferBuilder(128), name, value, labels); + var buf = Flat.ObserveHistogram(state, messageBuffer); + try { Flatbuffers.ParseVoidAndThrow(buf); } + finally { Flat.FreeBuf(buf); } + } + public ICollection ListKnownToggles() { var buf = Flat.ListKnownToggles(state); From aca6916d9b43b88c6c0f7cf3cc266d298e943994 Mon Sep 17 00:00:00 2001 From: Simon Hornby Date: Tue, 10 Feb 2026 08:29:18 +0200 Subject: [PATCH 3/6] Update dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs b/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs index 83c399f..61e2e1b 100644 --- a/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs +++ b/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs @@ -160,14 +160,15 @@ public static byte[] CreateObserveHistogramBuffer(FlatBufferBuilder builder, str } var labelEntries = new Offset[labels.Count]; - for (var i = 0; i < labels.Count; i++) + var index = 0; + foreach (var kvp in labels) { - var kvp = labels.ElementAt(i); - labelEntries[i] = SampleLabelEntry.CreateSampleLabelEntry( + labelEntries[index] = SampleLabelEntry.CreateSampleLabelEntry( builder, builder.CreateString(kvp.Key), builder.CreateString(kvp.Value) ); + index++; } return IncCounter.CreateLabelsVector(builder, labelEntries); From c31bfab6baba153862dc1be65b643fe8fc09db0a Mon Sep 17 00:00:00 2001 From: Simon Hornby Date: Tue, 10 Feb 2026 08:33:50 +0200 Subject: [PATCH 4/6] fix: avoid nested serialisation in histogram --- dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs b/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs index 61e2e1b..c44decd 100644 --- a/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs +++ b/dotnet-engine/Yggdrasil.Engine/Flatbuffers.cs @@ -119,13 +119,15 @@ public static byte[] CreateDefineHistogramBuffer(FlatBufferBuilder builder, stri var nameOffset = builder.CreateString(name); var helpOffset = builder.CreateString(help); var bucketArray = (buckets ?? Enumerable.Empty()).ToArray(); + var bucketsOffset = bucketArray.Length > 0 + ? DefineHistogram.CreateBucketsVector(builder, bucketArray) + : default(VectorOffset); DefineHistogram.StartDefineHistogram(builder); DefineHistogram.AddName(builder, nameOffset); DefineHistogram.AddHelp(builder, helpOffset); if (bucketArray.Length > 0) { - var bucketsOffset = DefineHistogram.CreateBucketsVector(builder, bucketArray); DefineHistogram.AddBuckets(builder, bucketsOffset); } From 0f4b9254ce52885df0849ddbfdf330b4122d9446 Mon Sep 17 00:00:00 2001 From: Simon Hornby Date: Fri, 13 Feb 2026 09:47:33 +0200 Subject: [PATCH 5/6] chore: clippy --- yggdrasilffi/src/flat/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yggdrasilffi/src/flat/mod.rs b/yggdrasilffi/src/flat/mod.rs index b530a17..5b05154 100644 --- a/yggdrasilffi/src/flat/mod.rs +++ b/yggdrasilffi/src/flat/mod.rs @@ -191,8 +191,8 @@ pub unsafe extern "C" fn flat_check_enabled( let engine = recover_lock(&lock); let enabled = engine.check_enabled(&context); - let impression_data = engine.should_emit_impression_event(&context.toggle_name); - engine.count_toggle(&context.toggle_name, enabled.unwrap_or(false)); + let impression_data = engine.should_emit_impression_event(context.toggle_name); + engine.count_toggle(context.toggle_name, enabled.unwrap_or(false)); Ok(Some(ResponseMessage { message: enabled, @@ -240,10 +240,10 @@ pub unsafe extern "C" fn flat_check_variant( let engine = recover_lock(&lock); let base_variant = engine.check_variant(&context); let toggle_enabled = engine.check_enabled(&context).unwrap_or_default(); - let impression_data = engine.should_emit_impression_event(&context.toggle_name); - engine.count_toggle(&context.toggle_name, toggle_enabled); + let impression_data = engine.should_emit_impression_event(context.toggle_name); + engine.count_toggle(context.toggle_name, toggle_enabled); if let Some(v) = base_variant.clone() { - engine.count_variant(&context.toggle_name, &v.name); + engine.count_variant(context.toggle_name, &v.name); } let message = base_variant.map(|variant| variant.to_enriched_response(toggle_enabled)); Ok(Some(ResponseMessage { From 7fa3437a9eff890b0e5996ff3522d13b3e2a6580 Mon Sep 17 00:00:00 2001 From: Simon Hornby Date: Fri, 13 Feb 2026 09:50:10 +0200 Subject: [PATCH 6/6] chore: fmt --- yggdrasilffi/src/flat/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/yggdrasilffi/src/flat/mod.rs b/yggdrasilffi/src/flat/mod.rs index 5b05154..ada0641 100644 --- a/yggdrasilffi/src/flat/mod.rs +++ b/yggdrasilffi/src/flat/mod.rs @@ -348,14 +348,22 @@ pub unsafe extern "C" fn flat_define_counter( fn parse_labels( labels: Option< - flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset>>, + flatbuffers::Vector< + '_, + flatbuffers::ForwardsUOffset>, + >, >, ) -> MetricLabels { labels .map(|entries| { entries .iter() - .map(|entry| (entry.key().to_owned(), entry.value().unwrap_or_default().to_owned())) + .map(|entry| { + ( + entry.key().to_owned(), + entry.value().unwrap_or_default().to_owned(), + ) + }) .collect() }) .unwrap_or_default()