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()

#![allow(unused)]
fn main() {
use glean_metrics::pages;

fn on_page_start() {
    self.timer_id = pages::page_load.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);
}

C++

#include "mozilla/glean/GleanMetrics.h"

mozilla::glean::pages::page_load.StopAndAccumulate(std::move(timerId));

JavaScript

Glean.pages.pageLoad.stopAndAccumulate(timerId);

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);
}

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.

import org.mozilla.yourApplication.GleanMetrics.Pages

// Get snapshot.
val snapshot = Pages.pageLoad.testGetValue()

// Usually you don't know the exact timing values,
// but how many should have been recorded.
assertEquals(1L, snapshot.count)
import org.mozilla.yourApplication.GleanMetrics.Pages;

// Get snapshot.
DistributionData snapshot = pages.INSTANCE.pageLoad().testGetValue();

// Usually you don't know the exact timing values,
// but how many should have been recorded.
assertEquals(1L, snapshot.getCount());
// Get snapshot.
let snapshot = try! pages.pageLoad.testGetValue()

// Usually you don't know the exact timing values,
// but how many should have been recorded.
XCTAssertEqual(1, snapshot.count)
from glean import load_metrics
metrics = load_metrics("metrics.yaml")

# Get snapshot.
snapshot = metrics.pages.page_load.test_get_value()

# Usually you don't know the exact timing values,
# but how many should have been recorded.
assert 1 == snapshot.count

#![allow(unused)]
fn main() {
use glean::ErrorType;
use glean_metrics::pages;

// Get snapshot
let snapshot = pages::page_load.test_get_value(None).unwrap();

// Usually you don't know the exact timing values,
// but how many should have been recorded.
assert_eq!(1, snapshot.values.len());
}

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);

testHasValue

Whether or not any value was recorded for a given timing distribution metric.

import org.mozilla.yourApplication.GleanMetrics.Pages

// Was anything recorded?
assertTrue(Pages.pageLoad.testHasValue())
import org.mozilla.yourApplication.GleanMetrics.Pages;

// Was anything recorded?
assertTrue(Pages.INSTANCE.pageLoad().testHasValue());
// Was anything recorded?
XCTAssert(pages.pageLoad.testHasValue())
from glean import load_metrics
metrics = load_metrics("metrics.yaml")

# Was anything recorded?
assert metrics.pages.page_load.test_has_value()

testGetNumRecordedErrors

Gets 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.InvalidValue))
import org.mozilla.yourApplication.GleanMetrics.Pages;

// Assert that no errors were recorded.
assertEquals(
    0,
    Pages.INSTANCE.pageLoad().testGetNumRecordedErrors(
        ErrorType.InvalidValue
    )
);
// 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)
);

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 a required time_unit parameter to specify the smallest unit of resolution that the timespan will record. The allowed values for time_unit are:

  • nanosecond
  • 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.