671 lines
28 KiB
Markdown
671 lines
28 KiB
Markdown
# Markers
|
||
|
||
Markers are packets of arbitrary data that are added to a profile by the Firefox code, usually to
|
||
indicate something important happening at a point in time, or during an interval of time.
|
||
|
||
Each marker has a name, a category, some common optional information (timing, backtrace, etc.),
|
||
and an optional payload of a specific type (containing arbitrary data relevant to that type).
|
||
|
||
:::{note}
|
||
This guide explains C++ markers in depth. To learn more about how to add a
|
||
marker in JavaScript, Rust or JVM, please take a look at their documentation
|
||
in {doc}`instrumenting-javascript`, {doc}`instrumenting-rust` or
|
||
{doc}`instrumenting-android` respectively.
|
||
:::
|
||
|
||
## Example
|
||
|
||
Short example, details below.
|
||
|
||
Note: Most marker-related identifiers are in the `mozilla` namespace, to be added where necessary.
|
||
|
||
```cpp
|
||
// Record a simple marker with the category of DOM.
|
||
PROFILER_MARKER_UNTYPED("Marker Name", DOM);
|
||
|
||
// Create a marker with some additional text information. (Be wary of printf!)
|
||
PROFILER_MARKER_TEXT("Marker Name", JS, MarkerOptions{}, "Additional text information.");
|
||
|
||
// Record a simple marker with automatic schema generation from variable names and types.
|
||
PROFILER_MARKER_SIMPLE_PAYLOAD("My Marker", DOM, MarkerOptions{}, mOpaque, mCount);
|
||
|
||
// Record a custom marker of type `ExampleNumberMarker` (see definition below).
|
||
PROFILER_MARKER("Number", OTHER, MarkerOptions{}, ExampleNumberMarker, 42);
|
||
```
|
||
|
||
```cpp
|
||
// Marker type definition.
|
||
struct ExampleNumberMarker : public BaseMarkerType<ExampleNumberMarker> {
|
||
// Unique marker type name.
|
||
static constexpr const char* Name = "number";
|
||
// Marker description.
|
||
static constexpr const char* Description = "This is a number marker.";
|
||
|
||
// For convenience.
|
||
using MS = MarkerSchema;
|
||
// Fields of payload for the marker.
|
||
static constexpr MS::PayloadField PayloadFields[] = {
|
||
{"number", MS::InputType::Uint32t, "Number", MS::Format::Integer}};
|
||
|
||
// Locations this marker should be displayed.
|
||
static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
|
||
MS::Location::MarkerTable};
|
||
// Location specific label for this marker.
|
||
static constexpr const char* ChartLabel = "Number: {marker.data.number}";
|
||
|
||
// Data specific to this marker type, as passed to PROFILER_MARKER/profiler_add_marker.
|
||
static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter, uint32_t a number) {
|
||
// Custom writer for marker fields, or using the default parent
|
||
// implementation if the function arguments match the schema.
|
||
StreamJSONMarkerDataImpl(aWriter, a number);
|
||
}
|
||
};
|
||
```
|
||
|
||
When adding a marker whose arguments differ from the schema, a translator
|
||
function and a custom implementation of StreamJSONMarkerData can be used.
|
||
|
||
```c++
|
||
// Marker type definition.
|
||
struct ExampleBooleanMarker : public BaseMarkerType<ExampleBooleanMarker> {
|
||
// Unique marker type name.
|
||
static constexpr const char* Name = "boolean";
|
||
// Marker description.
|
||
static constexpr const char* Description = "This is a boolean marker.";
|
||
|
||
// For convenience.
|
||
using MS = MarkerSchema;
|
||
// Fields of payload for the marker.
|
||
static constexpr MS::PayloadField PayloadFields[] = {
|
||
{"boolean", MS::InputType::CString, "Boolean"}};
|
||
|
||
// Locations this marker should be displayed.
|
||
static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
|
||
MS::Location::MarkerTable};
|
||
// Location specific label for this marker.
|
||
static constexpr const char* ChartLabel = "Boolean: {marker.data.boolean}";
|
||
|
||
// Data specific to this marker type, as passed to PROFILER_MARKER/profiler_add_marker.
|
||
static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter, bool aBoolean) {
|
||
// Note the schema expects a string, we cannot use the default implementation.
|
||
if (aBoolean) {
|
||
aWriter.StringProperty("boolean", "true");
|
||
} else {
|
||
aWriter.StringProperty("boolean", "false");
|
||
}
|
||
}
|
||
|
||
// The translation to the schema must also be defined in a translator function.
|
||
// The argument list should match that to PROFILER_MARKER/profiler_add_marker.
|
||
static void TranslateMarkerInputToSchema(void* aContext, bool aBoolean) {
|
||
// This should call ETW::OutputMarkerSchema with an argument list matching the schema.
|
||
if (aIsStart) {
|
||
ETW::OutputMarkerSchema(aContext, ExampleBooleanMarker{}, ProfilerStringView("true"));
|
||
} else {
|
||
ETW::OutputMarkerSchema(aContext, ExampleBooleanMarker{}, ProfilerStringView("false"));
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
A more detailed description is offered below.
|
||
|
||
## How to Record Markers
|
||
|
||
### Header to Include
|
||
|
||
If the compilation unit only defines and records untyped, text, and/or its own markers, include
|
||
{searchfox}`the main profiler markers header <tools/profiler/public/ProfilerMarkers.h>`:
|
||
|
||
```cpp
|
||
#include "mozilla/ProfilerMarkers.h"
|
||
```
|
||
|
||
If it also records one of the other common markers defined in
|
||
{searchfox}`ProfilerMarkerTypes.h <tools/profiler/public/ProfilerMarkerTypes.h>`,
|
||
include that one instead:
|
||
|
||
```cpp
|
||
#include "mozilla/ProfilerMarkerTypes.h"
|
||
```
|
||
|
||
And if it uses any other profiler functions (e.g., labels), use
|
||
{searchfox}`the main Gecko Profiler header <tools/profiler/public/GeckoProfiler.h>`
|
||
instead:
|
||
|
||
```cpp
|
||
#include "GeckoProfiler.h"
|
||
```
|
||
|
||
The above works from source files that end up in libxul, which is true for the majority
|
||
of Firefox source code. But some files live outside of libxul, such as mfbt, in which
|
||
case the advice is the same but the equivalent headers are from the Base Profiler instead:
|
||
|
||
```cpp
|
||
#include "mozilla/BaseProfilerMarkers.h" // Only own/untyped/text markers
|
||
#include "mozilla/BaseProfilerMarkerTypes.h" // Only common markers
|
||
#include "mozilla/BaseProfiler.h" // Markers and other profiler functions
|
||
```
|
||
|
||
### Untyped Markers
|
||
|
||
Untyped markers don't carry any information apart from common marker data:
|
||
Name, category, options.
|
||
|
||
```cpp
|
||
PROFILER_MARKER_UNTYPED(
|
||
// Name, and category pair.
|
||
"Marker Name", OTHER,
|
||
// Marker options, may be omitted if all defaults are acceptable.
|
||
MarkerOptions(MarkerStack::Capture(), ...));
|
||
```
|
||
|
||
`PROFILER_MARKER_UNTYPED` is a macro that simplifies the use of the main
|
||
`profiler_add_marker` function, by adding the appropriate namespaces.
|
||
|
||
1. Marker name
|
||
: The first argument is the name of this marker. This will be displayed in most places
|
||
the marker is shown. It can be a literal C string, or any dynamic string object.
|
||
2. {searchfox}`Category pair name <__GENERATED__/mozglue/baseprofiler/public/ProfilingCategoryList.h>`
|
||
: Choose a category + subcategory from the {searchfox}`the list of categories <mozglue/baseprofiler/build/profiling_categories.yaml>`.
|
||
This is the second parameter of each `SUBCATEGORY` line, for instance `LAYOUT_Reflow`.
|
||
(Internally, this is really a [MarkerCategory](https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerCategory)
|
||
object, in case you need to construct it elsewhere.)
|
||
3. [MarkerOptions](https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerOptions)
|
||
: See the options below. It can be omitted if there are no other arguments, `{}`, or
|
||
`MarkerOptions()` (no specified options); only one of the following option types
|
||
alone; or `MarkerOptions(...)` with one or more of the following options types:
|
||
|
||
- [MarkerThreadId](https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerThreadId)
|
||
: Rarely used, as it defaults to the current thread. Otherwise it specifies the target
|
||
"thread id" (aka "track") where the marker should appear; This may be useful when
|
||
referring to something that happened on another thread (use `profiler_current_thread_id()`
|
||
from the original thread to get its id); or for some important markers, they may be
|
||
sent to the "main thread", which can be specified with `MarkerThreadId::MainThread()`.
|
||
- [MarkerTiming](https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerTiming)
|
||
: This specifies an instant or interval of time. It defaults to the current instant if
|
||
left unspecified. Otherwise use `MarkerTiming::InstantAt(timestamp)` or
|
||
`MarkerTiming::Interval(ts1, ts2)`; timestamps are usually captured with
|
||
`TimeStamp::Now()`. It is also possible to record only the start or the end of an
|
||
interval, pairs of start/end markers will be matched by their name. *Note: The
|
||
upcoming "marker sets" feature will make this pairing more reliable, and also
|
||
allow more than two markers to be connected*.
|
||
- [MarkerStack](https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerStack)
|
||
: By default, markers do not record a "stack" (or "backtrace"). To record a stack at
|
||
this point, in the most efficient manner, specify `MarkerStack::Capture()`. To
|
||
record a previously captured stack, first store a stack into a
|
||
`UniquePtr<ProfileChunkedBuffer>` with `profiler_capture_backtrace()`, then pass
|
||
it to the marker with `MarkerStack::TakeBacktrace(std::move(stack))`.
|
||
- [MarkerInnerWindowId](https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerInnerWindowId)
|
||
: If you have access to an "inner window id", consider specifying it as an option, to
|
||
help profiler.firefox.com to classify them by tab.
|
||
|
||
### "Auto" Scoped Interval Markers
|
||
|
||
To capture time intervals around some important operations, it is common to store a timestamp, do the work,
|
||
and then record a marker, e.g.:
|
||
|
||
```cpp
|
||
void DoTimedWork() {
|
||
TimeStamp start = TimeStamp::Now();
|
||
DoWork();
|
||
PROFILER_MARKER_TEXT("Timed work", OTHER, MarkerTiming::IntervalUntilNowFrom(start), "Details");
|
||
}
|
||
```
|
||
|
||
[RAII](https://en.cppreference.com/w/cpp/language/raii) objects automate this, by recording the time
|
||
when the object is constructed, and later recording the marker when the object is destroyed at the end
|
||
of its C++ scope.
|
||
This is especially useful if there are multiple scope exit points.
|
||
|
||
`AUTO_PROFILER_MARKER_UNTYPED` and `AUTO_PROFILER_MARKER_TEXT` are the [only](https://searchfox.org/mozilla-central/search?q=id%3AAUTO_PROFILER_MARKER_UNTYPED)
|
||
[ones](https://searchfox.org/mozilla-central/search?q=id%3AAUTO_PROFILER_MARKER_TEXT)
|
||
implemented at this time.
|
||
|
||
```cpp
|
||
void MaybeDoTimedWork(bool aDoIt) {
|
||
AUTO_PROFILER_MARKER_UNTYPED("Timed work", OTHER);
|
||
if (!aDoIt) { /* Marker recorded here... */ return; }
|
||
DoWork();
|
||
/* ... or here. */
|
||
}
|
||
|
||
void MaybeDoTimedWork(bool aDoIt) {
|
||
AUTO_PROFILER_MARKER_TEXT("Timed work", OTHER, "Details");
|
||
if (!aDoIt) { /* Marker recorded here... */ return; }
|
||
DoWork();
|
||
/* ... or here. */
|
||
}
|
||
```
|
||
|
||
Note that these RAII objects only record one marker. In some situation, a very long
|
||
operation could be missed if it hasn't completed by the end of the profiling session.
|
||
In this case, consider recording two distinct markers, using
|
||
`MarkerTiming::IntervalStart()` and `MarkerTiming::IntervalEnd()`.
|
||
|
||
### Flow markers
|
||
|
||
Markers can be part of a `flow`. Those are ids that allow associating
|
||
different markers together. A `flow` is a 64-bits integer that's formatted as
|
||
either a `Flow` or a `TerminatingFlow`. `TerminatingFlows` mark the end of
|
||
a flow and allow a flow id to be reused for a new flow. This is especially
|
||
useful when using Flow ids derived from pointer values.
|
||
|
||
Flow markers are useful, for example, to annotate asynchronous operations, or
|
||
associate different parts of a long operation. In addition, it is possible to
|
||
determine the flow that was responsible for creating another flow, allowing
|
||
tracing through the causes and consequences of a series of (often asynchronous)
|
||
events.
|
||
|
||
```cpp
|
||
nsHttpChannel::OnStartRequest(nsIRequest* request) {
|
||
// A marker that spans the duration of this method
|
||
AUTO_PROFILER_FLOW_MARKER("nsHttpChannel::OnStartRequest", NETWORK, Flow::FromPointer(this));
|
||
// ...
|
||
}
|
||
|
||
nsHttpChannel::ConnectOnTailUnblock() {
|
||
// Will be in the same flow as this method execution, that also lasts the
|
||
// duration of the block
|
||
AUTO_PROFILER_FLOW_MARKER("nsHttpChannel::ConnectOnTailUnblock", NETWORK, Flow::FromPointer(this));
|
||
// ...
|
||
}
|
||
|
||
nsHttpChannel::~nsHttpChannel() {
|
||
// Terminate the flow -- this is a marker without a duration
|
||
PROFILER_MARKER("~nsHttpChannel", NETWORK, {}, TerminatingFlowMarker, Flow::FromPointer(this));
|
||
// ...
|
||
}
|
||
```
|
||
|
||
If there’s not an obvious pointer that matches the lifetime of the flow, there are alternatives:
|
||
|
||
- {searchfox}`Flow::ProcessScoped(uint64_t aFlowId) <mozilla-central/rev/86878e73a24fe32ea09dbae5b55362efaf7485c8:mozglue/baseprofiler/public/Flow.h#43>` -- the id should be unique in the process
|
||
- {searchfox}`Flow::Global(uint64_t aFlowId) <mozilla-central/rev/86878e73a24fe32ea09dbae5b55362efaf7485c8:mozglue/baseprofiler/public/Flow.h#58>` -- the id should be unique across all processes
|
||
|
||
`Runnable`, IPC, and `Task` have already been annotated with flow markers.
|
||
This allow linking flows together, even across process boundaries.
|
||
|
||
Those flow markers can be visualized in the profiler UI, but support hasn't been
|
||
merged yet. In the meantime, loading the Firefox profile in
|
||
<https://deploy-preview-5190--perf-html.netlify.app/> will allow you to see the
|
||
flow markers in a panel at the bottom. The frontend work is ongoing in
|
||
<https://github.com/firefox-devtools/profiler/pull/5190>.
|
||
|
||
### Text Markers
|
||
|
||
Text markers are very common, they carry an extra text as a fourth argument, in addition to
|
||
the marker name. Use the following macro:
|
||
|
||
```cpp
|
||
PROFILER_MARKER_TEXT(
|
||
// Name, category pair, options.
|
||
"Marker Name", OTHER, {},
|
||
// Text string.
|
||
"Here are some more details."
|
||
);
|
||
```
|
||
|
||
As useful as it is, using an expensive `printf` operation to generate a complex text
|
||
comes with a variety of issues string. It can leak potentially sensitive information
|
||
such as URLs can be leaked during the profile sharing step. profiler.firefox.com cannot
|
||
access the information programmatically. It won't get the formatting benefits of the
|
||
built-in marker schema. Please consider using a custom marker type to separate and
|
||
better present the data.
|
||
|
||
### \{fmt} Markers
|
||
|
||
`{fmt}` markers are similar to the text markers, but the string is formatted
|
||
using the [\{fmt}](/xpcom/fmt-in-gecko.html) library.
|
||
|
||
```cpp
|
||
PROFILER_MARKER_FMT("Marker Name", MEDIA_PLAYBACK, {},
|
||
"Three numbers: {} {} {}", 1, 2, 3);
|
||
```
|
||
|
||
The same caveat as the Text Marker (described in the previous paragraph) apply
|
||
here. The string formatting isn't performed if the marker wouldn't otherwise
|
||
be recorded, the most typical instance being that the profiler isn't running.
|
||
|
||
### Simple Payload Markers
|
||
|
||
For simple markers that need to carry structured data without defining a full
|
||
custom marker type, use the `PROFILER_MARKER_SIMPLE_PAYLOAD` macro. This macro
|
||
automatically generates a marker schema based on the variable names and types
|
||
you provide:
|
||
|
||
```cpp
|
||
PROFILER_MARKER_SIMPLE_PAYLOAD(
|
||
"My Marker", DOM,
|
||
MarkerOptions{}, // Optional marker options
|
||
mOpaque, mCount // Variable names that become payload fields
|
||
);
|
||
```
|
||
|
||
The macro performs compile-time type inference to determine appropriate input
|
||
types and formats for the marker schema. It supports various data types
|
||
including integers, timestamps, and strings. The variable names become
|
||
the field names in the marker payload.
|
||
|
||
Arguments must be simple tokens (i.e. `(start - end)` will not work as an
|
||
argument).
|
||
|
||
Type inference is handled automatically through template functions
|
||
`getDefaultInputTypeForType()` and `getDefaultFormatForType()` that provide
|
||
compile-time type checking and conversion. This eliminates the need to manually
|
||
specify field types and formats for common data types. Those functions can be
|
||
extended as needed.
|
||
|
||
The macro also supports custom table labels for markers and automatically
|
||
handles JSON streaming of marker data, providing a convenient alternative to
|
||
defining full custom marker types for simple use cases:
|
||
|
||
```cpp
|
||
PROFILER_MARKER_SIMPLE_PAYLOAD("My Marker", DOM,
|
||
"This is element number {marker.data.mCount}."
|
||
" Opaque: {marker.data.mOpaque}",
|
||
mOpaque, mCount
|
||
);
|
||
```
|
||
|
||
### Other Typed Markers
|
||
|
||
From C++ code, a marker of some type `YourMarker` (details about type definition follow) can be
|
||
recorded like this:
|
||
|
||
```cpp
|
||
PROFILER_MARKER(
|
||
"YourMarker name", OTHER,
|
||
MarkerOptions(MarkerTiming::IntervalUntilNowFrom(someStartTimestamp),
|
||
MarkerInnerWindowId(innerWindowId))),
|
||
YourMarker, "some string", 12345, "http://example.com", someTimeStamp);
|
||
```
|
||
|
||
After the first three common arguments (like in `PROFILER_MARKER_UNTYPED`), there are:
|
||
|
||
4. The marker type, which is the name of the C++ `struct` that defines that type.
|
||
5. A variadic list of type-specific argument. They must match the number of, and must
|
||
be convertible to the types defined in the schema. If they are not, they must match
|
||
the number of and be convertible to the types in `StreamJSONMarkerData` and
|
||
`TranslateMarkerInputToSchema`.
|
||
|
||
## Where to Define New Marker Types
|
||
|
||
The first step is to determine the location of the marker type definition:
|
||
|
||
- If this type is only used in one function, or a component, it can be defined in a
|
||
local common place relative to its use.
|
||
|
||
- For a more common type that could be used from multiple locations:
|
||
|
||
- If there is no dependency on XUL, it can be defined in the Base Profiler, which can
|
||
be used in most locations in the codebase:
|
||
{searchfox}`mozglue/baseprofiler/public/BaseProfilerMarkerTypes.h`
|
||
- However, if there is a XUL dependency, then it needs to be defined in the Gecko Profiler:
|
||
{searchfox}`tools/profiler/public/ProfilerMarkerTypes.h`
|
||
|
||
(how-to-define-new-marker-types)=
|
||
|
||
## How to Define New Marker Types
|
||
|
||
Each marker type must be defined once and only once.
|
||
The definition is a C++ `struct`, that inherits from `BaseMarkerType`, its identifier is used when recording
|
||
markers of that type in C++.
|
||
By convention, the suffix "Marker" is recommended to better distinguish them
|
||
from non-profiler entities in the source.
|
||
|
||
```cpp
|
||
struct YourMarker : BaseMarkerType<YourMarker> {
|
||
```
|
||
|
||
### Marker Type Name & Description
|
||
|
||
A marker type must have a unique name, it is used to keep track of the type of
|
||
markers in the profiler storage, and to identify them uniquely on profiler.firefox.com.
|
||
(It does not need to be the same as the `struct`'s name.)
|
||
|
||
This type name is defined in a special static data member `Name`:
|
||
|
||
```cpp
|
||
// …
|
||
static constexpr const char* Name = "YourMarker";
|
||
```
|
||
|
||
In addition you must add a description of your marker in a special static data member `Description`:
|
||
|
||
```cpp
|
||
// …
|
||
static constexpr const char* Description = "This is my marker!";
|
||
```
|
||
|
||
If you expect users to be passing unique names for individual instances of the marker,
|
||
you may want to add the following to ensure those names get stored when using ETW:
|
||
|
||
```cpp
|
||
// …
|
||
static constexpr bool StoreName = true;
|
||
```
|
||
|
||
### Marker Type Data
|
||
|
||
All markers of any type have some common data: A name, a category, options like
|
||
timing, etc. as previously explained.
|
||
|
||
In addition, a certain marker type may carry zero of more arbitrary pieces of
|
||
information, and they are always the same for all markers of that type.
|
||
|
||
These are defined in a special static member data array of `PayloadField` s.
|
||
Each payload field specifies a key, a C++ type description, a label, a format,
|
||
and optionally some additional options (see the `PayloadField` type). The
|
||
most important fields are:
|
||
|
||
- Key: Element property name as streamed in `StreamJSONMarkerData`.
|
||
- Type: An enum value describing the C++ type specified to PROFILER_MARKER/profiler_add_marker.
|
||
- Label: Prefix to display to label the field.
|
||
- Format: How to format the data element value, see [MarkerSchema::Format for details](https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerSchema%3A%3AFormat).
|
||
|
||
```cpp
|
||
// …
|
||
// This will be used repeatedly and is done for convenience.
|
||
using MS = MarkerSchema;
|
||
static constexpr MS::PayloadField PayloadFields[] = {
|
||
{"number", MS::InputType::Uint32t, "Number", MS::Format::Integer}};
|
||
```
|
||
|
||
In addition, a `StreamJSONMarkerData` function must be defined that matches
|
||
the C++ argument types to PROFILER_MARKER.
|
||
|
||
The first function parameters is always `SpliceableJSONWriter& aWriter`,
|
||
it will be used to stream the data as JSON, to later be read by
|
||
profiler.firefox.com.
|
||
|
||
```cpp
|
||
// …
|
||
static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter,
|
||
```
|
||
|
||
The following function parameters is how the data is received as C++ objects
|
||
from the call sites.
|
||
|
||
- Most C/C++ [POD (Plain Old Data)](https://en.cppreference.com/w/cpp/named_req/PODType)
|
||
and [trivially-copyable](https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable)
|
||
types should work as-is, including `TimeStamp`.
|
||
- Character strings should be passed using `const ProfilerString8View&` (this handles
|
||
literal strings, and various `std::string` and `nsCString` types, and spans with or
|
||
without null terminator). Use `const ProfilerString16View&` for 16-bit strings such as
|
||
`nsString`.
|
||
- Other types can be used if they define specializations for `ProfileBufferEntryWriter::Serializer`
|
||
and `ProfileBufferEntryReader::Deserializer`. You should rarely need to define new
|
||
ones, but if needed see how existing specializations are written, or contact the
|
||
[perf-tools team for help](https://chat.mozilla.org/#/room/#profiler:mozilla.org).
|
||
|
||
Passing by value or by reference-to-const is recommended, because arguments are serialized
|
||
in binary form (i.e., there are no optimizable `move` operations).
|
||
|
||
For example, here's how to handle a string, a 64-bit number, another string, and
|
||
a timestamp:
|
||
|
||
```cpp
|
||
// …
|
||
const ProfilerString8View& aString,
|
||
const int64_t aBytes,
|
||
const ProfilerString8View& aURL,
|
||
const TimeStamp& aTime) {
|
||
```
|
||
|
||
Then the body of the function turns these parameters into a JSON stream.
|
||
|
||
If these parameter types match the types specified in the schema, both in order
|
||
and number. It can simply call the default implementation.
|
||
|
||
```cpp
|
||
// …
|
||
static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter,
|
||
const ProfilerString8View& aString,
|
||
const int64_t aBytes,
|
||
const ProfilerString8View& aURL,
|
||
const TimeStamp& aTime) {
|
||
StreamJSONMarkerDataImpl(aWrite, aString, aBytes, aURL, aTime);
|
||
}
|
||
```
|
||
|
||
If the parameters passed to PROFILER_MARKER do not match the schema, some
|
||
additional work is required.
|
||
|
||
When this function is called, the writer has just started a JSON object, so
|
||
everything that is written should be a named object property. Use
|
||
`SpliceableJSONWriter` functions, in most cases `...Property` functions
|
||
from its parent class `JSONWriter`: `NullProperty`, `BoolProperty`,
|
||
`IntProperty`, `DoubleProperty`, `StringProperty`. (Other nested JSON
|
||
types like arrays or objects are not supported by the profiler.)
|
||
|
||
As a special case, `TimeStamps` must be streamed using `aWriter.TimeProperty(timestamp)`.
|
||
|
||
The property names will be used to identify where each piece of data is stored and
|
||
how it should be displayed on profiler.firefox.com (see next section).
|
||
|
||
Suppose our marker schema defines a string for a boolean, here is how that could be streamed.
|
||
|
||
```cpp
|
||
// …
|
||
|
||
static void StreamJSONMarkerData(SpliceableJSONWriter& aWriter,
|
||
bool aBoolean) {
|
||
aWriter.StringProperty("myBoolean", aBoolean ? "true" : "false");
|
||
}
|
||
```
|
||
|
||
In addition, a `TranslateMarkerInputToSchema` function must be added to
|
||
ensure correct output to ETW.
|
||
|
||
```c++
|
||
// The translation to the schema must also be defined in a translator function.
|
||
// The argument list should match that to PROFILER_MARKER/profiler_add_marker.
|
||
static void TranslateMarkerInputToSchema(void* aContext, bool aBoolean) {
|
||
// This should call ETW::OutputMarkerSchema with an argument list matching the schema.
|
||
if (aIsStart) {
|
||
ETW::OutputMarkerSchema(aContext, YourMarker{}, ProfilerStringView("true"));
|
||
} else {
|
||
ETW::OutputMarkerSchema(aContext, YourMarker{}, ProfilerStringView("false"));
|
||
}
|
||
}
|
||
```
|
||
|
||
(marker-type-display-schema)=
|
||
|
||
### Marker Type Display Schema
|
||
|
||
Now that we have defined how to stream type-specific data (from Firefox to
|
||
profiler.firefox.com), we need to describe where and how this data will be
|
||
displayed on profiler.firefox.com.
|
||
|
||
The location data member determines where this marker will be displayed in
|
||
the profiler.firefox.com UI. See the [MarkerSchema::Location enumeration for the
|
||
full list](https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerSchema%3A%3ALocation).
|
||
|
||
Here is the most common set of locations, showing markers of that type in both the
|
||
Marker Chart and the Marker Table panels:
|
||
|
||
```cpp
|
||
// …
|
||
static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
|
||
MS::Location::MarkerTable};
|
||
```
|
||
|
||
Some labels can optionally be specified, to display certain information in different
|
||
locations: `ChartLabel`, `TooltipLabel`, and `TableLabel`; or `AllLabels` to
|
||
define all of them the same way.
|
||
|
||
The arguments is a string that may refer to marker data within braces:
|
||
|
||
- `{marker.name}`: Marker name.
|
||
- `{marker.data.X}`: Type-specific data, as streamed with property name "X" from `StreamJSONMarkerData` (e.g., `aWriter.IntProperty("X", a number);`
|
||
- `{marker.data.X ? 'yes' : 'no'}`: Ternary expression — renders the first string when the field is truthy,
|
||
or the second string when it is falsy (null, undefined, 0, false, or empty string).
|
||
Only `marker.data.*` fields are supported as the condition, and both branches must be single-quoted string literals.
|
||
|
||
For example, here's how to set the Marker Chart label to show the marker name and the
|
||
`myBytes` number of bytes:
|
||
|
||
```cpp
|
||
// …
|
||
static constexpr const char* ChartLabel = "{marker.name} – {marker.data.myBytes}";
|
||
```
|
||
|
||
Ternary expressions can be combined with regular field lookups. For instance, a label could conditionally show an icon when a request was canceled:
|
||
|
||
```cpp
|
||
// …
|
||
static constexpr const char* TableLabel = "{marker.data.canceled ? '❌' : ''} {marker.data.delay}";
|
||
```
|
||
|
||
profiler.firefox.com will apply the label with the data in a consistent manner. For
|
||
example, with this label definition, it could display marker information like the
|
||
following in the Firefox Profiler's Marker Chart:
|
||
|
||
- "Marker Name – 10B"
|
||
- "Marker Name – 25.204KB"
|
||
- "Marker Name – 512.54MB"
|
||
|
||
For implementation details on this processing, see [src/profiler-logic/marker-schema.js](https://github.com/firefox-devtools/profiler/blob/main/src/profile-logic/marker-schema.ts)
|
||
in the profiler's front-end.
|
||
|
||
Any other `struct` member function is ignored. There could be utility functions used by the above
|
||
compulsory functions, to make the code clearer.
|
||
|
||
And that is the end of the marker definition `struct`.
|
||
|
||
```cpp
|
||
// …
|
||
};
|
||
```
|
||
|
||
## Performance Considerations
|
||
|
||
During profiling, it is best to reduce the amount of work spent doing profiler
|
||
operations, as they can influence the performance of the code that you want to profile.
|
||
|
||
Whenever possible, consider passing simple types to marker functions, such that
|
||
`StreamJSONMarkerData` will do the minimum amount of work necessary to serialize
|
||
the marker type-specific arguments to its internal buffer representation. POD types
|
||
(numbers) and strings are the easiest and cheapest to serialize. Look at the
|
||
corresponding `ProfileBufferEntryWriter::Serializer` specializations if you
|
||
want to better understand the work done.
|
||
|
||
Avoid doing expensive operations when recording markers. E.g.: `printf` of
|
||
different things into a string, or complex computations; instead pass the
|
||
`printf`/computation arguments straight through to the marker function, so that
|
||
`StreamJSONMarkerData` can do the expensive work at the end of the profiling session.
|
||
|
||
## Marker Architecture Description
|
||
|
||
The above sections should give all the information needed for adding your own marker
|
||
types. However, if you are wanting to work on the marker architecture itself, this
|
||
section will describe how the system works.
|
||
|
||
TODO:
|
||
: - Briefly describe the buffer and serialization.
|
||
|
||
- Describe the template strategy for generating marker types
|
||
- Describe the serialization and link to profiler front-end docs on marker processing (if they exist)
|