diff --git a/examples/cpp/SampleCppMini/main.cpp b/examples/cpp/SampleCppMini/main.cpp index 3cddf79a2..1d371ced6 100644 --- a/examples/cpp/SampleCppMini/main.cpp +++ b/examples/cpp/SampleCppMini/main.cpp @@ -31,7 +31,7 @@ void test_cpp_api(const char * token, int ticketType, const char *ticket) if (ticket != nullptr) { - const char *ticketNames[7] = + const char *ticketNames[8] = { "TicketType_MSA_Device", "TicketType_MSA_User", @@ -40,7 +40,7 @@ void test_cpp_api(const char * token, int ticketType, const char *ticket) "TicketType_AAD", "TicketType_AAD_User", "TicketType_AAD_JWT", - "TicketType_AAD_Device", + "TicketType_AAD_Device" }; printf("\nSet ticket %s=%s\n", ticketNames[ticketType], ticket); auto tc = LogManager::GetAuthTokensController(); diff --git a/lib/api/capi.cpp b/lib/api/capi.cpp index 3e0d9bd53..13d9f5b5d 100644 --- a/lib/api/capi.cpp +++ b/lib/api/capi.cpp @@ -242,10 +242,24 @@ evt_status_t mat_log(evt_context_t *ctx) EventProperties props; props.unpack(evt, ctx->size); + // Determine ingestion token to use for this record. + std::string token; + + // Use LogManager configuration primary token if available. + if (config.HasConfig(CFG_STR_PRIMARY_TOKEN)) + { + token = static_cast(config[CFG_STR_PRIMARY_TOKEN]); + } + + // Allow to override iKey per event via property. + // C API client maintains one handle for different tenants. auto m = props.GetProperties(); - EventProperty &prop = m[COMMONFIELDS_IKEY]; - std::string token = prop.as_string; - props.erase(COMMONFIELDS_IKEY); + if (m.count(COMMONFIELDS_IKEY)) + { + EventProperty& prop = m[COMMONFIELDS_IKEY]; + token = prop.as_string; + props.erase(COMMONFIELDS_IKEY); + } // Privacy feature for OTEL C API client: // diff --git a/lib/include/public/ctmacros.hpp b/lib/include/public/ctmacros.hpp index 42547e41d..b32b4b9b6 100644 --- a/lib/include/public/ctmacros.hpp +++ b/lib/include/public/ctmacros.hpp @@ -127,4 +127,24 @@ #define EVTSDK_LIBABI_CDECL MATSDK_LIBABI_CDECL #define EVTSDK_SPEC MATSDK_SPEC +/* Implement struct packing for stable FFI C API */ +#ifdef __clang__ +# define MATSDK_PACKED_STRUCT __attribute__((packed)) +# define MATSDK_PACK_PUSH +# define MATSDK_PACK_POP +#elif __GNUC__ +# define MATSDK_PACKED_STRUCT __attribute__((packed)) +# define MATSDK_PACK_PUSH +# define MATSDK_PACK_POP +#elif _MSC_VER +# define MATSDK_PACKED_STRUCT +# define MATSDK_PACK_PUSH __pragma(pack(push, 1)) +# define MATSDK_PACK_POP __pragma(pack(pop)) +#else +/* No packing behavior on unknown compilers */ +# define MATSDK_PACKED_STRUCT +# define MATSDK_PACK_PUSH +# define MATSDK_PACK_POP +#endif + #endif diff --git a/lib/include/public/mat.h b/lib/include/public/mat.h index 949f9a338..a633b1b89 100644 --- a/lib/include/public/mat.h +++ b/lib/include/public/mat.h @@ -9,7 +9,7 @@ * For version handshake check there is no mandatory requirement to update the $PATCH level. * Ref. https://semver.org/ for Semantic Versioning documentation. */ -#define TELEMETRY_EVENTS_VERSION "3.1.0" +#define TELEMETRY_EVENTS_VERSION "3.7.0" #include "ctmacros.hpp" @@ -59,7 +59,8 @@ extern "C" { EVT_OP_FLUSH = 0x0000000A, EVT_OP_VERSION = 0x0000000B, EVT_OP_OPEN_WITH_PARAMS = 0x0000000C, - EVT_OP_MAX = EVT_OP_OPEN_WITH_PARAMS + 1 + EVT_OP_MAX = EVT_OP_OPEN_WITH_PARAMS + 1, + EVT_OP_MAXINT = 0xFFFFFFFF } evt_call_t; typedef enum evt_prop_t @@ -79,11 +80,13 @@ extern "C" { TYPE_BOOL_ARRAY, TYPE_GUID_ARRAY, /* NULL-type */ - TYPE_NULL + TYPE_NULL, + TYPE_MAXINT = 0xFFFFFFFF } evt_prop_t; - typedef struct evt_guid_t + typedef struct MATSDK_PACKED_STRUCT evt_guid_t { +MATSDK_PACK_PUSH /** * * Specifies the first eight hexadecimal digits of the GUID. @@ -111,19 +114,22 @@ extern "C" { * */ uint8_t Data4[8]; +MATSDK_PACK_POP } evt_guid_t; typedef int64_t evt_handle_t; typedef int32_t evt_status_t; typedef struct evt_event evt_event; - typedef struct evt_context_t + typedef struct MATSDK_PACKED_STRUCT evt_context_t { +MATSDK_PACK_PUSH evt_call_t call; /* In */ evt_handle_t handle; /* In / Out */ void* data; /* In / Out */ evt_status_t result; /* Out */ uint32_t size; /* In / Out */ +MATSDK_PACK_POP } evt_context_t; /** @@ -183,12 +189,14 @@ extern "C" { uint64_t** as_arr_time; } evt_prop_v; - typedef struct evt_prop + typedef struct MATSDK_PACKED_STRUCT evt_prop { +MATSDK_PACK_PUSH const char* name; evt_prop_t type; evt_prop_v value; uint32_t piiKind; +MATSDK_PACK_POP } evt_prop; /** diff --git a/wrappers/netcore/EventNativeAPI.cs b/wrappers/netcore/EventNativeAPI.cs new file mode 100644 index 000000000..26ad7a82a --- /dev/null +++ b/wrappers/netcore/EventNativeAPI.cs @@ -0,0 +1,583 @@ +#pragma warning disable IDE1006 // ignore naming rule violations: we preserve original C API naming for clarity here +#pragma warning disable IDE0044 // ignore readonly suggestion for field passed over P/Invoke +#undef TRACE + +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.IO; + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Collections; + +namespace Microsoft +{ + namespace Telemetry + { + namespace Core + { + public class Constants + { + public const string LIBRARY_NAME = "ClientTelemetry"; + public const string VERSION = "3.7.0-netcore"; + } + + public enum EventCallType : UInt32 + { + EVT_OP_LOAD = 0x00000001, + EVT_OP_UNLOAD = 0x00000002, + EVT_OP_OPEN = 0x00000003, + EVT_OP_CLOSE = 0x00000004, + EVT_OP_CONFIG = 0x00000005, + EVT_OP_LOG = 0x00000006, + EVT_OP_PAUSE = 0x00000007, + EVT_OP_RESUME = 0x00000008, + EVT_OP_UPLOAD = 0x00000009, + EVT_OP_FLUSH = 0x0000000A, + EVT_OP_VERSION = 0x0000000B, + EVT_OP_OPEN_WITH_PARAMS = 0x0000000C, + EVT_OP_MAX = EVT_OP_OPEN_WITH_PARAMS + 1 + } + + public enum EventPropertyType : UInt32 + { + /* Basic types */ + TYPE_STRING = 0, + TYPE_INT64 = 1, + TYPE_DOUBLE = 2, + TYPE_TIME = 3, + TYPE_BOOLEAN = 4, + TYPE_GUID = 5, + /* Arrays of basic types */ + TYPE_STRING_ARRAY = 6, + TYPE_INT64_ARRAY = 7, + TYPE_DOUBLE_ARRAY = 8, + TYPE_TIME_ARRAY = 9, + TYPE_BOOL_ARRAY = 10, + TYPE_GUID_ARRAY = 11, + /* NULL-type */ + TYPE_NULL = 12 + } + + public struct PiiKind + { + /// No PII kind. + public const int None = 0; + /// An LDAP distinguished name. + public const int DistinguishedName = 1; + /// Generic data. + public const int GenericData = 2; + /// An IPV4 Internet address. + public const int IPv4Address = 3; + /// An IPV6 Internet address. + public const int IPv6Address = 4; + /// An e-mail subject. + public const int MailSubject = 5; + /// A telephone number. + public const int PhoneNumber = 6; + /// A query string. + public const int QueryString = 7; + /// A SIP address + public const int SipAddress = 8; + /// An e-mail address. + public const int SmtpAddress = 9; + /// An identity. + public const int Identity = 10; + /// A uniform resource indicator. + public const int Uri = 11; + /// A fully-qualified domain name. + public const int Fqdn = 12; + /// A legacy IPV4 Internet address. + public const int IPv4AddressLegacy = 13; + public const int CustomerData = 32; + } + + [StructLayout(LayoutKind.Explicit, Size = 16, CharSet = CharSet.Ansi)] + public unsafe struct EventGUIDType + { + /** + * + * Specifies the first eight hexadecimal digits of the GUID. + * + */ + [FieldOffset(0)] UInt32 Data1; + + /* + * Specifies the first group of four hexadecimal digits. + * + */ + [FieldOffset(4)] UInt16 Data2; + + /** + * + * Specifies the second group of four hexadecimal digits. + * + */ + [FieldOffset(6)] UInt16 Data3; + + /** + * An array of eight bytes. + * The first two bytes contain the third group of four hexadecimal digits. + * The remaining six bytes contain the final 12 hexadecimal digits. + * + */ + [FieldOffset(8)] + fixed byte Data4[8]; + } + + [StructLayout(LayoutKind.Explicit, Size = 28, CharSet = CharSet.Ansi)] + public unsafe struct EventContextType + { + [FieldOffset(0)] public UInt32 call; + [FieldOffset(4)] public ulong handle; + [FieldOffset(12)] public IntPtr data; + [FieldOffset(20)] public UInt32 result; + [FieldOffset(24)] public UInt32 size; + } + + public enum EventOpenParamType + { + OPEN_PARAM_TYPE_HTTP_HANDLER_SEND = 0, + OPEN_PARAM_TYPE_HTTP_HANDLER_CANCEL = 1, + OPEN_PARAM_TYPE_TASK_DISPATCHER_QUEUE = 2, + OPEN_PARAM_TYPE_TASK_DISPATCHER_CANCEL = 3, + OPEN_PARAM_TYPE_TASK_DISPATCHER_JOIN = 4 + } + + [StructLayout(LayoutKind.Explicit, Size = 12, CharSet = CharSet.Ansi)] + public unsafe struct EventOpenParam + { + [FieldOffset(0)] public UInt32 type; + [FieldOffset(4)] public IntPtr data; + }; + + [StructLayout(LayoutKind.Explicit, Size = 8, CharSet = CharSet.Ansi)] + public unsafe struct EventPropertyValue + { + /* Basic types */ + [FieldOffset(0)] public UInt64 as_uint64; + [FieldOffset(0)] public IntPtr as_string; + [FieldOffset(0)] public Int64 as_int64; + [FieldOffset(0)] public double as_double; + [FieldOffset(0)] public bool as_bool; + [FieldOffset(0)] public IntPtr as_guid; + [FieldOffset(0)] public UInt64 as_time; +#if FALSE + /* We don't support passing arrays yet. Code below needs to be ported from C++ to C# */ + /* Array types are nullptr-terminated array of pointers */ + char** as_arr_string; + int64_t** as_arr_int64; + bool** as_arr_bool; + double** as_arr_double; + evt_guid_t** as_arr_guid; + uint64_t** as_arr_time; +#endif + }; + + /** + * + * Wraps logger configuration string and all input parameters to 'evt_open_with_params' + * + */ + // TODO: this structure size depends on architecture - 32-bit or 64-bit.. + // Since all Mac OS X and Linux are mostly 64-bit by now, as well as + // most Windows - we should assume that the struct layout is optimized + // for 64-bit. From a practical standpoint - somebody building .NET Core + // app would likely consider running it on a platform that is modern + // enough. One way to solve this issue for 32-bit machines is to add a + // custom SDK build flag that enforces certain struct layout. i.e. + // positioning the two pointers below as 64-bit integers instead of 32-bit. + [StructLayout(LayoutKind.Explicit, Size = 20, CharSet = CharSet.Ansi)] + public unsafe struct EventOpenWithParamsDataType + { + [FieldOffset(0)] public IntPtr config; + [FieldOffset(8)] public IntPtr parameters; /* pointer to array of EventOpenParam */ + [FieldOffset(8)] public UInt32 paramsCount; + } + + [StructLayout(LayoutKind.Explicit, Size = 24, CharSet = CharSet.Ansi)] + public unsafe struct EventPropertyKeyValue + { + [FieldOffset(0)] public IntPtr name; + [FieldOffset(8)] public EventPropertyType type; + [FieldOffset(12)] public EventPropertyValue value; + [FieldOffset(20)] public UInt32 piiKind; + } + + /** + * + * Identifies HTTP request method type + * + */ + public enum HttpRequestType + { + HTTP_REQUEST_TYPE_GET = 0, + HTTP_REQUEST_TYPE_POST = 1, + } + + public class EventProperties : Dictionary + { + + public IntPtr nativeBuffer = IntPtr.Zero; + public int nativeSize = 0; + public int szEvtPropKV = Marshal.SizeOf(typeof(EventPropertyKeyValue)); + + public EventProperties() + { + } + internal unsafe void AllocNative() + { + nativeSize = (Count + 1) * szEvtPropKV; + nativeBuffer = Marshal.AllocHGlobal(nativeSize);// sizeof(EventPropertyKeyValue)); + int i = 0; + EventPropertyKeyValue* propPtr = (EventPropertyKeyValue*)(IntPtr.Zero); + foreach (KeyValuePair item in this) + { + propPtr = (EventPropertyKeyValue*)(nativeBuffer) + i; + (*propPtr).name = Marshal.StringToHGlobalAnsi(item.Key); + (*propPtr).piiKind = 0; // TODO: add Pii Kind support + (*propPtr).type = item.Value.type; +#if (TRACE) + Console.Write("0x{0:X} ", (long)propPtr); +#endif + switch (item.Value.type) + { + case EventPropertyType.TYPE_BOOLEAN: + (*propPtr).value.as_bool = item.Value.value.as_bool; +#if (TRACE) + Console.WriteLine("boolean {0}={1}", item.Key, (*propPtr).value.as_bool); +#endif + break; + case EventPropertyType.TYPE_DOUBLE: + (*propPtr).value.as_double = item.Value.value.as_double; +#if (TRACE) + Console.WriteLine("double {0}={1}", item.Key, (*propPtr).value.as_double); +#endif + break; + case EventPropertyType.TYPE_GUID: + // TODO: not implemented +#if (TRACE) + Console.WriteLine("guid {0}={1}", item.Key, (*propPtr).value.as_guid); +#endif + break; + case EventPropertyType.TYPE_INT64: + (*propPtr).value.as_int64 = item.Value.value.as_int64; +#if (TRACE) + Console.WriteLine("int64 {0}={1}", item.Key, (*propPtr).value.as_int64); +#endif + break; + case EventPropertyType.TYPE_STRING: + { + string s = (string)(item.Value.objValue); + (*propPtr).value.as_string = Marshal.StringToHGlobalAnsi(s); + (*propPtr).piiKind = item.Value.piiKind; +#if (TRACE) + Console.WriteLine("string {0}={1} (piiKind={2})", item.Key, s, item.Value.piiKind); +#endif + break; + } + default: + break; + } + i++; + } + /* NULL terminator property at the end of property list */ + propPtr = (EventPropertyKeyValue*)(nativeBuffer) + i; + (*propPtr).name = IntPtr.Zero; + (*propPtr).type = EventPropertyType.TYPE_NULL; + } + + internal unsafe void FreeNative() + { + if (nativeBuffer == IntPtr.Zero) + { + throw new Exception("Memory not allocated!"); + } + // TODO: assert count==Count + int count = nativeSize / szEvtPropKV; + for (int i = 0; i < count; i++) + { + EventPropertyKeyValue* propPtr = (EventPropertyKeyValue*)(nativeBuffer) + i; + EventPropertyType type = (EventPropertyType)((*propPtr).type); + switch (type) + { + case EventPropertyType.TYPE_GUID: + // TODO: not implemented + break; + case EventPropertyType.TYPE_STRING: + { + IntPtr strPtr = (*propPtr).value.as_string; + if (strPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(strPtr); + } + break; + } + default: + break; + } + if ((*propPtr).name != IntPtr.Zero) + { + Marshal.FreeHGlobal((*propPtr).name); + } + } + Marshal.FreeHGlobal(nativeBuffer); + nativeBuffer = IntPtr.Zero; + nativeSize = 0; + } + } + + public class EventProperty + { + public EventPropertyType type; + public EventPropertyValue value; + public object objValue = null; + public UInt32 piiKind = 0; + + public EventProperty(string strValue) + { + type = EventPropertyType.TYPE_STRING; + objValue = strValue; + } + + public EventProperty(string strValue, UInt32 piiKind) + { + type = EventPropertyType.TYPE_STRING; + objValue = strValue; + this.piiKind = piiKind; + } + + public EventProperty(Guid guidValue) + { + // TODO: compact GUID on wire is not implemented! + // Currently we flatten it to string. + // type = EventPropertyType.TYPE_GUID; + type = EventPropertyType.TYPE_STRING; + objValue = guidValue.ToString(); + } + + public EventProperty(Int64 intValue) + { + type = EventPropertyType.TYPE_INT64; + value.as_int64 = intValue; + } + + public EventProperty(int intValue) + { + type = EventPropertyType.TYPE_INT64; + value.as_int64 = intValue; + } + + public EventProperty(double doubleValue) + { + type = EventPropertyType.TYPE_DOUBLE; + value.as_double = doubleValue; + } + + public EventProperty(bool boolValue) + { + type = EventPropertyType.TYPE_BOOLEAN; + value.as_bool = boolValue; + } + + public static implicit operator EventProperty(string v) => new EventProperty(v); + public static implicit operator EventProperty(int v) => new EventProperty(v); + public static implicit operator EventProperty(double v) => new EventProperty(v); + public static implicit operator EventProperty(Guid v) => new EventProperty(v); + + public static EventProperty FromObject(object v) + { + if (v is Guid guid) + { + return new EventProperty(guid); + } + + switch (Type.GetTypeCode(v.GetType())) + { + case TypeCode.String: + return new EventProperty((string)v); + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + return new EventProperty((int)v); + case TypeCode.Double: + return new EventProperty((double)v); + case TypeCode.Boolean: + return new EventProperty((bool)v); + default: + break; + } + + return new EventProperty(v.ToString()); + } + }; + + public static class EventNativeAPI + { + // Conditional compilation: pass different library name depending on target OS + + [DllImport(Constants.LIBRARY_NAME, EntryPoint = "evt_api_call_default")] + internal static extern UInt32 evt_api_call([In, Out] ref EventContextType context); + + /** + * + * Create or open existing SDK instance. + * + * SDK configuration. + * SDK instance handle. + */ + public static ulong evt_open(string cfg) + { + EventContextType context = new EventContextType + { + call = (Byte)EventCallType.EVT_OP_OPEN, + data = Marshal.StringToHGlobalAnsi(cfg) + }; + evt_api_call(ref context); + Marshal.FreeHGlobal(context.data); + return context.handle; + } + + /** + * + * Destroy or close SDK instance by handle + * + * SDK instance handle. + * Status code. + */ + public static ulong evt_close(ulong inHandle) + { + EventContextType context = new EventContextType + { + call = (Byte)EventCallType.EVT_OP_CLOSE, + handle = inHandle + }; + return evt_api_call(ref context); + } + + /** + * REMEMBER! Privacy feature for OTEL C API client: + * + * C API customer that does not explicitly pass down JSON + * config["config]["scope"] = COMMONFIELDS_SCOPE_ALL; + * + * should not be able to capture the host's context vars. + */ + public static ulong evt_log(ulong inHandle, ref EventProperties properties) + { + ulong result = 0; + unsafe + { + properties.AllocNative(); + EventContextType context = new EventContextType + { + call = (Byte)EventCallType.EVT_OP_LOG, + handle = inHandle, + data = properties.nativeBuffer, + size = 0 /* (uint)(properties.Count) */ + }; + result = evt_api_call(ref context); + properties.FreeNative(); + }; + return result; + } + + /** + * + * Pauses transmission. In that mode events stay in ram or saved to disk, not sent. + * + * SDK handle. + * Status code. + */ + public static ulong evt_pause(ulong inHandle) + { + EventContextType context = new EventContextType + { + call = (Byte)EventCallType.EVT_OP_PAUSE, + handle = inHandle + }; + return evt_api_call(ref context); + } + + /** + * + * Resumes transmission. Pending telemetry events should be attempted to be sent. + * + * SDK handle. + * Status code. + */ + public static ulong evt_resume(ulong inHandle) + { + EventContextType context = new EventContextType + { + call = (Byte)EventCallType.EVT_OP_RESUME, + handle = inHandle + }; + return evt_api_call(ref context); + } + + /** + * Provide a hint to telemetry system to attempt force-upload of events + * without waiting for the next batch timer interval. This API does not + * guarantee the upload. + * + * SDK handle. + * Status code. + */ + public static ulong evt_upload(ulong inHandle) + { + EventContextType context = new EventContextType + { + call = (Byte)EventCallType.EVT_OP_UPLOAD, + handle = inHandle + }; + return evt_api_call(ref context); + } + + /** + * Save pending telemetry events to offline storage on disk. + * + * SDK handle. + * Status code. + */ + public static ulong evt_flush(ulong inHandle) + { + EventContextType context = new EventContextType + { + call = (Byte)EventCallType.EVT_OP_FLUSH, + handle = inHandle + }; + return evt_api_call(ref context); + } + + /** + * Pass down SDK header version to SDK library. Needed for late binding version checking. + * This method provides means of a handshake between library header and a library impl. + * It is up to app dev to verify the value returned, making a decision whether some SDK + * features are implemented/supported by particular SDK version or not. + * + * SDK header semver. + * SDK library semver + */ + public static string evt_version() + { + byte[] data = Encoding.ASCII.GetBytes(Constants.VERSION); + var nativeDataPtr = Marshal.AllocHGlobal(data.Length + 1); + Marshal.Copy(data, 0, nativeDataPtr, data.Length); + Marshal.WriteByte(nativeDataPtr + data.Length, 0); + EventContextType context = new EventContextType + { + call = (Byte)EventCallType.EVT_OP_VERSION, + data = nativeDataPtr + }; + evt_api_call(ref context); + string result = Marshal.PtrToStringAnsi(context.data); + Marshal.FreeHGlobal(nativeDataPtr); + return result; + } + } + + } + } +} diff --git a/wrappers/netcore/EventSender.csproj b/wrappers/netcore/EventSender.csproj new file mode 100644 index 000000000..73dc23c23 --- /dev/null +++ b/wrappers/netcore/EventSender.csproj @@ -0,0 +1,27 @@ + + + Exe + netcoreapp3.1 + true + + + + + PreserveNewest + + + + PreserveNewest + + + + + + + + diff --git a/wrappers/netcore/EventSender.xml b/wrappers/netcore/EventSender.xml new file mode 100644 index 000000000..b10fcd35f --- /dev/null +++ b/wrappers/netcore/EventSender.xml @@ -0,0 +1,4 @@ + + + + diff --git a/wrappers/netcore/Program.cs b/wrappers/netcore/Program.cs new file mode 100644 index 000000000..99b0133c8 --- /dev/null +++ b/wrappers/netcore/Program.cs @@ -0,0 +1,133 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.IO; +using System.Diagnostics; +using Microsoft.Telemetry.Core; + +#if HAVE_SYSTEM_JSON +// TODO: Unity 2021 doesn't have support for System.Json yet +using System.Json; +#else +using Newtonsoft.Json.Linq; +#endif + +namespace EventSender +{ + + class Program + { + + /// + /// Read 1DS SDK configuration from JSON configuration file. + /// + /// + /// + static string ReadConfiguration(string filename) + { + string result = ""; + using (StreamReader sr = new StreamReader(filename)) + { + result = sr.ReadToEnd(); + } + return result; + } + + /// + /// Run action in a loop and measure common performance characteristics. + /// + /// + /// + static void StressTest(Action action, int maxIterations) + { + long total0 = GC.GetTotalMemory(true); + long frag0 = GC.GetGCMemoryInfo().FragmentedBytes; + Stopwatch sw = new Stopwatch(); + sw.Start(); + + for (int i = 0; i < maxIterations; i++) + { + action(i); + } + + sw.Stop(); + long total1 = GC.GetTotalMemory(true); + long frag1 = GC.GetGCMemoryInfo().FragmentedBytes; + // Print some benchmarking results for offline storage + serialization + TimeSpan ts = sw.Elapsed; + Console.WriteLine("Elapsed = {0}", ts); + Console.WriteLine("Event rate = {0} eps", + (ts.TotalMilliseconds != 0) ? + (maxIterations / ts.TotalSeconds) : 1000); + Console.WriteLine("Latency = {0} ms", (ts.TotalMilliseconds / maxIterations)); + Console.WriteLine("Mem used = {0} bytes", total1 - total0); + } + + /// + /// Small demo how to use 1DS .NET Core API routed via 1DS C API. + /// All features of C API are supported. + /// + /// + static void Main(string[] args) + { + Console.WriteLine("Reading configuration..."); + string cfg = ReadConfiguration("sdk-config.json"); + + // Parse to verify it is valid and print it out.. + // Parser should throw if config is invalid. +#if HAVE_SYSTEM_JSON + JsonObject jsonDoc = (JsonObject)JsonObject.Parse(cfg); +#else + JObject jsonDoc = JObject.Parse(cfg); +#endif + Console.WriteLine("SDK configuration:\n{0}", jsonDoc.ToString()); + + // Obtain SDK version from native library + Console.WriteLine("SDK version: {0}", EventNativeAPI.evt_version()); + + // Initialize + Console.WriteLine(">>> evt_open..."); + var handle = EventNativeAPI.evt_open(cfg); + Console.WriteLine("handle={0}", handle); + + // Log something + Console.WriteLine(">>> evt_log..."); + var props = new EventProperties() { + { "name", "SampleNetCore" }, + { "strKey", "value1" }, + { "intKey", 12345 }, + { "dblKey", 0.12345 } , + { "guidKey", new Guid("73e21739-9d4e-497d-9c66-8e399a532ec9") } + }; + EventNativeAPI.evt_log(handle, ref props); + + // Now let's run a small stress test... + StressTest( + (param1) => + { + var props = new EventProperties() { + { "name", "SampleNetCore.PerfTest" }, + { "intKey", param1 }, + }; + EventNativeAPI.evt_log(handle, ref props); + } + , 10 // number of iterations + ); + + ulong result = 0; + + // Flush events to storage + result = EventNativeAPI.evt_flush(handle); + Console.WriteLine("result={0}", result); + + // Force upload of all pending events + result = EventNativeAPI.evt_upload(handle); + Console.WriteLine("result={0}", result); + + // FlushAndTeardown + Console.WriteLine(">>> evt_close..."); + result = EventNativeAPI.evt_close(handle); + Console.WriteLine("result={0}", result); + } + } +} diff --git a/wrappers/netcore/README.md b/wrappers/netcore/README.md new file mode 100644 index 000000000..a74a77c07 --- /dev/null +++ b/wrappers/netcore/README.md @@ -0,0 +1,19 @@ +# .NET Core wrapper example for 1DS C/C++ SDK + +Note that this wrapper is incomplete simple reference implementation that illustrates how to use 1DS C API from cross-platform .NET Core application. + +## POSIX instructions (Linux, Mac) + +1. Install latest .NET Core for your platform. + +2. Make sure you compile and install shared library build of SDK (`build.sh -l shared`). + +3. `run.sh` to compile and run the sample wrapper. + +## Windows instructions + +1. Install latest .NET Core for your platform. + +2. Open `Solutions\MSTelemetry.sln` and compile `win32-dll` project, producing `ClientTelemetry.dll` + +3. `run.cmd` to compile and run the sample wrapper. diff --git a/wrappers/netcore/appsettings.json b/wrappers/netcore/appsettings.json new file mode 100644 index 000000000..ecd461122 --- /dev/null +++ b/wrappers/netcore/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "IncludeScopes": true, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + }, + "Console": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } + } +} + diff --git a/wrappers/netcore/run.cmd b/wrappers/netcore/run.cmd new file mode 100644 index 000000000..3700b2611 --- /dev/null +++ b/wrappers/netcore/run.cmd @@ -0,0 +1,2 @@ +set "PATH=%CD%\..\..\Solutions\out\Debug\x64\win32-dll;%PATH%" +dotnet run -c Debug diff --git a/wrappers/netcore/run.sh b/wrappers/netcore/run.sh new file mode 100755 index 000000000..d975d50fc --- /dev/null +++ b/wrappers/netcore/run.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Mac OS X: +# brew cask install dotnet-sdk + +#export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH +#export DYLD_FALLBACK_LIBRARY_PATH=/usr/local/lib:$DYLD_FALLBACK_LIBRARY_PATH + +#dotnet run -c Debug -v diag +dotnet run -c Debug + + + diff --git a/wrappers/netcore/sdk-config.json b/wrappers/netcore/sdk-config.json new file mode 100644 index 000000000..dfc4796ed --- /dev/null +++ b/wrappers/netcore/sdk-config.json @@ -0,0 +1,56 @@ +{ + "config": { + "host": "*", + "scope": "*" + }, + "name": "C-API-Client-0", + "version": "1.0.0", + "cacheFileFullNotificationPercentage": 75, + "cacheFilePath": "offline-storage.db", + "cacheFileSizeLimitInBytes": 3145728, + "cacheFullNotificationIntervalTime": 5000, + "cacheMemoryFullNotificationPercentage": 75, + "cacheMemorySizeLimitInBytes": 524288, + "compat": { + "customTypePrefix": "custom", + "dotType": true + }, + "enableDbDropIfFull": false, + "enableLifecycleSession": false, + "enableNetworkDetector": true, + "enableTrace": true, + "eventCollectorUri": "https://mobile.events.data.microsoft.com/OneCollector/1.0/", + "hostMode": true, + "http": { + "compress": false, + "contentEncoding": "deflate", + "msRootCheck": false + }, + "maxDBFlushQueues": 3, + "maxPendingHTTPRequests": 4, + "maxTeardownUploadTimeInSec": 5, + "minimumTraceLevel": 1, + "multiTenantEnabled": true, + "primaryToken": "fba3c287ba474016b77e0ab612175255-8ef3561c-6103-4027-8ca0-2c79dcbd8901-6902", + "sample": { + "rate": 0 + }, + "sdkmode": 0, + "sessionResetEnabled": false, + "stats": { + "interval": 0, + "split": false, + "tokenInt": "8130ef8ff472405d89d6f420038927ea-0c0d561e-cca5-4c81-90ed-0aa9ad786a03-7166", + "tokenProd": "4bb4d6f7cafc4e9292f972dca2dcde42-bd019ee8-e59c-4b0f-a02c-84e72157a3ef-7485" + }, + "tpm": { + "backoffConfig": "E,3000,300000,2,1", + "clockSkewEnabled": true, + "maxBlobSize": 2097152, + "maxRetryCount": 5 + }, + "traceLevelMask": 4294967295, + "utc": { + "enabled": false + } +} \ No newline at end of file