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 timespans 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.

Configuration

If you wanted to create a timing distribution to measure page load times, first you need to add an entry for it to the metrics.yaml file:

pages:
type: timing_distribution
description: >
Counts how long each page takes to load
...


API

Now you can use the timing distribution from the application's code. Starting a timer returns a timer ID that needs to be used to stop or cancel the timer at a later point. Multiple intervals can be measured concurrently. For example, to measure page load time on a number of tabs that are loading at the same time, each tab object needs to store the running timer ID.

import mozilla.components.service.glean.GleanTimerId
import org.mozilla.yourApplication.GleanMetrics.Pages

val timerId : GleanTimerId

fun onPageStart(e: Event) {
}

}


For convenience one can measure the time of a function or block of code:

Pages.pageLoad.measure {
}


There are test APIs available too. For convenience, properties sum and count are exposed to facilitate validating that data was recorded correctly.

Continuing the pageLoad example above, at this point the metric should have a sum == 11 and a count == 2:

import org.mozilla.yourApplication.GleanMetrics.Pages

// Was anything recorded?

// Get snapshot.

// Usually you don't know the exact timing values, but how many should have been recorded.
assertEquals(1L, snapshot.count)

// Assert that no errors were recorded.

import mozilla.components.service.glean.GleanTimerId;
import org.mozilla.yourApplication.GleanMetrics.Pages;

GleanTimerId timerId;

void onPageStart(Event e) {
}

}


There are test APIs available too. For convenience, properties sum and count are exposed to facilitate validating that data was recorded correctly.

Continuing the pageLoad example above, at this point the metric should have a sum == 11 and a count == 2:

import org.mozilla.yourApplication.GleanMetrics.Pages;

// Was anything recorded?

// Get snapshot.

// Usually you don't know the exact timing values, but how many should have been recorded.
assertEquals(1L, snapshot.getCount);

// Assert that no errors were recorded.
assertEquals(
0,
ErrorType.InvalidValue
)
);

import Glean

var timerId : GleanTimerId

func onPageStart() {
}

}


For convenience one can measure the time of a function or block of code:

Pages.pageLoad.measure {
}


There are test APIs available too. For convenience, properties sum and count are exposed to facilitate validating that data was recorded correctly.

Continuing the pageLoad example above, at this point the metric should have a sum == 11 and a count == 2:

@testable import Glean

// Was anything recorded?

// Get snapshot.

// Usually you don't know the exact timing values, but how many should have been recorded.
XCTAssertEqual(1, snapshot.count)

// Assert that no errors were recorded.

from glean import load_metrics

class PageHandler:
def __init__(self):
self.timer_id = None

def on_page_start(self, event):
# ...

# ...


The Python bindings also have a context manager for measuring time:

with metrics.pages.page_load.measure():


There are test APIs available too. For convenience, properties sum and count are exposed to facilitate validating that data was recorded correctly.

Continuing the page_load example above, at this point the metric should have a sum == 11 and a count == 2:

# Was anything recorded?

# Get snapshot.

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

# Assert that no errors were recorded.
ErrorType.INVALID_VALUE
)

using static Mozilla.YourApplication.GleanMetrics.Pages;

var timerId;

void onPageStart(Event e) {
}

}


There are test APIs available too. For convenience, properties sum and count are exposed to facilitate validating that data was recorded correctly.

Continuing the pageLoad example above, at this point the metric should have a sum == 11 and a count == 2:

using static Mozilla.YourApplication.GleanMetrics.Pages;

// Was anything recorded?

// Get snapshot.

// Usually you don't know the exact timing values, but how many should have been recorded.
Assert.Equal(1, snapshot.Values.Count);

// Assert that no errors were recorded.


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

fn on_page_start() {
}

}
}


There are test APIs available too.


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

// Was anything recorded?

// Assert no errors were recorded.
let errors = [
ErrorType::InvalidValue,
ErrorType::InvalidState,
ErrorType::InvalidOverflow
];
for error in errors {
}
}


Note: C++ APIs are only available in Firefox Desktop.

#include "mozilla/glean/GleanMetrics.h"

PR_Sleep(PR_MillisecondsToInterval(10));


There are test APIs available too:

#include "mozilla/glean/GleanMetrics.h"

// Does it have an expected values?
ASSERT_TRUE(data.sum > 0);


Note: JS APIs are only available in Firefox Desktop.

const timerId = Glean.pages.pageLoad.start();
await sleep(10);


There are test APIs available too:

Assert.ok(Glean.pages.pageLoad.testGetValue().sum > 0);


Limits

• Timings are recorded in nanoseconds.

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

Examples

• How long does it take a page to load?

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.

Simulator

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.