Timing Distribution
Timing distributions are used to accumulate and store time measurement, for analyzing distributions of the timing data.
To measure the distribution of single timespans, see Timespans. To record absolute times, see Datetimes.
Timing distributions are recorded in a histogram where the buckets have an exponential distribution, specifically with 8 buckets for every power of 2. That is, the function from a value \( x \) to a bucket index is:
\[ \lfloor 8 \log_2(x) \rfloor \]
This makes them suitable for measuring timings on a number of time scales without any configuration.
Note Check out how this bucketing algorithm would behave on the Simulator.
Timings always span the full length between start
and stopAndAccumulate
.
If the Glean upload is disabled when calling start
, the timer is still started.
If the Glean upload is disabled at the time stopAndAccumulate
is called, nothing is recorded.
Multiple concurrent timings in different threads may be measured at the same time.
Timings are always stored and sent in the payload as nanoseconds. However, the time_unit
parameter
controls the minimum and maximum values that will recorded:
nanosecond
: 1ns <= x <= 10 minutesmicrosecond
: 1μs <= x <= ~6.94 daysmillisecond
: 1ms <= x <= ~19 years
Overflowing this range is considered an error and is reported through the error reporting mechanism. Underflowing this range is not an error and the value is silently truncated to the minimum value.
Additionally, when a metric comes from GeckoView (the geckoview_datapoint
parameter is present),
the time_unit
parameter specifies the unit that the samples are in when passed to Glean.
Glean will convert all of the incoming samples to nanoseconds internally.
Recording API
start
Start tracking time for the provided metric.
Multiple timers can run simultaneously.
Returns a unique TimerId
for the new timer.
import mozilla.components.service.glean.GleanTimerId
import org.mozilla.yourApplication.GleanMetrics.Pages
val timerId : GleanTimerId
fun onPageStart(e: Event) {
timerId = Pages.pageLoad.start()
}
import mozilla.components.service.glean.GleanTimerId;
import org.mozilla.yourApplication.GleanMetrics.Pages;
GleanTimerId timerId;
void onPageStart(Event e) {
timerId = Pages.INSTANCE.pageLoad().start();
}
import Glean
var timerId : GleanTimerId
func onPageStart() {
timerId = Pages.pageLoad.start()
}
from glean import load_metrics
metrics = load_metrics("metrics.yaml")
class PageHandler:
def __init__(self):
self.timer_id = None
def on_page_start(self, event):
self.timer_id = metrics.pages.page_load.start()
use glean_metrics::pages;
fn on_page_start() {
self.timer_id = pages::page_load.start();
}
import * as pages from "./path/to/generated/files/pages.js";
function onPageStart() {
// store this ID, you will need it later to stop or cancel your timer
const timerId = pages.pageLoad.start();
}
C++
#include "mozilla/glean/GleanMetrics.h"
auto timerId = mozilla::glean::pages::page_load.Start();
JavaScript
let timerId = Glean.pages.pageLoad.start();
stopAndAccumulate
Stops tracking time for the provided metric and associated timer id.
Adds a count to the corresponding bucket in the timing distribution.
This will record an error if start
was not called.
import org.mozilla.yourApplication.GleanMetrics.Pages
fun onPageLoaded(e: Event) {
Pages.pageLoad.stopAndAccumulate(timerId)
}
import org.mozilla.yourApplication.GleanMetrics.Pages;
void onPageLoaded(Event e) {
Pages.INSTANCE.pageLoad().stopAndAccumulate(timerId);
}
import Glean
func onPageLoaded() {
Pages.pageLoad.stopAndAccumulate(timerId)
}
from glean import load_metrics
metrics = load_metrics("metrics.yaml")
class PageHandler:
def on_page_loaded(self, event):
metrics.pages.page_load.stop_and_accumulate(self.timer_id)
use glean_metrics::pages;
fn on_page_loaded() {
pages::page_load.stop_and_accumulate(self.timer_id);
}
import * as pages from "./path/to/generated/files/pages.js";
function onPageLoaded() {
pages.pageLoad.stopAndAccumulate(timerId);
}
C++
#include "mozilla/glean/GleanMetrics.h"
mozilla::glean::pages::page_load.StopAndAccumulate(std::move(timerId));
JavaScript
Glean.pages.pageLoad.stopAndAccumulate(timerId);
accumulateSamples
Accumulates the provided signed samples in the metric.
This is required so that the platform-specific code can provide us with
64 bit signed integers if no u64
comparable type is available. This
will take care of filtering and reporting errors for any provided negative
sample.
Please note that this assumes that the provided samples are already in
the "unit" declared by the instance of the metric type (e.g. if the
instance this method was called on is using TimeUnit::Second
, then
samples
are assumed to be in that unit).
import org.mozilla.yourApplication.GleanMetrics.Pages
fun onPageLoaded(e: Event) {
Pages.pageLoad.accumulateSamples(samples)
}
import org.mozilla.yourApplication.GleanMetrics.Pages;
void onPageLoaded(Event e) {
Pages.INSTANCE.pageLoad().accumulateSamples(samples);
}
from glean import load_metrics
metrics = load_metrics("metrics.yaml")
class PageHandler:
def on_page_loaded(self, event):
metrics.pages.page_load.accumulate_samples(samples)
use glean_metrics::pages;
fn on_page_loaded() {
pages::page_load.accumulate_samples(samples);
}
import * as pages from "./path/to/generated/files/pages.js";
function onPageLoaded() {
pages.pageLoad.accumulateSamples(samples);
}
C++
#include "mozilla/glean/GleanMetrics.h"
mozilla::glean::pages::page_load.AccumulateRawSamples(sample);
JavaScript
This operation is not currently supported in JavaScript.
accumulateSingleSample
Accumulates a single signed sample and appends it to the metric. Prefer this for the common use case of having a single value to avoid having to pass a collection over a foreign language interface.
A signed value is required so that the platform-specific code can provide
us with a 64 bit signed integer if no u64
comparable type is available.
This will take care of filtering and reporting errors for a negative
sample.
Please note that this assumes that the provided sample is already in
the "unit" declared by the instance of the metric type (e.g. if the
instance this method was called on is using TimeUnit::Second
, then
sample
is assumed to be in that unit).
import org.mozilla.yourApplication.GleanMetrics.Pages
fun onPageLoaded(e: Event) {
Pages.pageLoad.accumulateSingleSample(sample)
}
import org.mozilla.yourApplication.GleanMetrics.Pages;
void onPageLoaded(Event e) {
Pages.INSTANCE.pageLoad().accumulateSingleSample(sample);
}
from glean import load_metrics
metrics = load_metrics("metrics.yaml")
class PageHandler:
def on_page_loaded(self, event):
metrics.pages.page_load.accumulate_single_sample(sample)
use glean_metrics::pages;
fn on_page_loaded() {
pages::page_load.accumulate_single_sample(sample);
}
import * as pages from "./path/to/generated/files/pages.js";
function onPageLoaded() {
pages.pageLoad.accumulateSingleSample(sample);
}
C++
#include "mozilla/glean/GleanMetrics.h"
mozilla::glean::pages::page_load.AccumulateRawDuration(aDuration);
JavaScript
This operation is not currently supported in JavaScript.
Limits
- Samples are limited to the maximum value for the given time unit.
- Only non-negative values may be recorded (
>= 0
). - Negative values are discarded and an
ErrorType::InvalidValue
is generated for each instance. - Samples that are longer than maximum sample time for the given unit generate an
ErrorType::InvalidOverflow
error for each instance.
Recorded errors
invalid_value
: If recording a negative timespan.invalid_state
: If a non-existing/stopped timer is stopped again.invalid_overflow
: If recording a time longer than the maximum for the given unit.
measure
For convenience one can measure the time of a function or block of code.
import org.mozilla.yourApplication.GleanMetrics.Pages
Pages.pageLoad.measure {
// Load a page
}
import Glean
Pages.pageLoad.measure {
// Load a page
}
from glean import load_metrics
metrics = load_metrics("metrics.yaml")
with metrics.pages.page_load.measure():
# Load a page
cancel
Aborts a previous start
call.
No error is recorded if start
was not called.
import org.mozilla.yourApplication.GleanMetrics.Pages
fun onPageError(e: Event) {
Pages.pageLoad.cancel(timerId)
}
import org.mozilla.yourApplication.GleanMetrics.Pages;
fun onPageError(e: Event) {
Pages.INSTANCE.pageLoad().cancel(timerId);
}
import Glean
func onPageError() {
Pages.pageLoad.cancel(timerId)
}
from glean import load_metrics
metrics = load_metrics("metrics.yaml")
class PageHandler:
def on_page_error(self, event):
metrics.pages.page_load.cancel(self.timer_id)
use glean_metrics::pages;
fn on_page_error() {
pages::page_load.cancel(self.timer_id);
}
import * as pages from "./path/to/generated/files/pages.js";
function onPageError() {
pages.pageLoad.cancel(timerId);
}
C++
#include "mozilla/glean/GleanMetrics.h"
mozilla::glean::pages::page_load.Cancel(std::move(timerId));
JavaScript
Glean.pages.pageLoad.cancel(timerId);
Testing API
testGetValue
Gets the recorded value for a given timing distribution metric.
Returns a struct with counts per buckets and total sum if data is stored.
Returns a language-specific empty/null value if no data is stored.
Has an optional argument to specify the name of the ping you wish to retrieve data from, except
in Rust where it's required. None
or no argument will default to the first value found for send_in_pings
.
import org.mozilla.yourApplication.GleanMetrics.Pages
// Get snapshot.
val snapshot = Pages.pageLoad.testGetValue()
// Does the sum have the expected value?
assertEquals(11, snapshot.sum)
// Usually you don't know the exact timing values,
// but how many should have been recorded.
assertEquals(2L, snapshot.count)
import org.mozilla.yourApplication.GleanMetrics.Pages;
// Get snapshot.
DistributionData snapshot = pages.INSTANCE.pageLoad().testGetValue();
// Does the sum have the expected value?
assertEquals(11, snapshot.sum);
// Usually you don't know the exact timing values,
// but how many should have been recorded.
assertEquals(2L, snapshot.getCount());
// Get snapshot.
let snapshot = pages.pageLoad.testGetValue()
// Does the sum have the expected value?
XCTAssertEqual(11, snapshot.sum)
// Usually you don't know the exact timing values,
// but how many should have been recorded.
XCTAssertEqual(2, snapshot.count)
from glean import load_metrics
metrics = load_metrics("metrics.yaml")
# Get snapshot.
snapshot = metrics.pages.page_load.test_get_value()
# Does the sum have the expected value?
assert 11 == snapshot.sum
# Usually you don't know the exact timing values,
# but how many should have been recorded.
assert 2 == snapshot.count
use glean::ErrorType;
use glean_metrics::pages;
// Get snapshot
let snapshot = pages::page_load.test_get_value(None).unwrap();
// Does the sum have the expected value?
assert_eq!(11, snapshot.sum);
// Usually you don't know the exact timing values,
// but how many should have been recorded.
assert_eq!(2, snapshot.count);
import * as pages from "./path/to/generated/files/pages.js";
const snapshot = await pages.pageLoad.testGetValue();
// Usually you don't know the exact timing values,
// but how many should have been recorded.
assert.equal(1, snapshot.count);
C++
#include "mozilla/glean/GleanMetrics.h"
// Does it have an expected values?
const data = mozilla::glean::pages::page_load.TestGetValue().value().unwrap();
ASSERT_TRUE(data.sum > 0);
JavaScript
Assert.ok(Glean.pages.pageLoad.testGetValue().sum > 0);
testGetNumRecordedErrors
Gets the number of errors recorded for a given timing distribution metric.
import org.mozilla.yourApplication.GleanMetrics.Pages
// Assert that no errors were recorded.
assertEquals(
0,
Pages.pageLoad.testGetNumRecordedErrors(ErrorType.INVALID_VALUE)
)
import org.mozilla.yourApplication.GleanMetrics.Pages;
// Assert that no errors were recorded.
assertEquals(
0,
Pages.INSTANCE.pageLoad().testGetNumRecordedErrors(ErrorType.INVALID_VALUE)
);
// Assert that no errors were recorded.
XCTAssertEqual(0, Pages.pageLoad.testGetNumRecordedErrors(.invalidValue))
from glean import load_metrics
metrics = load_metrics("metrics.yaml")
# Assert that no errors were recorded.
assert 0 == metrics.pages.page_load.test_get_num_recorded_errors(
ErrorType.INVALID_VALUE
)
use glean::ErrorType;
use glean_metrics::pages;
assert_eq!(
0,
pages::page_load.test_get_num_recorded_errors(ErrorType::InvalidValue)
);
import * as pages from "./path/to/generated/files/pages.js";
import { ErrorType } from "@mozilla/glean/<platform>";
assert.equal(1, await pages.pageLoad.testGetNumRecordedErrors(ErrorType.InvalidValue));
Metric parameters
Example timing distribution metric definition:
pages:
page_load:
type: timing_distribution
time_unit: millisecond
description: >
Counts how long each page takes to load
bugs:
- https://bugzilla.mozilla.org/000000
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3
notification_emails:
- me@mozilla.com
expires: 2020-10-01
Extra metric parameters
time_unit
Timing distributions have an optional time_unit
parameter to specify the smallest unit of resolution that the timespan will record.
The allowed values for time_unit
are:
nanosecond
(default)microsecond
millisecond
second
minute
hour
day
Limits
-
Timings are recorded in nanoseconds.
-
On Android, the
SystemClock.elapsedRealtimeNanos()
function is used, so it is limited by the accuracy and performance of that timer. The time measurement includes time spent in sleep. -
On iOS, the
mach_absolute_time
function is used, so it is limited by the accuracy and performance of that timer. The time measurement does not include time spent in sleep. -
On Python 3.7 and later,
time.monotonic_ns()
is used. On earlier versions of Python,time.monotonics()
is used, which is not guaranteed to have nanosecond resolution. -
In Rust,
time::precise_time_ns()
is used.
-
-
The maximum timing value that will be recorded depends on the
time_unit
parameter:nanosecond
: 1ns <= x <= 10 minutesmicrosecond
: 1μs <= x <= ~6.94 daysmillisecond
: 1ms <= x <= ~19 years
Longer times will be truncated to the maximum value and an error will be recorded.
Data questions
- How long does it take a page to load?
Reference
Simulator
Please, insert your custom data below as a JSON array.
Note The data provided, is assumed to be in the configured time unit. The data recorded, on the other hand, is always in nanoseconds. This means that, if the configured time unit is not
nanoseconds
, the data will be transformed before being recorded. Notice this, by using the select field above to change the time unit and see the mean of the data recorded changing.