glean/glean-core/ARCHITECTURE.md

6.7 KiB

Architecture

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.

UniFFI definition

The API available to foreign-language SDKs is defined in glean-core/src/glean.udl.

namespace glean

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

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)

Constructor

Every metric type needs a constructor, taking a CommonMetricData object and any additional configuration.

constructor(CommonMetricData meta);

Recording methods

Every metric type can export one or more recording methods.

void add(optional i32 amount = 1);

Test methods

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.

Core implementation (Rust)

namespace glean

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.

Core

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.

Dispatcher

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 types

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.

Tests

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.

Foreign-language SDK: Kotlin

The Glean Kotlin SDK lives in glean-core/android.

General API

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 initialized flag

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.

Ping upload worker

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

Metric 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.CounterMetric

Some more complex type might need additional work. These should wrap the internal metric and add the additional state.

Tests

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.