Performance tracing within MetaMask is the act of tracking the durations of user flows and code execution.
This can be debugged locally via developer builds, but also occurs in user production builds.
The resulting data is uploaded to Sentry where it can be aggregated, analysed, and trigger alerts to notify us of reduced performance.
Automated tracing is provided by the @sentry/browser
and @sentry/react-native
packages.
They include the following integrations based on platform:
With no additional code, these automatically record the durations of operations such as:
- Browser Page Loads
- HTTP Callouts
- React Routing
Custom tracing is when we manually update the code to invoke specific utility functions to record durations as we see fit to align with conceptual user flows such as:
- Ethereum Transaction Processing
- Signature Request Processing
Or alternatively to track more technical code stages such as:
- UI Initialisation
The trace utilities provide an abstraction of the respective Sentry package to create Sentry transactions with minimum syntax, but maximum simplicity to support features such as:
- Tags
- Nested Traces
- Custom Timestamps
The TraceName
enum provides a central definition of all traces in use within the MetaMask client.
It also simplifies the creation of traces that are started and stopped from different locations in the code.
import { trace, TraceName } from '...';
function someFunction() {
return 1 + 1;
}
function someOtherFunction() {
...
const value = trace(
{ name: TraceName.SomeTrace },
someFunction
);
...
}
The trace
function automatically handles promises and records the duration until the promise is resolved or rejected.
import { trace, TraceName } from '...';
async function someFunction() {
return new Promise(resolve => {
...
resolve();
});
}
async function someOtherFunction() {
...
const value = await trace(
{ name: TraceName.SomeTrace },
someFunction
);
...
}
// File1.ts
import { trace, TraceName } from '...';
function someFunction() {
...
trace({ name: TraceName.SomeTrace });
...
}
// File2.ts
import { endTrace, TraceName } from '...';
function someOtherFunction() {
...
endTrace({ name: TraceName.SomeTrace });
...
}
Nested traces are reflected in Sentry and allow the breakdown of a larger duration into smaller named durations.
import { trace, TraceName } from '...';
function someNestedFunction1() {
return 1 + 1;
}
function someNestedFunction2() {
return 2 + 2;
}
function someParentFunction(traceContext: TraceContext) {
...
const value1 = trace(
{
name: TraceName.SomeNestedTrace1,
parentContext: traceContext
},
someNestedFunction1
);
const value2 = trace(
{
name: TraceName.SomeNestedTrace2,
parentContext: traceContext
},
someNestedFunction2
);
...
}
function someOtherFunction() {
...
trace(
{ name: TraceName.ParentTrace },
(traceContext) => someParentFunction(traceContext)
);
...
}
import { trace, TraceName } from '...';
function someFunction() {
return 1 + 1;
}
function someOtherFunction() {
...
const value = trace(
{
name: TraceName.SomeTrace
tags: {
someBoolean: true,
someString: 'test',
// Number tags are converted to Sentry measurements
// to support search operations such as >= and <=.
someNumber: 123
}
},
someFunction
);
...
}
import { trace, endTrace, TraceName } from '...';
...
trace({
name: TraceName.SomeTrace,
startTime: Date.now() - 123
});
...
endTrace({
name: TraceName.SomeTrace,
timestamp: Date.now() + 456
});
...