From 9da43bade9e8069df1d336401db957e3dd7f084f Mon Sep 17 00:00:00 2001 From: Sean Parker Date: Fri, 28 Aug 2020 14:01:19 +0100 Subject: [PATCH] Event Tracing API wrapper (#1452) * Add C# API wrappers for Span, Item, EventData data operations * Add wrappers for Event Tracer logic * Add Parameter Conversions * Use closures to convert from external to internal Event * Add Event Tracer parameter conversion closure * Update Worker SDK IO bindings to support v14.8.0 * Add changelog entry --- CHANGELOG.md | 1 + .../io.improbable.worker.sdk/CEventTrace.cs | 38 +- .../Packages/io.improbable.worker.sdk/CIO.cs | 46 +- .../io.improbable.worker.sdk/EventTracing.cs | 410 ++++++++++++++++++ .../EventTracing.cs.meta | 3 + .../io.improbable.worker.sdk/IOStorage.cs | 8 +- .../io.improbable.worker.sdk/IOStream.cs | 64 ++- .../ParameterConversion.cs | 221 ++++++++++ .../ParameterConversion.cs.meta | 3 + 9 files changed, 748 insertions(+), 46 deletions(-) create mode 100644 workers/unity/Packages/io.improbable.worker.sdk/EventTracing.cs create mode 100644 workers/unity/Packages/io.improbable.worker.sdk/EventTracing.cs.meta create mode 100644 workers/unity/Packages/io.improbable.worker.sdk/ParameterConversion.cs create mode 100644 workers/unity/Packages/io.improbable.worker.sdk/ParameterConversion.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index e2d1c55c82..cb4acfc8bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Added - Added `MeansImplicitUse` attribute to `RequireAttribute` to reduce warnings in Rider IDE. [#1462](https://github.com/spatialos/gdk-for-unity/pull/1462) +- Added Event Tracing API. [#1452](https://github.com/spatialos/gdk-for-unity/pull/1452) ### Changed diff --git a/workers/unity/Packages/io.improbable.worker.sdk/CEventTrace.cs b/workers/unity/Packages/io.improbable.worker.sdk/CEventTrace.cs index b35e12f852..969cd87413 100644 --- a/workers/unity/Packages/io.improbable.worker.sdk/CEventTrace.cs +++ b/workers/unity/Packages/io.improbable.worker.sdk/CEventTrace.cs @@ -18,13 +18,27 @@ internal unsafe class CEventTrace * Data for an event. This is a collection of key-value pairs (fields). Use EventData* functions to * read or write fields. */ - public class EventData : CptrHandle + public class EventDataHandle : CptrHandle { + public EventDataHandle() + { + } + + internal EventDataHandle(IntPtr handle) + { + SetHandle(handle); + } + protected override bool ReleaseHandle() { EventDataDestroy(handle); return true; } + + internal IntPtr GetUnderlying() + { + return handle; + } } public class EventTracer : CptrHandle @@ -100,7 +114,7 @@ public struct Span */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Trace_EventData_Create")] - public static extern EventData EventDataCreate(); + public static extern EventDataHandle EventDataCreate(); /** Frees resources for the event data object.*/ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, @@ -113,12 +127,12 @@ public struct Span */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Trace_EventData_AddStringFields")] - public static extern void EventDataAddStringFields(EventData data, Uint32 count, Char** keys, Char** values); + public static extern void EventDataAddStringFields(EventDataHandle data, Uint32 count, Char** keys, Char** values); /** Returns the number of fields on the given event data object. */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Trace_EventData_GetFieldCount")] - public static extern Uint32 EventDataGetFieldCount(EventData data); + public static extern Uint32 EventDataGetFieldCount(EventDataHandle data); /** * Returns all the key value pairs in the event data object. keys and values must have capacity for @@ -128,12 +142,12 @@ public struct Span */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Trace_EventData_GetStringFields")] - public static extern void EventDataGetStringFields(EventData data, Char** keys, Char** values); + public static extern void EventDataGetStringFields(EventDataHandle data, Char** keys, Char** values); /** Returns the value for the given key. */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Trace_EventData_GetFieldValue")] - public static extern Char* EventDataGetFieldValue(EventData data, Char* key); + public static extern Char* EventDataGetFieldValue(EventDataHandle data, Char* key); /** Data for an event added to the event-tracer. */ [StructLayout(LayoutKind.Sequential)] @@ -153,7 +167,7 @@ public struct Event public struct Item { /** The type of the item, defined using ItemType. */ - public Uint8 ItemType; + public ItemType ItemType; /** An item can either be a Span or an Event. */ public Union ItemUnion; @@ -187,12 +201,12 @@ public struct EventTracerParameters /** Creates an event-tracer. */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, - EntryPoint = "EventTracerCreate")] + EntryPoint = "Trace_EventTracer_Create")] public static extern EventTracer EventTracerCreate(EventTracerParameters* parameters); /** Frees resources for an event-tracer. */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, - EntryPoint = "EventTracerDestroy")] + EntryPoint = "Trace_EventTracer_Destroy")] public static extern void EventTracerDestroy(IntPtr eventTracer); /** @@ -234,8 +248,8 @@ public struct EventTracerParameters * EventTracerGetActiveSpanId will return a null span ID. */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, - EntryPoint = "Trace_EventTracer_UnsetActiveSpanId")] - public static extern void EventTracerUnsetActiveSpanId(EventTracer eventTracer); + EntryPoint = "Trace_EventTracer_ClearActiveSpanId")] + public static extern void EventTracerClearActiveSpanId(EventTracer eventTracer); /** Gets the active span ID on the event-tracer. */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, @@ -272,7 +286,7 @@ public struct EventTracerParameters * The item is initialized by copying the provided item; pass a NULL item argument to create an * item in an uninitialized state. * - * Directly creating a TraceItem object (on the stack or the heap) by other means than calling this + * Directly creating a Item object (on the stack or the heap) by other means than calling this * method is discouraged as it will lead to undefined behaviour when passing that item to certain * trace API methods (e.g. SerializeItemToStream). */ diff --git a/workers/unity/Packages/io.improbable.worker.sdk/CIO.cs b/workers/unity/Packages/io.improbable.worker.sdk/CIO.cs index 2bc19c2605..c721422fb3 100644 --- a/workers/unity/Packages/io.improbable.worker.sdk/CIO.cs +++ b/workers/unity/Packages/io.improbable.worker.sdk/CIO.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; using Int64 = System.Int64; using Uint64 = System.UInt64; @@ -28,10 +29,31 @@ protected override bool ReleaseHandle() } } - public enum OpenMode + [Flags] + public enum OpenModes : Uint32 { - /* Opens the stream in the default mode. */ - OpenModeDefault = 0x00, + /** + * Allow input operations on the stream. Input operations always occur at the read position, which + * is initialized to the beginning of the stream. + */ + OpenModeRead = 0x01, + + /** + * Allow output operations on the stream. Output operations always occur at the write position, + * which is initialized to the end of the stream. + */ + OpenModeWrite = 0x02, + + /** + * Truncates any existing content upon opening. If not set, writes are appended to the end of the + * stream's existing content. + */ + OpenModeTruncate = 0x04, + + /** + * Specify that writes should be appended to the stream's existing content, if any exists. + */ + OpenModeAppend = 0x08, } /** @@ -85,16 +107,14 @@ public enum OpenMode * The file stream has a conceptually infinite capacity; its true capacity depends on the * underlying filesystem. * - * Upon creation of the file stream, the file is created if it does not exist. The file stream is - * initialized to read from the beginning of the file and append to the end, regardless of whether - * it previously existed or not. + * The open_mode argument should be passed as a combination of OpenMode values. * - * Returns a pointer to a file stream. Never returns NULL. Call StreamGetLastError to check - * if an error occurred during file stream creation. + * Returns a pointer to a file stream. Never returns NULL. You *must* call Io_Stream_GetLastError to + * check if an error occurred during file stream creation. */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Io_CreateFileStream")] - public static extern StreamHandle CreateFileStream(Char* filename, OpenMode openMode); + public static extern StreamHandle CreateFileStream(Char* filename, OpenModes openModes); /* Destroys the I/O stream. */ [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, @@ -168,5 +188,13 @@ public enum OpenMode [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Io_Stream_GetLastError")] public static extern Char* StreamGetLastError(StreamHandle stream); + + /** + * Clears the stream's current error such that the next call to Io_Stream_GetLastError returns + * nullptr. + */ + [DllImport(Constants.WorkerLibrary, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "Io_Stream_ClearError")] + public static extern void StreamClearError(StreamHandle stream); } } diff --git a/workers/unity/Packages/io.improbable.worker.sdk/EventTracing.cs b/workers/unity/Packages/io.improbable.worker.sdk/EventTracing.cs new file mode 100644 index 0000000000..186aebaaf5 --- /dev/null +++ b/workers/unity/Packages/io.improbable.worker.sdk/EventTracing.cs @@ -0,0 +1,410 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Improbable.Worker.CInterop.Internal; + +namespace Improbable.Worker.CInterop +{ + public struct SpanId : IEquatable + { + internal static UIntPtr SpanIdSize = new UIntPtr(16); + public unsafe fixed byte Data[16]; + + public static SpanId Null => GetNullSpanId(); + + public override bool Equals(object obj) + { + return obj is SpanId && Equals(obj); + } + + public bool Equals(SpanId other) + { + return CEventTrace.SpanIdEqual( + ParameterConversion.ConvertSpanId(this), + ParameterConversion.ConvertSpanId(other)) > 0; + } + + public static bool operator ==(SpanId lhs, SpanId rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(SpanId lhs, SpanId rhs) + { + return !lhs.Equals(rhs); + } + + public override int GetHashCode() + { + return CEventTrace.SpanIdHash(ParameterConversion.ConvertSpanId(this)); + } + + private static SpanId GetNullSpanId() + { + var nativeSpanId = new SpanId(); + var nullSpanId = CEventTrace.SpanIdNull(); + unsafe + { + ApiInterop.Memcpy(nativeSpanId.Data, nullSpanId.Data, SpanIdSize); + } + + return nativeSpanId; + } + } + + public struct Span + { + public SpanId Id; + public SpanId[] Causes; + } + + public struct Event + { + public SpanId Id; + public ulong UnixTimestampMillis; + public string Message; + public string Type; + public TraceEventData Data; + } + + public enum ItemType + { + Span = 1, + Event = 2 + } + + public struct Item + { + public ItemType ItemType; + public Span? Span; + public Event? Event; + + public static Item Create(IOStorage storage, Item? itemToConvert = null) + { + unsafe + { + var newItem = new Item(); + if (itemToConvert != null) + { + ParameterConversion.ConvertItem(itemToConvert.Value, nativeItem => + { + newItem = ParameterConversion.ConvertItem(CEventTrace.ItemCreate(storage.Storage, nativeItem)); + }); + } + else + { + newItem = ParameterConversion.ConvertItem(CEventTrace.ItemCreate(storage.Storage, null)); + } + + return newItem; + } + } + + public void SerializeToStream(IOStream stream) + { + unsafe + { + var serializedItemResult = 0; + ParameterConversion.ConvertItem(this, nativeItem => + { + var itemSize = CEventTrace.GetSerializedItemSize(nativeItem); + serializedItemResult = CEventTrace.SerializeItemToStream(stream.Stream, nativeItem, itemSize); + }); + + if (serializedItemResult == 1) + { + return; + } + + var errorMessage = CEventTrace.GetLastError(); + throw new IOException(ApiInterop.FromUtf8Cstr(errorMessage)); + } + } + + public static Item DeserializeNextItemFromStream(IOStream stream) + { + unsafe + { + var itemContainer = GetThreadLocalItem(); + var itemSize = CEventTrace.GetNextSerializedItemSize(stream.Stream); + + var deserializeStatus = CEventTrace.DeserializeItemFromStream(stream.Stream, itemContainer, itemSize); + if (deserializeStatus != 1) + { + var errorMessage = CEventTrace.GetLastError(); + throw new IOException(ApiInterop.FromUtf8Cstr(errorMessage)); + } + + return ParameterConversion.ConvertItem(itemContainer); + } + } + + internal static unsafe CEventTrace.Item* GetThreadLocalItem() + { + var item = CEventTrace.ItemGetThreadLocal(); + switch (item->ItemType) + { + case CEventTrace.ItemType.Span: + item->ItemUnion.Span.Id = CEventTrace.SpanIdNull(); + item->ItemUnion.Span.Causes = null; + item->ItemUnion.Span.CauseCount = 0; + break; + case CEventTrace.ItemType.Event: + item->ItemUnion.Event.Data = IntPtr.Zero; + item->ItemUnion.Event.Id = CEventTrace.SpanIdNull(); + item->ItemUnion.Event.Message = null; + item->ItemUnion.Event.Type = null; + break; + } + + item->ItemType = 0; + return item; + } + } + + public class EventTracer : IDisposable + { + private CEventTrace.EventTracer eventTracer; + private IList handleList; + + public bool IsEnabled { get; private set; } + + public EventTracer(EventTracerParameters[] parameters) + { + unsafe + { + ParameterConversion.ConvertEventTracerParameters(parameters, (internalParameters, handles) => + { + eventTracer = CEventTrace.EventTracerCreate(internalParameters); + handleList = handles; + }); + } + } + + public void Dispose() + { + eventTracer.Dispose(); + foreach (var handle in handleList) + { + handle.Dispose(); + } + } + + public void Enable() + { + CEventTrace.EventTracerEnable(eventTracer); + IsEnabled = true; + } + + public void Disable() + { + CEventTrace.EventTracerDisable(eventTracer); + IsEnabled = false; + } + + public void SetActiveSpanId(SpanId spanId) + { + CEventTrace.EventTracerSetActiveSpanId(eventTracer, ParameterConversion.ConvertSpanId(spanId)); + } + + public void ClearActiveSpanId() + { + CEventTrace.EventTracerClearActiveSpanId(eventTracer); + } + + public SpanId GetActiveSpanId() + { + var nativeSpanId = CEventTrace.EventTracerGetActiveSpanId(eventTracer); + var spanId = new SpanId(); + unsafe + { + ApiInterop.Memcpy(spanId.Data, nativeSpanId.Data, SpanId.SpanIdSize); + } + + return spanId; + } + + // Returns the SpanId of the newly-created Span in the EventTracer + public SpanId AddSpan(SpanId[] causes) + { + unsafe + { + if (causes == null) + { + return AddSpan(); + } + + var causeIds = new CEventTrace.SpanId[causes.Length]; + for (var i = 0; i < causes.Length; i++) + { + causeIds[i] = ParameterConversion.ConvertSpanId(causes[i]); + } + + CEventTrace.SpanId createdSpanId; + fixed (CEventTrace.SpanId* fixedCauseIds = causeIds) + { + createdSpanId = CEventTrace.EventTracerAddSpan(eventTracer, fixedCauseIds, (uint) causeIds.Length); + } + + var newSpanId = new SpanId(); + ApiInterop.Memcpy(newSpanId.Data, createdSpanId.Data, SpanId.SpanIdSize); + + return newSpanId; + } + } + + public SpanId AddSpan(SpanId spanId) + { + var newSpanId = new SpanId(); + unsafe + { + var internalSpanId = ParameterConversion.ConvertSpanId(spanId); + var createdSpanId = CEventTrace.EventTracerAddSpan(eventTracer, &internalSpanId, 1); + ApiInterop.Memcpy(newSpanId.Data, createdSpanId.Data, SpanId.SpanIdSize); + } + + return newSpanId; + } + + public SpanId AddSpan() + { + var newSpanId = new SpanId(); + unsafe + { + var createdSpanId = CEventTrace.EventTracerAddSpan(eventTracer, null, 0); + ApiInterop.Memcpy(newSpanId.Data, createdSpanId.Data, SpanId.SpanIdSize); + } + + return newSpanId; + } + + public void AddEvent(Event @event) + { + ParameterConversion.ConvertEvent(@event, internalEvent => + { + CEventTrace.EventTracerAddEvent(eventTracer, internalEvent); + }); + } + + public bool ShouldSampleEvent(Event @event) + { + bool shouldSampleEvent = false; + ParameterConversion.ConvertEvent(@event, internalEvent => + { + shouldSampleEvent = CEventTrace.EventTracerShouldSampleEvent(eventTracer, internalEvent) > 0; + }); + + return shouldSampleEvent; + } + } + + public class TraceEventData + { + internal readonly CEventTrace.EventDataHandle EventData; + + internal TraceEventData(IntPtr handle) + { + EventData = new CEventTrace.EventDataHandle(handle); + } + + public TraceEventData() + { + EventData = CEventTrace.EventDataCreate(); + } + + public TraceEventData(IEnumerable> eventData) : this() + { + AddAll(eventData); + } + + public string this[string key] + { + get => GetValue(key); + set => AddField(key, value); + } + + public int Count => GetFieldCount(); + + public void AddAll(IEnumerable> fields) + { + foreach (var kvp in fields) + { + AddField(kvp.Key, kvp.Value); + } + } + + public Dictionary GetAll() + { + unsafe + { + var numberOfFields = CEventTrace.EventDataGetFieldCount(EventData); + var nativeKeys = new byte*[numberOfFields]; + var nativeValues = new byte*[numberOfFields]; + + var fields = new Dictionary(); + fixed (byte** keys = nativeKeys) + fixed (byte** values = nativeValues) + { + CEventTrace.EventDataGetStringFields(EventData, keys, values); + + for (var i = 0; i < numberOfFields; i++) + { + fields.Add(ApiInterop.FromUtf8Cstr(nativeKeys[i]), ApiInterop.FromUtf8Cstr(nativeValues[i])); + } + } + + return fields; + } + } + + private void AddField(string key, string value) + { + unsafe + { + fixed (byte* keyPointer = ApiInterop.ToUtf8Cstr(key)) + fixed (byte* valuePointer = ApiInterop.ToUtf8Cstr(value)) + { + CEventTrace.EventDataAddStringFields(EventData, 1, &keyPointer, &valuePointer); + } + } + } + + private string GetValue(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + unsafe + { + byte* valuePointer; + fixed (byte* keyPointer = ApiInterop.ToUtf8Cstr(key)) + { + valuePointer = CEventTrace.EventDataGetFieldValue(EventData, keyPointer); + } + + var value = ApiInterop.FromUtf8Cstr(valuePointer); + if (value.Equals("null")) + { + throw new KeyNotFoundException($"Key '{key}' is not found."); + } + + return value; + } + } + + private int GetFieldCount() + { + return (int) CEventTrace.EventDataGetFieldCount(EventData); + } + } + + public delegate void TraceCallback(object userData, Item item); + + public class EventTracerParameters + { + public TraceCallback TraceCallback; + public object UserData; + } +} diff --git a/workers/unity/Packages/io.improbable.worker.sdk/EventTracing.cs.meta b/workers/unity/Packages/io.improbable.worker.sdk/EventTracing.cs.meta new file mode 100644 index 0000000000..64f360e916 --- /dev/null +++ b/workers/unity/Packages/io.improbable.worker.sdk/EventTracing.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 90e962f9b25542748a8ee09e5dd5dbc0 +timeCreated: 1596538090 \ No newline at end of file diff --git a/workers/unity/Packages/io.improbable.worker.sdk/IOStorage.cs b/workers/unity/Packages/io.improbable.worker.sdk/IOStorage.cs index e2f58019f0..464cabfb0a 100644 --- a/workers/unity/Packages/io.improbable.worker.sdk/IOStorage.cs +++ b/workers/unity/Packages/io.improbable.worker.sdk/IOStorage.cs @@ -5,22 +5,22 @@ namespace Improbable.Worker.CInterop { public sealed class IOStorage : IDisposable { - private readonly CIO.StorageHandle storage; + internal readonly CIO.StorageHandle Storage; public IOStorage() { - storage = CIO.StorageCreate(); + Storage = CIO.StorageCreate(); } /// public void Dispose() { - storage.Dispose(); + Storage.Dispose(); } public void Clear() { - CIO.StorageClear(storage); + CIO.StorageClear(Storage); } } } diff --git a/workers/unity/Packages/io.improbable.worker.sdk/IOStream.cs b/workers/unity/Packages/io.improbable.worker.sdk/IOStream.cs index 5a48eada96..bacfe23c40 100644 --- a/workers/unity/Packages/io.improbable.worker.sdk/IOStream.cs +++ b/workers/unity/Packages/io.improbable.worker.sdk/IOStream.cs @@ -4,25 +4,46 @@ namespace Improbable.Worker.CInterop { - public enum OpenMode + [Flags] + public enum OpenModes : UInt32 { - /* Opens the stream in the default mode. */ - OpenModeDefault = 0x00, + /** + * Allow read operations on the stream. Read operations always occur at the read position, which + * is initialized to the beginning of the stream. + */ + OpenModeRead = 1, + + /** + * Allow write operations on the stream. Write operations always occur at the write position, + * which is initialized to the end of the stream. + */ + OpenModeWrite = 2, + + /** + * Truncates any existing content upon opening. If not set, writes are appended to the end of the + * stream's existing content. + */ + OpenModeTruncate = 4, + + /** + * Specify that writes should be appended to the stream's existing content, if any exists. + */ + OpenModeAppend = 8, } public sealed unsafe class IOStream : IDisposable { - private readonly CIO.StreamHandle stream; + internal readonly CIO.StreamHandle Stream; private IOStream(CIO.StreamHandle stream) { - this.stream = stream; + Stream = stream; } /// public void Dispose() { - stream.Dispose(); + Stream.Dispose(); } public static IOStream CreateRingBufferStream(uint capacity) @@ -30,11 +51,12 @@ public static IOStream CreateRingBufferStream(uint capacity) return new IOStream(CIO.CreateRingBufferStream(capacity)); } - public static IOStream CreateFileStream(string fileName, OpenMode openMode) + public static IOStream CreateFileStream(string fileName, + OpenModes openModes = OpenModes.OpenModeRead | OpenModes.OpenModeWrite | OpenModes.OpenModeTruncate) { fixed (byte* fileNameBytes = ApiInterop.ToUtf8Cstr(fileName)) { - return new IOStream(CIO.CreateFileStream(fileNameBytes, (CIO.OpenMode) openMode)); + return new IOStream(CIO.CreateFileStream(fileNameBytes, (CIO.OpenModes) openModes)); } } @@ -42,7 +64,7 @@ public long Write(byte[] data) { ThrowIfStreamClosed(); - var remainingCapacity = CIO.StreamGetRemainingWriteCapacityBytes(stream); + var remainingCapacity = CIO.StreamGetRemainingWriteCapacityBytes(Stream); if (remainingCapacity < data.Length) { throw new NotSupportedException("Not enough stream capacity to write data."); @@ -51,7 +73,7 @@ public long Write(byte[] data) var bytesWritten = 0L; fixed (byte* dataToWrite = data) { - bytesWritten = CIO.StreamWrite(stream, dataToWrite, 1); + bytesWritten = CIO.StreamWrite(Stream, dataToWrite, 1); } if (bytesWritten != -1) @@ -59,7 +81,7 @@ public long Write(byte[] data) return bytesWritten; } - var rawError = CIO.StreamGetLastError(stream); + var rawError = CIO.StreamGetLastError(Stream); throw new IOException(ApiInterop.FromUtf8Cstr(rawError)); } @@ -72,7 +94,7 @@ public long Read(uint bytesToRead, out byte[] streamData) var bytesRead = 0L; fixed (byte* streamDataPointer = streamData) { - bytesRead = CIO.StreamRead(stream, streamDataPointer, bytesToRead); + bytesRead = CIO.StreamRead(Stream, streamDataPointer, bytesToRead); } if (bytesRead != -1) @@ -80,7 +102,7 @@ public long Read(uint bytesToRead, out byte[] streamData) return bytesRead; } - var rawError = CIO.StreamGetLastError(stream); + var rawError = CIO.StreamGetLastError(Stream); throw new IOException(ApiInterop.FromUtf8Cstr(rawError)); } @@ -92,7 +114,7 @@ public long Read(byte[] streamData) var bytesRead = 0L; fixed (byte* streamDataPointer = streamData) { - bytesRead = CIO.StreamRead(stream, streamDataPointer, bytesToRead); + bytesRead = CIO.StreamRead(Stream, streamDataPointer, bytesToRead); } if (bytesRead != -1) @@ -100,7 +122,7 @@ public long Read(byte[] streamData) return bytesRead; } - var rawError = CIO.StreamGetLastError(stream); + var rawError = CIO.StreamGetLastError(Stream); throw new IOException(ApiInterop.FromUtf8Cstr(rawError)); } @@ -113,7 +135,7 @@ public long Peek(uint bytesToPeek, out byte[] streamData) var bytesPeeked = 0L; fixed (byte* streamDataPointer = streamData) { - bytesPeeked = CIO.StreamPeek(stream, streamDataPointer, bytesToPeek); + bytesPeeked = CIO.StreamPeek(Stream, streamDataPointer, bytesToPeek); } if (bytesPeeked != -1) @@ -121,7 +143,7 @@ public long Peek(uint bytesToPeek, out byte[] streamData) return bytesPeeked; } - var rawError = CIO.StreamGetLastError(stream); + var rawError = CIO.StreamGetLastError(Stream); throw new IOException(ApiInterop.FromUtf8Cstr(rawError)); } @@ -129,25 +151,25 @@ public long Ignore(uint bytesToIgnore) { ThrowIfStreamClosed(); - var bytesIgnored = CIO.StreamIgnore(stream, bytesToIgnore); + var bytesIgnored = CIO.StreamIgnore(Stream, bytesToIgnore); if (bytesIgnored != -1) { return bytesIgnored; } - var rawError = CIO.StreamGetLastError(stream); + var rawError = CIO.StreamGetLastError(Stream); throw new IOException(ApiInterop.FromUtf8Cstr(rawError)); } public uint GetRemainingCapacity() { - return CIO.StreamGetRemainingWriteCapacityBytes(stream); + return CIO.StreamGetRemainingWriteCapacityBytes(Stream); } private void ThrowIfStreamClosed() { - if (stream.IsClosed) + if (Stream.IsClosed) { throw new ObjectDisposedException("Cannot access a disposed object."); } diff --git a/workers/unity/Packages/io.improbable.worker.sdk/ParameterConversion.cs b/workers/unity/Packages/io.improbable.worker.sdk/ParameterConversion.cs new file mode 100644 index 0000000000..04edd8c152 --- /dev/null +++ b/workers/unity/Packages/io.improbable.worker.sdk/ParameterConversion.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; + +namespace Improbable.Worker.CInterop.Internal +{ + // Merge with /worker_sdk/csharp_cinterop/sdk/worker/internal/ParameterConversion.cs + // when changes upstreamed to Worker SDK + internal static class ParameterConversion + { + public unsafe delegate void ItemCallback(CEventTrace.Item* ptr); + + public delegate void EventParametersCallback(CEventTrace.Event internalEvent); + + public unsafe delegate void EventTracerParametersCallback(CEventTrace.EventTracerParameters* parameters, WrappedGcHandle[] handles); + + internal class WrappedGcHandle : CriticalFinalizerObject, IDisposable + { + private GCHandle handle; + + public WrappedGcHandle(object obj) + { + handle = GCHandle.Alloc(obj); + } + + ~WrappedGcHandle() + { + if (handle.IsAllocated) + { + handle.Free(); + } + } + + public void Dispose() + { + if (handle.IsAllocated) + { + handle.Free(); + } + + GC.SuppressFinalize(this); + } + + public IntPtr Get() + { + return GCHandle.ToIntPtr(handle); + } + } + + public static unsafe CEventTrace.SpanId ConvertSpanId(SpanId spanId) + { + var internalSpanId = new CEventTrace.SpanId(); + ApiInterop.Memcpy(internalSpanId.Data, spanId.Data, SpanId.SpanIdSize); + return internalSpanId; + } + + public static unsafe void ConvertEvent(Event eventToConvert, EventParametersCallback callback) + { + CEventTrace.Event internalEvent = new CEventTrace.Event(); + internalEvent.UnixTimestampMillis = eventToConvert.UnixTimestampMillis; + internalEvent.Id = ConvertSpanId(eventToConvert.Id); + + if (eventToConvert.Data != null && !eventToConvert.Data.EventData.IsClosed) + { + internalEvent.Data = eventToConvert.Data.EventData.GetUnderlying(); + } + + fixed (byte* eventType = ApiInterop.ToUtf8Cstr(eventToConvert.Type)) + fixed (byte* eventMessage = ApiInterop.ToUtf8Cstr(eventToConvert.Message)) + { + internalEvent.Type = eventType; + internalEvent.Message = eventMessage; + + callback(internalEvent); + } + } + + public static unsafe Item ConvertItem(CEventTrace.Item* itemContainer) + { + var newItem = new Item(); + if (itemContainer->ItemType == 0) + { + // The item is newly initialized so return an empty Item + return newItem; + } + + newItem.ItemType = (ItemType) itemContainer->ItemType; + switch (newItem.ItemType) + { + case ItemType.Span: + newItem.Span = new Span(); + + var newSpan = newItem.Span.Value; + ApiInterop.Memcpy(newSpan.Id.Data, itemContainer->ItemUnion.Span.Id.Data, SpanId.SpanIdSize); + + newSpan.Causes = new SpanId[(int) itemContainer->ItemUnion.Span.CauseCount]; + for (var i = 0; i < newSpan.Causes.Length; i++) + { + fixed (byte* spanIdDest = newSpan.Causes[i].Data) + { + ApiInterop.Memcpy(spanIdDest, itemContainer->ItemUnion.Span.Causes[i].Data, SpanId.SpanIdSize); + } + } + + newItem.Span = newSpan; + break; + case ItemType.Event: + newItem.Event = new Event(); + + var newEvent = newItem.Event.GetValueOrDefault(); + newEvent.Id = new SpanId(); + ApiInterop.Memcpy(newEvent.Id.Data, itemContainer->ItemUnion.Event.Id.Data, SpanId.SpanIdSize); + + newEvent.UnixTimestampMillis = itemContainer->ItemUnion.Event.UnixTimestampMillis; + newEvent.Type = ApiInterop.FromUtf8Cstr(itemContainer->ItemUnion.Event.Type); + newEvent.Message = ApiInterop.FromUtf8Cstr(itemContainer->ItemUnion.Event.Message); + + newEvent.Data = new TraceEventData(itemContainer->ItemUnion.Event.Data); + var fields = newEvent.Data.GetAll(); + // Release memory allocated to the underlying event data in the itemContainer + newEvent.Data.EventData.Dispose(); + + // Add the data to the newly initialized event data struct + newEvent.Data = new TraceEventData(fields); + + newItem.Event = newEvent; + break; + default: + throw new NotSupportedException("Invalid Item Type provided."); + } + + return newItem; + } + + public static unsafe void ConvertItem(Item item, ItemCallback callback) + { + var newItem = Item.GetThreadLocalItem(); + + newItem->ItemUnion = new CEventTrace.Item.Union(); + newItem->ItemType = (CEventTrace.ItemType) item.ItemType; + switch (item.ItemType) + { + case ItemType.Span: + var spanItem = item.Span.Value; + newItem->ItemUnion.Span = new CEventTrace.Span(); + newItem->ItemUnion.Span.Id = ConvertSpanId(spanItem.Id); + newItem->ItemUnion.Span.CauseCount = (uint) spanItem.Causes.Length; + var causesPointer = stackalloc CEventTrace.SpanId[spanItem.Causes.Length]; + newItem->ItemUnion.Span.Causes = causesPointer; + for (var i = 0; i < newItem->ItemUnion.Span.CauseCount; i++) + { + newItem->ItemUnion.Span.Causes[i] = ConvertSpanId(spanItem.Causes[i]); + } + + callback(newItem); + + break; + case ItemType.Event: + var eventItem = item.Event.Value; + ConvertEvent(eventItem, internalEvent => + { + newItem->ItemUnion.Event = internalEvent; + callback(newItem); + }); + + break; + } + } + + public static unsafe void ConvertEventTracerParameters(EventTracerParameters[] parameters, EventTracerParametersCallback callback) + { + var internalParameters = ConvertTracerParameters(parameters, out var handles); + + fixed (CEventTrace.EventTracerParameters* parameterBuffer = internalParameters) + { + callback(parameterBuffer, handles); + } + } + + private static unsafe WrappedGcHandle ConvertTracerParameter(EventTracerParameters parameter, ref CEventTrace.EventTracerParameters internalParameters) + { + var wrappedParameterObject = new WrappedGcHandle(parameter); + + internalParameters.UserData = wrappedParameterObject.Get().ToPointer(); + internalParameters.TraceCallback = parameter.TraceCallback == null + ? IntPtr.Zero + : Marshal.GetFunctionPointerForDelegate(CallbackThunkDelegates.TraceCallbackThunkDelegate); + + return wrappedParameterObject; + } + + private static CEventTrace.EventTracerParameters[] ConvertTracerParameters(EventTracerParameters[] parameters, out WrappedGcHandle[] handles) + { + handles = new WrappedGcHandle[parameters.Length]; + var internalParameters = new CEventTrace.EventTracerParameters[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + { + var parameterHandle = ConvertTracerParameter(parameters[i], ref internalParameters[i]); + if (parameterHandle != null) + { + handles[i] = parameterHandle; + } + } + + return internalParameters; + } + + private static unsafe class CallbackThunkDelegates + { + public static readonly CEventTrace.TraceCallback TraceCallbackThunkDelegate = TraceCallbackThunk; + + [MonoPInvokeCallback(typeof(CEventTrace.TraceCallback))] + private static void TraceCallbackThunk(void* callbackPtr, CEventTrace.Item* responseItem) + { + var callbackHandle = (EventTracerParameters) GCHandle.FromIntPtr((IntPtr) callbackPtr).Target; + callbackHandle.TraceCallback(callbackHandle.UserData, ConvertItem(responseItem)); + } + } + } +} diff --git a/workers/unity/Packages/io.improbable.worker.sdk/ParameterConversion.cs.meta b/workers/unity/Packages/io.improbable.worker.sdk/ParameterConversion.cs.meta new file mode 100644 index 0000000000..a8b3d964da --- /dev/null +++ b/workers/unity/Packages/io.improbable.worker.sdk/ParameterConversion.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fcc8c334b8ba4bb1a6b265d4611783b4 +timeCreated: 1596556578 \ No newline at end of file