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 minutes
  • microsecond: 1μs <= x <= ~6.94 days
  • millisecond: 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);
}

This API is not currently exposed in Firefox Desktop, see Bug 1884183.

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

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 minutes
    • microsecond: 1μs <= x <= ~6.94 days
    • millisecond: 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.

Data options

Properties

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.