# 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.md) 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)
