Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C API improvements. Host-Guest improvements. Ability to update context via C API. #1122

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
7fd5bef
Initial commit of changes to simplify creation of shared library
maxgolov Jan 8, 2020
7a99957
Add netcore30 wrapper example on top of shared lib
maxgolov Jan 8, 2020
4abaff9
Clean-up the wrapper a little bit
maxgolov Jan 8, 2020
bde1f87
Merge branch 'master' into maxgolov/shared_library
maxgolov Jan 9, 2020
c73b4ef
Add more APIs to netcore implementation
maxgolov Jan 18, 2020
2ddf49d
Merge branch 'master' into maxgolov/shared_library
maxgolov Jan 18, 2020
30b7fe0
Working implementation of C# .NET Core 3.1 wrapper on top of C API (t…
maxgolov Jan 19, 2020
5a3059c
Add some support for GUID type (not the final impl)
maxgolov Jan 19, 2020
9f638ea
Merge branch 'master' into maxgolov/shared_library
maxgolov Mar 31, 2020
72cec82
Merge branch 'master' of https://github.com/microsoft/cpp_client_tele…
maxgolov Apr 12, 2020
d0a1bf5
Merge branch 'maxgolov/shared_library' of https://github.com/microsof…
maxgolov Apr 12, 2020
1d87d06
Merge branch 'master' into maxgolov/shared_library
maxgolov Apr 16, 2020
3702210
Merge branch 'master' into maxgolov/shared_library
maxgolov May 2, 2020
f05f1f6
Merge branch 'master' into maxgolov/shared_library
maxgolov May 21, 2020
dd687da
Merge branch 'master' of https://github.com/microsoft/cpp_client_tele…
maxgolov Sep 25, 2020
0b54436
Merge branch 'master' of https://github.com/microsoft/cpp_client_tele…
maxgolov Sep 25, 2020
3e0c9a6
Verify that the .NET Core wrapper sample works on Windows
maxgolov Sep 25, 2020
cf47c90
Merge branch 'master' into maxgolov/shared_library
maxgolov Sep 25, 2020
214f3ba
Merge branch 'master' into maxgolov/shared_library
maxgolov Sep 27, 2020
489e2e2
Merge branch 'master' into maxgolov/shared_library
maxgolov Oct 2, 2020
e607fa4
Merge branch 'master' into maxgolov/shared_library
maxgolov Oct 6, 2020
acadc08
Merge branch 'maxgolov/shared_library' of https://github.com/microsof…
maxgolov Feb 12, 2023
ef6ccbd
Move packing behavior where it would fit better - ctmacros.hpp
maxgolov Feb 12, 2023
2faa5c1
There really is 8 of them, not 7
maxgolov Feb 12, 2023
d2bbc61
Clean-up C# side
maxgolov Feb 13, 2023
3266cd9
Allow C API to decide what token to use: LogManager primaryToken or i…
maxgolov Feb 13, 2023
515e059
Since packing is a potentially ABI-breaking change, need to update th…
maxgolov Feb 13, 2023
7f525d9
Minor bugfix
maxgolov Feb 13, 2023
9de1d48
Use debug library in the sample
maxgolov Feb 13, 2023
b9b8fc0
Add helper method and use Newtonsoft for now
maxgolov Feb 13, 2023
7bb5310
.NET Core wrapper clean-up
maxgolov Mar 6, 2023
d969ba9
Sync to main
maxgolov Mar 28, 2023
0fbefc4
Merge branch 'main' of https://github.com/microsoft/cpp_client_teleme…
maxgolov Mar 28, 2023
22eb472
Cherry-pick latest changes
maxgolov Mar 28, 2023
8a3c556
Merge branch 'main' of https://github.com/microsoft/cpp_client_teleme…
maxgolov Mar 30, 2023
70f52d0
Revert change
maxgolov Mar 30, 2023
35236a2
C API improvements
maxgolov Mar 30, 2023
061b8ce
Add design document
maxgolov Mar 30, 2023
67dadfe
Fix typos
maxgolov Mar 30, 2023
1748192
Remove unnecessary files
maxgolov Mar 30, 2023
99682b6
Fix a few typos and improve markdown looks
maxgolov Mar 30, 2023
e1d3d34
Merge branch 'main' into maxgolov/c_api_context
maxgolov Mar 30, 2023
e44dcb2
Fix sign mismatch warning for Linux tests
maxgolov Mar 30, 2023
872e46d
Merge branch 'maxgolov/c_api_context' of https://github.com/microsoft…
maxgolov Mar 30, 2023
af67f9c
- Implement cross-arch 32-bit vs 64-bit standard layout.
maxgolov Apr 4, 2023
1201248
Fix typo.
maxgolov Apr 4, 2023
ae60ce2
Merge branch 'main' into maxgolov/c_api_context
maxgolov Apr 4, 2023
880f4a3
Merge branch 'main' of https://github.com/microsoft/cpp_client_teleme…
maxgolov Jun 3, 2023
f909036
Merge branch 'main' into maxgolov/c_api_context
maxgolov Jun 5, 2023
01ddfff
Merge branch 'maxgolov/c_api_context' of https://github.com/microsoft…
maxgolov Jun 5, 2023
ee8d88d
Merge branch 'main' into maxgolov/c_api_context
lalitb Jun 13, 2023
f6a65e7
Merge branch 'main' into maxgolov/c_api_context
maxgolov Jul 6, 2023
70d7ba0
Merge branch 'maxgolov/c_api_context' of https://github.com/microsoft…
maxgolov Jul 6, 2023
5de639a
Merge branch 'main' of https://github.com/microsoft/cpp_client_teleme…
maxgolov Sep 12, 2023
fe63e64
Formatting change and resolve merge conflicts
maxgolov Sep 13, 2023
3f6bf3d
Revert unrelated changes
maxgolov Sep 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
361 changes: 361 additions & 0 deletions docs/host-guest-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,361 @@
# 1DS C/C++ Host-Guest API detailed design

## Preface

There are scenarios where hybrid applications (C#, C/C++, JavaScript) may need to propagate telemetry
to Common Shared Telemetry library (1DS).

In those scenarios all parts of application need to discover the SDK instance. For example:

- App Core C++ SDK initializes Telemetry Stack as Telemetry `Host`, letting other delay-loaded components
of application to reuse its telemetry stack.
- App Core C++ SDK could use `LogManager::SetContext(...)` and Semantic Context C++ APIs to populate some
common low-level knowledge, accessible only from C++ layer, ex. `ext.user.localId`.
- Extension SDKs (Plugins) could act as Telemetry "Guest". These would discover the existing telemetry
stack and could use `evt_set_logmanager_context` C API to append their context variables to shared
telemetry context.
- App Common C# layer could also act as Telemetry "Guest". It latches itself to the existing instance
of native telemetry stack, appending its own variables available from application store, ex. `ext.app.name`,
`ext.app.id`. Since packaged application also knows from its package what platform it is designed for,
it could populate `ext.os.name`. For example, set it to `Meta Quest 2` / `Meta Quest Pro` intead of
generic `Android` moniker. Presently 1DS C/C++ SDK itself cannot auto-discover those intricacies.
- App layer could also propagate additional compile-time constants, such as `app.build.env`, git tag
of app, etc.

Consolidated Shared Telemetry Context contains a set of fields populated by various elements of
a system stack. From top C# / JS layers to the bottom C++ layer, spanning across extension plugins /
libraries loaded in app context. Those libraries could consume telemetry stack via C API.

Shared context properties could get stamped on all events emitted by a product, irrespective of whether
these events originated from a high-level app written in C#, or a lower-level extension SDK written in C.

## Ultimate user guide to 1DS C/C++ SDK Host-Guest API

`Host`-`Guest` API has been designed for the following scenarios:

### Sharing telemetry stack

Main component - `Host` loads its accessory component (or SDK) - `Guest`. Both components use the
same shared dynamically loadable 1DS C++ SDK binary, e.g. `ClientTelemetry.dll`, `libmaesdk.so`, or
`libcat.so` - whatever is the "distro" used to package 1DS C++ SDK.

`Guest` could dynamically discover and load 1DS C++ via C API. It could latch to currently initialized
instance of its `Host` component `LogManager`. `Guest` could also create its own totally separate sandboxed
instance of a `Guest` `LogManager`. `GetProcAddress` is supported on Windows. `dlsym` supported on Linux
and Android. Lazy-binding (automagic binding / auto-discovery of `Host` telemetry stack) is supported
on Linux, Mac and Android. `P/Invoke` for C# is also fully supported cross-platform for .NET and Mono.
1DS C API provides one unified struct layout, with packed structs approach that works on modern
Intel-x64 and ARM64 OS. One single C# assembly could interoperate with 1DS C++ SDK in a uniform way.

### SDK-in-SDK scenario

Native code SDK could load another extension/accessory SDK. Both parts must share the same telemetry stack.
Extension SDK could be written in C or C++. Main SDK is treated as `Host`, additional SDKs are treated
as `Guests`. `Host` could also facilitate the ingection of Diagnostic Data Viewer plugin, in order to
satisfy our Privacy and Compliance oblogations. Additional `Guest` modules could enrich the main `Host`
shared telemetry context with their properties.

### Telemetry flows and `Telemetry Data Isolation` scenarios

In some cases many different application modules (plugins) get loaded into the main app address space.
For example, `Azure Calling SDK` or `Microsoft Information Protection SDK` running in another product.
These plugin(s) may not necessarily need to share their telemetry flows with the main app. In that case
the modules must operate within their own trust and data collection boundary. Data uploads need to be
controlled separately, with Required Service Data flowing to Azure location of a service resource;
while Optional Customer Data may need to flow to its own regional EUDB-compliant collector.

`Host`-`Guest` API solves this challenge by providing partitioning for different components using the
same telemetry SDK. If necessary, different modules telemetry collection processes run totally isolated
from one another.

## Common Considerations

`HostGuestTests.cpp` module in functional test contains several usage examples.

See detailed explanation of configuration options and examples below.

### Dissecting Host configuration

`Host` configuration example:

```json
{
"cacheFilePath": "MyOfflineStorage.db",
"config": {
"host": "C-API-Host",
"scope": "*"
},
"stats": {
"interval": 0
},
"name": "C-API-Host",
"version": "1.0.0",
"primaryToken": "ffffffffffffffffffffffffffffffff-ffffffff-ffff-ffff-ffff-ffffffffffff-0001"
"maxTeardownUploadTimeInSec": 5,
"hostMode": true,
"minimumTraceLevel": 0,
"sdkmode": 0
}
```

`Host` could specify the two matching parameters:

- `"host": "C-API-Host"`
- `"name": "C-API-Host"`

If host parameter matches the name parameter, then it assumed that the `Host` module acts as the one and
only `Host` in the application. It will be creating its own data collection sandbox. It will not latch to
any other `Host` modules that could be running in the same app. Multiple `Host` modules supported.

In some scenarios a `Host` would prefer to latch (join) an existing telemetry session. This is especially
helpful if multiple Hosts need to share one data collection domain and their startup/load order is not
clearly defined. In that case, a session initialized by first `Host` could be shared with other Hosts.
Data collection domain performs ref-counting of instances latched to it.

Hosts could specify `"host": "*"` to attach to existing data collection session. If first `Host` leaves
(unloads or closes its handle), remaining entities in that session continue operating until the last
`Host` leaves the data collection domain.

Both Guests and Hosts may utilize the `scope` parameter that controls if these would be sharing the
same common telemetry context shared within a sandbox:

- `scope="*"` - SHARED or ALL, means that a component will contribute its context to shared context.
- `scope="-"` - NONE or RESTRICTED, means that a component will not contribute its context, and will
not receive any values from the shared context. This mechanism allows to satisfy data collection
and privacy obligations. Each entity acts within their own data collection and compliance boundary
without sharing any of their telemetry contexts with other modules in the process.

## Dissecting Guest configuration

Guest configuration example:

```json
{
"cacheFilePath": "MyOfflineStorage.db",
"config": {
"host": "*",
"scope": "*"
},
"stats": {
"interval": 0
},
"name": "C-API-Guest",
"version": "1.0.0",
"primaryToken": "ffffffffffffffffffffffffffffffff-ffffffff-ffff-ffff-ffff-ffffffffffff-0002",
"maxTeardownUploadTimeInSec": 5,
"hostMode": false,
"minimumTraceLevel": 0,
"sdkmode": 0
}
```

Guest entity:

- specifies its own data storage file. This is helpful if Guest starts up prior to any other `Host`.
- `"host": "*"` parameter allows the Guest to latch to any host.
- `"scope": "*"` parameter allows the Guest to contribute and share its telemetry context with other modules (`Host` and Guests).
- Hosts and Guests to present themselves with unique name, ex. `"name": "C-API-Guest"` and unique version, ex. `1.0.0`.
- Guest must specify `"hostMode": false`. That is how SDK knows that a Guest is expected to join another `Host`'s sandbox.
- Guest may omit the scope parameter. In this case the Guest cannot capture the main `Host` telemetry contexts.
This is done intentionally as a security feature. Main application developers may ask their plugin developers
to never capture any telemetry contexts populated by the main application. For example, in some cases - main
application `ext.user.localId` or session `TraceId` cannot be shared with extension. There is no explicit
permission model. Since most components are expected to be assembled and tested by product development teams,
the team should audit the usage of Guest scope parameter by the plugins it is loading. There is runtime code
isolation provided by this mechanism. It is based on trust that all loadable modules exercise their due
diligence while setting up their telemetry configuration.

### End-to-end example

`Host` code:

```cpp
// Host JSON configuration:
const char* hostConfig = JSON_CONFIG(
{
"cacheFilePath" : "/some/path/MyOfflineStorage.db",
"config" : {
"host" : "C-API-Host",
"scope" : "*"
},
"name" : "C-API-Host",
"version" : "1.0.0",
"primaryToken" : "ffffffffffffffffffffffffffffffff-ffffffff-ffff-ffff-ffff-ffffffffffff-0001",
"hostMode" : true
});

// Host initializes in Host mode, waiting for Guest()s to register.
evt_handle_t hostHandle = evt_open(hostConfig);

// evt_prop[] array that contains common context properties.
// Contexts between Hosts and Guests could be merged into one shared context.
evt_prop hostContext[] = TELEMETRY_EVENT(
_STR("ext.device.localId", "a:4318b22fbc11ca8f"),
_STR("ext.device.make", "Microsoft"),
_STR("ext.device.model", "Clippy"),
_STR("ext.os.name", "MS-DOS"),
_STR("ext.os.ver", "2100")
);

// Host appends common context properties at top-level LogManager.
// These variables will be shared with Guest(s).
evt_set_logmanager_context(hostHandle, hostContext);

evt_prop hostEvent[] = TELEMETRY_EVENT(
// Part A/B fields
_STR(COMMONFIELDS_EVENT_NAME, "Event.Host"),
_INT(COMMONFIELDS_EVENT_PRIORITY, static_cast<int64_t>(EventPriority_Immediate)),
_INT(COMMONFIELDS_EVENT_LATENCY, static_cast<int64_t>(EventLatency_Max)),
_INT(COMMONFIELDS_EVENT_LEVEL, DIAG_LEVEL_REQUIRED),
_STR("strKey", "value1"),
_INT("intKey", 12345),
_DBL("dblKey", 3.14),
_BOOL("boolKey", true),
_GUID("guidKey", "{01020304-0506-0708-090a-0b0c0d0e0f00}");
evt_log(hostHandle, hostEvent);

```

In above example:

- `Host` performs initialization.
- populates its top-level LogManager semantic context with known values.

For example, the `Host` C++ layer could use native API to access the lower-level platform-specific
Device Id, Device Make, Model. `Host` may emit a telemetry event that would combine the event data
with its context data.

Guest code:

```cpp

// Guest JSON configuration:
const char* guestConfig = JSON_CONFIG(
{
"config" : {
"host" : "*",
"scope" : "*"
},
"name" : "C-API-Guest",
"version" : "1.0.0",
"primaryToken" : "ffffffffffffffffffffffffffffffff-ffffffff-ffff-ffff-ffff-ffffffffffff-0002",
"hostMode" : false
});

// Guest initializes in Guest mode and latches to previously running Host.
auto guestHandle = evt_open(guestConfig);

// evt_prop[] array that contains context properties:
evt_prop guestContext[] = TELEMETRY_EVENT(
_STR("ext.app.id", "com.Microsoft.Clippy"),
_STR("ext.app.ver", "1.0.0"),
_STR("ext.app.locale", "en-US"),
_STR("ext.net.cost", "Unmetered"),
_STR("ext.net.type", "QuantumLeap"));

// Guest could append some of its common context properties on top of shared context:
evt_set_logmanager_context(guestHandle, guestContext);

evt_prop guestEvent[] = TELEMETRY_EVENT(
_STR("name", "Event.Guest"),
_INT(COMMONFIELDS_EVENT_PRIORITY, static_cast<int64_t>(EventPriority_Immediate)),
_INT(COMMONFIELDS_EVENT_LATENCY, static_cast<int64_t>(EventLatency_Max)),
_INT(COMMONFIELDS_EVENT_LEVEL, DIAG_LEVEL_REQUIRED),
_STR("strKey", "value2"),
_INT("intKey", 67890),
_DBL("dblKey", 3.14),
_BOOL("boolKey", false),
_GUID("guidKey", "{01020304-0506-0708-090a-0b0c0d0e0f01}");
evt_log(guestHandle, guestEvent);

```

In above example:

- Guest registers and shares the scope with the `Host`.
- Guest entity could operate on a totally different abstraction layer, e.g. higher-level Unity C# or Android Java app.
It could obtain certain system parameters that are easily accessible only by the higher-level app. Such as, app store
application name and version. It could be a layer that performs User Authentication and Authorization, subsequently
sharing the User Identity as part of common telemetry context shared with lower-level code across the language boundary.

Reference design showing how to use 1DS C API from .NET Core, Mono and Unity applications is provided.

Above examples generate the following event payloads.

`Host` Event payload in Common Schema notation:

```json
{
"data": {
"boolKey": true,
"dblKey": 3.14,
"guidKey": [[4,3,2,1,6,5,8,7,9,10,11,12,13,14,15,0]],
"intKey": 12345,
"strKey": "value1"
},
"ext": {
"device": {
"localId": "a:4318b22fbc11ca8f",
"make": "Microsoft",
"model": "Clippy"
},
"os": {
"name": "MS-DOS",
"ver": "2100"
}
},
"iKey": "o:7c8b1796cbc44bd5a03803c01c2b9d61",
"name": "Event.Host",
"time": 1680074712000,
"ver": "3.0"
}
```

Guest Event payload in Common Schema notation. Note that Guest event emitted after `Host` initialization
contains the superset of all consolidated common properties:

```json
{
"data": {
"boolKey": true,
"dblKey": 3.14,
"guidKey": [[4,3,2,1,6,5,8,7,9,10,11,12,13,14,15,0]],
"intKey": 12345,
"strKey": "value1"
},
"ext": {
"app": {
"id": "com.Microsoft.Clippy",
"locale": "en-US",
"name": "com.Microsoft.Clippy",
"ver": "1.0.0"
},
"device": {
"localId": "a:4318b22fbc11ca8f",
"make": "Microsoft",
"model": "Clippy"
},
"net": {
"cost": "Unmetered",
"provider": "",
"type": "QuantumLeap"
},
"os": {
"name": "MS-DOS",
"ver": "2100"
}
},
"iKey": "o:7c8b1796cbc44bd5a03803c01c2b9d61",
"name": "Event.Guest",
"time": 1680074712000,
"ver": "3.0"
}
```

`Host`-`Guest` approach allows us to share one common telemetry diagnostic context across the language
boundaries in a hybrid application designed with different programming languages: C/C++, C#, and
JavaScript. Other programming languages may easily leverage Foreign Function Interface and 1DS C API.

`Host`-`Guest` interface plays a central role in aggregation of different module contexts into one
common shared telemetry context of application. C++ example is available in `SampleCppLogManagers`
project.
12 changes: 3 additions & 9 deletions examples/c/SampleC/deploy-dll.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,9 @@
set PROJECT_DIR=%~dp0

@mkdir %PROJECT_DIR%\include
copy %PROJECT_DIR%..\..\..\lib\include\public\mat.h %PROJECT_DIR%\include
copy %PROJECT_DIR%..\..\..\lib\include\public\Version.h %PROJECT_DIR%\include
copy %PROJECT_DIR%..\..\..\lib\include\public\ctmacros.hpp %PROJECT_DIR%\include
copy /Y %PROJECT_DIR%..\..\..\lib\include\public\mat.h %PROJECT_DIR%\include
copy /Y %PROJECT_DIR%..\..\..\lib\include\public\ctmacros.hpp %PROJECT_DIR%\include

@mkdir %PROJECT_DIR%\lib\%1\%2
copy %PROJECT_DIR%..\..\..\Solutions\out\%1\%2\win32-dll\*.lib %PROJECT_DIR%\lib\%1\%2

@mkdir -p %PROJECT_DIR%\%1\%2
copy %PROJECT_DIR%..\..\
copy %PROJECT_DIR%..\..\..\Solutions\out\%1\%2\win32-dll\*.* %PROJECT_DIR%\lib\%1\%2
copy %PROJECT_DIR%..\..\..\Solutions\out\%1\%2\win32-dll\*.* %3
robocopy %PROJECT_DIR%..\..\..\Solutions\out\%1\%2\win32-dll %3 *.dll /S
exit /b 0
Loading