summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/docs/dev/new_metric_types.md
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean/docs/dev/new_metric_types.md')
-rw-r--r--toolkit/components/glean/docs/dev/new_metric_types.md282
1 files changed, 282 insertions, 0 deletions
diff --git a/toolkit/components/glean/docs/dev/new_metric_types.md b/toolkit/components/glean/docs/dev/new_metric_types.md
new file mode 100644
index 0000000000..151b41dc34
--- /dev/null
+++ b/toolkit/components/glean/docs/dev/new_metric_types.md
@@ -0,0 +1,282 @@
+# Adding a New Metric Type
+
+This document covers how to add a new metric type to FOG.
+You should only have to do this if a new metric type is added to the
+[Glean SDK](https://mozilla.github.io/glean/book/user/metrics/index.html)
+and it is needed in Firefox Desktop.
+
+## IPC
+
+For detailed information about the IPC design,
+including a list of forbidden operations,
+please consult
+[the FOG IPC documentation](ipc.md).
+
+When adding a new metric type, the main IPC considerations are:
+* Which operations are forbidden because they are not commutative?
+ * Most `set`-style operations cannot be reconciled sensibly across multiple processes.
+* If there are non-forbidden operations,
+what partial representation will this metric have in non-main processes?
+Put another way, what shape of storage will this take up in the
+[IPC Payload](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/api/src/ipc.rs)?
+ * For example, Counters can aggregate all partial counts together to a single
+ "partial sum". So
+ [its representation](https://searchfox.org/mozilla-central/rev/803b368879fa332e8e2c1840bf1ec164f7ed2c32/toolkit/components/glean/api/src/ipc.rs#45)
+ in the IPC Payload is just a single number per Counter.
+ * In contrast, Timing Distributions' bucket arrangements are known only to the core,
+ so it can't combine sample counts in child processes.
+ Instead we record durations in the highest resolution (nanos),
+ and send a stream of high-precision samples across IPC.
+
+To implement IPC support in a metric type,
+we split the metric into three pieces:
+1. An umbrella `enum` with the name `MetricTypeMetric`.
+ * It has a `Child` and a `Parent` variant.
+ * It is IPC-aware and is responsible for
+ * If on a non-parent-process,
+ either storing partial representations in the IPC Payload,
+ or logging errors if forbidden non-test APIs are called.
+ (Or panicking if test APIs are called.)
+ * If on the parent process, dispatching API calls on its inner Rust Language Binding metric.
+2. The parent-process implementation is supplied by
+ [the RLB](https://crates.io/crates/glean/).
+ * For testing, it stores the `MetricId` that identifies this particular metric in a cross-process fashion.
+ * For testing, it exposes a `child_metric()` function to create its `Child` equivalent.
+ * For testing and if it supports operations in a non-parent-process,
+ it exposes a `metric_id()` function to access the stored `MetricId`.
+3. The `MetricTypeIpc` is the non-parent-process implementation.
+ * If it does support operations in non-parent processes it stores the
+ `MetricId` that identifies this particular metric in a cross-process fashion.
+
+## Mirrors
+
+FOG can mirror Glean metrics to Telemetry probes via the
+[Glean Interface For Firefox Telemetry](../user/gifft.md).
+
+Can this metric type be mirrored?
+Should it be mirrored?
+
+If so, add an appropriate Telemetry probe for it to mirror to,
+documenting the compatibility in
+[the GIFFT docs](../user/gifft.md).
+
+### GIFFT Tests
+
+If you add a GIFFT mirror, don't forget to test that the mirror works.
+You should be able to do this by adding a task to
+[`toolkit/components/glean/tests/xpcshell/test_GIFFT.js`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/tests/xpcshell/test_GIFFT.js).
+
+### GIFFT C++ State: Typical Locking and Shutdown
+
+Some metric types (`labeled_*`, `timespan`, `timing_distribution`)
+require holding state in C++ to make GIFFT work.
+Pings also hold state to support `testBeforeNextSubmit()`.
+If your new metric type requires state in C++,
+the current state-of-the-art is a `StaticDataMutex`-locked `UniquePtr` to a `nsTHashTable`.
+Access to the inner map is guarded by the lock and is controlled and lazily-instantiated through a single access function.
+[See Ping's `GetCallbackMapLock()`](https://searchfox.org/mozilla-central/source/toolkit/components/glean/bindings/private/Ping.cpp)
+for example.
+
+It is important to clear this state to avoid leaks.
+(See [bug 1752417](https://bugzilla.mozilla.org/show_bug.cgi?id=1752417).)
+However, instrumentation may call metrics APIs at any time.
+
+Therefore, GIFFT explicitly stops supporting these state-requiring operations after the
+[`AppShutdownTelemetry` shutdown phase](https://searchfox.org/mozilla-central/source/xpcom/base/ShutdownPhase.h).
+This is because during the next phase (`XPCOMWillShutdown`) we clear the state.
+
+## Rust
+
+FOG uses the Rust Language Binding APIs (the `glean` crate) with a layer of IPC on top.
+
+The IPC additions and glean-core trait implementations are in the
+[`private` module of the `fog` crate](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/api/src/metrics).
+
+Each metric type gets its own file, mimicking the structure in
+[`glean_core`](https://github.com/mozilla/glean/tree/main/glean-core/src/metrics)
+and [`glean`](https://github.com/mozilla/glean/tree/main/glean-core/rlb/src/private).
+Unless, of course, that metric is a labeled metric type.
+Then the sub metric type gets its own file,
+and you need to add "Labeledness" to it by implementing
+`Sealed` for your new type following the pattern in
+[`api/src/private/labeled.rs`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/api/src/private/labeled.rs).
+
+Every method on the metric type is public for now,
+including test methods,
+and is at least all the methods exposed via the
+[metric traits](https://github.com/mozilla/glean/tree/main/glean-core/src/traits).
+
+To support IPC and the MLA FFI (see below)
+we identify metric instances by MetricId and store them in maps in
+[the `__glean_metric_maps` mod of `metrics.rs`](https://hg.mozilla.org/mozilla-central/toolkit/components/glean/api/src/metrics.rs).
+This work is done by the `rust.py` and `rust(_pings).jinja2` extensions to `glean_parser` found
+[in the `build_scripts/glean_parser_ext/` folder](https://searchfox.org/mozilla-central/source/toolkit/components/glean/build_scripts/glean_parser_ext).
+
+You shouldn't have to edit these files for new metric types,
+as the original modifications to `glean_parser` for this type should already be generating correct code.
+
+### Rust Tests
+
+You should be able to smoke test the basic functionality in Rust unit tests.
+You can do this within the metric type implementation file directly.
+
+## C++ and JS
+
+The C++ and JS APIs are implemented [atop the Rust API](code_organization.md).
+We treat them both together since, though they're different languages,
+they're both implemented in C++ and share much of their implementation.
+
+The overall design is to build the C++ API atop the Multi-Language Architecture's
+(MLA's) FFI, then build the JS API atop the C++ API.
+This allows features like the
+[Glean Interface For Firefox Telemetry (GIFFT)](../user/gifft.md)
+that target only C++ and JS to be more simply implemented in the C++ layer.
+Exceptions to this (where the JS uses the FFI directly) are discouraged.
+
+Each metric type has six pieces you'll need to cover:
+
+### 1. MLA FFI
+
+- Using our convenient macros,
+ define the metric type's Multi-Language Architecture FFI layer above the Rust API in
+ [`api/src/ffi/`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/api/src/ffi/).
+
+### 2. C++ Impl
+
+- Implement a type called `XMetric` (e.g. `CounterMetric`) in `mozilla::glean::impl` in
+ [`bindings/private/`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/bindings/private/).
+ - Its methods should be named the same as the ones in the Rust API, transformed to `CamelCase`.
+ - They should all be public.
+ - Multiplex the FFI's `test_have` and `test_get` functions into a single
+ `TestGetValue` function that returns a
+ `mozilla::Maybe` wrapping the C++ type that best fits the metric type.
+- Include the new metric type in
+ [`bindings/MetricTypes.h`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/bindings/MetricTypes.h).
+- Include the new files in
+ [`moz.build`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/moz.build).
+ The header file should be added to `EXPORTS.mozilla.glean.bindings` and the
+ `.cpp` file should be added to `UNIFIED_SOURCES`.
+
+### 3. IDL
+
+- Duplicate the public API (including its docs) to
+ [`xpcom/nsIGleanMetrics.idl`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/xpcom/nsIGleanMetrics.idl)
+ with the name `nsIGleanX` (e.g. `nsIGleanCounter`).
+ - Inherit from `nsISupports`.
+ - The naming style for members here is `lowerCamelCase`.
+ You'll need a
+ [GUID](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Generating_GUIDs)
+ because this is XPCOM, but you'll only need the canonical form since we're only exposing to JS.
+ - The `testGetValue` method will return a
+ `jsval` to permit it to return `undefined` when there is no value.
+
+### 4. JS Impl
+
+- Add an `nsIGleanX`-deriving, `XMetric`-owning type called
+ `GleanX` (e.g. `GleanCounter`) in the same header and `.cpp` as `XMetric` in
+ [`bindings/private/`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/bindings/private/).
+ - Don't declare any methods beyond a ctor
+ (takes a `uint32_t` metric id, init-constructs a `impl::XMetric` member)
+ and dtor (`default`): the IDL will do the rest so long as you remember to add
+ `NS_DECL_ISUPPORTS` and `NS_DECL_NSIGLEANX`.
+ - In the definition of `GleanX`, member identifiers are back to
+ `CamelCase` and need macros like `NS_IMETHODIMP`.
+ Delegate operations to the owned `XMetric`, returning
+ `NS_OK` no matter what in non-test methods.
+ - Test-only methods can return `NS_ERROR` codes on failures,
+ but mostly return `NS_OK` and use `undefined` in the
+ `JS::MutableHandleValue` result to signal no value.
+
+### 6. Tests
+
+Two languages means two test suites.
+
+- Add a never-expiring test-only metric of your type to
+ [`test_metrics.yaml`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/tests/test_metrics.yaml).
+ - Feel free to be clever with the name,
+ but be sure to make clear that it is test-only.
+- **C++ Tests (GTest)** - Add a small test case to
+ [`gtest/TestFog.cpp`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/tests/gtest/TestFog.cpp).
+ - For more details, peruse the [testing docs](testing.md).
+- **JS Tests (xpcshell)** - Add a small test case to
+ [`xpcshell/test_Glean.js`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/tests/xpcshell/test_Glean.js)
+ and
+ [`xpcshell/test_JOG.js`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/tests/xpcshell/test_JOG.js).
+ If your metric type has supported IPC operations, also add cases to the `IPC` variants of these test files.
+ - For more details, peruse the [testing docs](testing.md).
+
+### 7. API Documentation
+
+Metric API Documentation is centralized in
+[the Glean SDK Book](https://mozilla.github.io/glean/book/user/metrics/index.html).
+
+You will need to craft a Pull Request against
+[the SDK](https://github.com/mozilla/glean/)
+adding a C++ and JS example to the specific metric type's API docs.
+
+Add a notice at the top of both examples that these APIs are only available in Firefox Desktop:
+````md
+<div data-lang="C++" class="tab">
+
+> **Note**: C++ APIs are only available in Firefox Desktop.
+
+```c++
+#include "mozilla/glean/GleanMetrics.h"
+
+mozilla::glean::category_name::metric_name.Api(args);
+```
+
+There are test APIs available too:
+
+```c++
+#include "mozilla/glean/GleanMetrics.h"
+
+ASSERT_EQ(value, mozilla::glean::category_name::metric_name.TestGetValue().ref());
+```
+</div>
+
+// and again for <div data-lang="JS">
+````
+
+If you're lucky, the Rust API will have already been added.
+Otherwise you'll need to write an example for that one too.
+
+### 8. Labeled metrics (if necessary)
+
+If your new metric type is Labeled, you have more work to do.
+I'm assuming you've already implemented the non-labeled sub metric type following the steps above.
+Now you must add "Labeledness" to it.
+
+There are four pieces to this:
+
+#### FFI
+
+- To add the writeable storage Rust will use to store the dynamically-generated sub metric instances,
+ add your sub metric type's map as a list item in the `submetric_maps` `mod` of
+ [`rust.jinja2`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2).
+- Following the pattern of the others, add a `fog_{your labeled metric name here}_get()` FFI API to
+ `api/src/ffi/mod.rs`.
+ This is what C++ and JS will use to allocate and retrieve sub metric instances by id.
+
+#### C++
+
+- Following the pattern of the others, add a template specialiation for `Labeled<YourSubMetric>::Get` to
+ [`bindings/private/Labeled.cpp`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/bindings/private/Labeled.cpp).
+ This will ensure C++ consumers can fetch or create sub metric instances.
+
+#### JS
+
+- Already handled for you since the JS types all inherit from `nsISupports`
+ and the JS template knows to add your new type to `NewSubMetricFromIds(...)`
+ (see `GleanLabeled::NamedGetter` if you're curious).
+
+#### Tests
+
+- The labeled variant will need tests the same as Step #6.
+ A tip: be sure to test two labels with different values.
+
+## Python Tests
+
+We have a suite of tests for ensuring code generation generates appropriate code.
+You should add a metric to [that suite](testing.md) for your new metric type.
+You will need to regenerate the expected files.