This document describes the architecture of the Glean SDK, covering the glean-core crate and its interaction with a foreign-language SDK.
Some of the listed things are not yet implemented as is, but desired.
The API available to foreign-language SDKs is defined in glean-core/src/glean.udl.
The top-level glean namespace describes the API that is used by foreign-language SDKs to configure & initialize the Glean object and change its state while running.
To avoid name clashes all functions are prefixed with glean_.
Some functions MUST NOT be used outside of test mode. These have a glean_test_ prefix.
Note: This DOES NOT correspond to the public Glean General API.
The foreign language SDK needs to implement the public API by deferring to glean_* functions.
All methods should include appropriate documentation.
Metric types are defined as a UniFFI interface.
interface CounterMetric {
...
};
This should describe the API exposed to users directly.
Implementation note:
When migrating a data type try to keep the API stable, e.g. use the same integer types.
Breaking changes (e.g. changing the integer type) can be changed in a followup.
(All example code below uses the Counter metric)
Every metric type needs a constructor, taking a CommonMetricData object and any additional configuration.
constructor(CommonMetricData meta);
Every metric type can export one or more recording methods.
void add(optional i32 amount = 1);
Every metric should export a test_get_value method.
i32? test_get_value(optional string? ping_name = null);
Additional methods can be exposed as well, e.g. to get errors.
TODO: Add example to the counter metric.
Everything exposed in the top-level glean namespace is implemented in glean-core/src/lib.rs (or publicly exported there).
Most methods there probably need to dispatch their task to be sure they run after Glean has been initialized.
If that's not the case they should get proper documentation why it's not necessary.
Test methods can use block_on_dispatcher and run synchronously.
This part holds additional state (global_state()), such as the client info and callbacks.
Note: Always lock the Glean object first, then acquire a handle to the global state, to avoid lock-order-inversion.
Implementation note: We might eventually merge that back into the Glean object to reduce the parts where we hold state.
The actual implementation of the Glean object is in glean-core/src/core/mod.rs.
This object holds all state of Glean, such as the database handle, upload state, etc.
Methods on the Glean object MUST run synchronously and not use the dispatcher.
Note: This code is (nearly) unmodified from the old glean-core implementation.
The core object defers to other objects for further work. Other objects, such as the database, ping maker, MUST run synchronously and not use the dispatcher.
Note: These other objects are also the unmodified code from the old glean-core implementation.
The dispatcher is based on the initial RLB implementation. Tasks will run on a separate thread. The dispatcher adds a test mode to run tasks asynchronously. This MUST NOT be used outside of foreign-language SDK tests.
Metric type implementations live in glean-core/src/metrics.
They should be based on the initial glean-core implementations,
but modified to expose the API defined for the RLB (see glean-core/rlb/src/private).
Recording methods should wrap everything into tasks launched on the dispatcher.
Actual recording should be a separate synchronous method with a _sync suffix.
That will make testing easier.
TODO: Sync methods should only be crate-public, but that requires us moving tests.
Test methods should block on the dispatcher, then run synchronously to return data.
Currently metric type tests are implemented as integration tests in glean-core/tests.
It's best to test the synchronous API of the metric,
as that way they can run in parallel with their own instance of a Glean object.
However that requires for synchronous methods to be exposed publicly.
Eventually we might migrate these tests back into unit tests within the crate, so we can turn synchronous methods to crate-public only.
The Glean Kotlin SDK lives in glean-core/android.
The general API is implemented in Glean.kt.
It imports the UniFFI-created module mozilla.telemetry.glean.internal.* and essentially wraps the exposed functions to provide the user-facing general API.
It holds a bit of state, including
- the HTTP uploader
- the metrics ping scheduler
- the lifecycle observer
- an
initializedflag
It can directly call glean-core exposed methods and MUST NOT use a dispatcher.
It can avoid calling glean-core methods if its not initialized.
The most complex part is the test mode, where it can be reset. Resetting the Glean object MUST NOT be possible in non-test usage.
The ping upload worker is responsible for getting ping upload requests, invoking the actual HTTP uploader and communicating back the status of the upload. It runs on the Android work manager. It SHOULD NOT use another dispatcher thread.
It uses the glean_*_upload_* methods from the global namespace.
That interface is described in Upload mechanism
TODO: Update the upload mechanism docs with the new UniFFI types
Most metric types SHOULD NOT need additional implementation in Kotlin. As they are exposed in a different namespace we can re-export them, eventually from a share file.
package mozilla.telemetry.glean.private
typealias CounterMetricType = mozilla.telemetry.glean.internal.CounterMetricSome more complex type might need additional work.
These should wrap the internal metric and add the additional state.
Most tests that only use the (test-)public API should conceptually continue to work as is.
They will require some changes to adopt the new syntax (e.g. passing the metric info as CommonMetricData to constructors).
If they don't pass we first need to make sure we didn't modify behavior of Glean itself. That's more likely than the test being wrong. However some previously test-public methods might be gone, so the test needs an update.
At this early stage the majority of tests does not work. Metric type tests will only work when that metric is converted to use UniFFI.