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
enum
with the nameMetricTypeMetric
.It has a
Child
and aParent
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.
The parent-process implementation is supplied by the RLB.
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 itsChild
equivalent.For testing and if it supports operations in a non-parent-process, it exposes a
metric_id()
function to access the storedMetricId
.
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.
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::impl
inbindings/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
andtest_get
functions into a singleTestGetValue
function that returns amozilla::Maybe
wrapping 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.bindings
and the.cpp
file should be added toUNIFIED_SOURCES
.
3. IDL¶
Duplicate the public API (including its docs) to
xpcom/nsIGleanMetrics.idl
with 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
testGetValue
method will return ajsval
to permit it to returnundefined
when there is no value.
4. JS Impl¶
Add an
nsIGleanX
-deriving,XMetric
-owning type calledGleanX
(e.g.GleanCounter
) in the same header and.cpp
asXMetric
inbindings/private/
.Don’t declare any methods beyond a ctor (takes a
uint32_t
metric id, init-constructs aimpl::XMetric
member) and dtor (default
): the IDL will do the rest so long as you remember to addNS_DECL_ISUPPORTS
andNS_DECL_NSIGLEANX
.In the definition of
GleanX
, member identifiers are back toCamelCase
and need macros likeNS_IMETHODIMP
. Delegate operations to the ownedXMetric
, returningNS_OK
no matter what in non-test methods.Test-only methods can return
NS_ERROR
codes on failures, but mostly returnNS_OK
and useundefined
in theJS::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
.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_maps
mod
ofrust.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>::Get
tobindings/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 toNewSubMetricFromIds(...)
(seeGleanLabeled::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 for your new metric type. You will need to regenerate the expected files.