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 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.
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?
For example, Counters can aggregate all partial counts together to a single “partial sum”. So its representation 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:
An umbrella
enumwith the nameMetricTypeMetric.It has a
Childand aParentvariant.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.
The parent-process implementation is supplied by the RLB.
For testing, it stores the
MetricIdthat identifies this particular metric in a cross-process fashion.For testing, it exposes a
child_metric()function to create itsChildequivalent.For testing and if it supports operations in a non-parent-process, it exposes a
metric_id()function to access the storedMetricId.
The
MetricTypeIpcis the non-parent-process implementation.If it does support operations in non-parent processes it stores the
MetricIdthat 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.
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.
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.
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.
Each metric type gets its own file, mimicking the structure in
glean_core
and glean.
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.
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.
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. 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) 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/.
2. C++ Impl¶
Implement a type called
XMetric(e.g.CounterMetric) inmozilla::glean::implinbindings/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_haveandtest_getfunctions into a singleTestGetValuefunction that returns amozilla::Maybewrapping the C++ type that best fits the metric type.
Include the new metric type in
bindings/MetricTypes.h.Include the new files in
moz.build. The header file should be added toEXPORTS.mozilla.glean.bindingsand the.cppfile should be added toUNIFIED_SOURCES.
3. IDL¶
Duplicate the public API (including its docs) to
xpcom/nsIGleanMetrics.idlwith the namensIGleanX(e.g.nsIGleanCounter).Inherit from
nsISupports.The naming style for members here is
lowerCamelCase. You’ll need a GUID because this is XPCOM, but you’ll only need the canonical form since we’re only exposing to JS.The
testGetValuemethod will return ajsvalto permit it to returnundefinedwhen there is no value.
4. JS Impl¶
Add an
nsIGleanX-deriving,XMetric-owning type calledGleanX(e.g.GleanCounter) in the same header and.cppasXMetricinbindings/private/.Don’t declare any methods beyond a ctor (takes a
uint32_tmetric id, init-constructs aimpl::XMetricmember) and dtor (default): the IDL will do the rest so long as you remember to addNS_DECL_ISUPPORTSandNS_DECL_NSIGLEANX.In the definition of
GleanX, member identifiers are back toCamelCaseand need macros likeNS_IMETHODIMP. Delegate operations to the ownedXMetric, returningNS_OKno matter what in non-test methods.Test-only methods can return
NS_ERRORcodes on failures, but mostly returnNS_OKand useundefinedin theJS::MutableHandleValueresult 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.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.For more details, peruse the testing docs.
JS Tests (xpcshell) - Add a small test case to
xpcshell/test_Glean.js.For more details, peruse the testing docs.
7. API Documentation¶
Metric API Documentation is centralized in the Glean SDK Book.
You will need to craft a Pull Request against the SDK 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:
<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_mapsmodofrust.jinja2.Following the pattern of the others, add a
fog_{your labeled metric name here}_get()FFI API toapi/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>::Gettobindings/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
nsISupportsand the JS template knows to add your new type toNewSubMetricFromIds(...)(seeGleanLabeled::NamedGetterif 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 for your new metric type. You will need to regenerate the expected files.