summaryrefslogtreecommitdiffstats
path: root/tools/profiler/docs/instrumenting-rust.rst
blob: 0c5021eec135c3f5818ee6d11dad2ec9873e9f05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
Instrumenting Rust
==================

There are multiple ways to use the profiler with Rust. Native stack sampling already
includes the Rust frames without special handling. There is the "Native Stacks"
profiler feature (via about:profiling), which enables stack walking for native code.
This is most likely turned on already for every profiler presets.

In addition to that, there is a profiler Rust API to instrument the Rust code
and add more information to the profile data. There are three main functionalities
to use:

1. Register Rust threads with the profiler, so the profiler can record these threads.
2. Add stack frame labels to annotate and categorize a part of the stack.
3. Add markers to specifically mark instants in time, or durations. This can be
   helpful to make sense of a particular piece of the code, or record events that
   normally wouldn't show up in samples.

Crate to Include as a Dependency
--------------------------------

Profiler Rust API is located inside the ``gecko-profiler`` crate. This needs to
be included in the project dependencies before the following functionalities can
be used.

To be able to include it, a new dependency entry needs to be added to the project's
``Cargo.toml`` file like this:

.. code-block:: toml

    [dependencies]
    gecko-profiler = { path = "../../tools/profiler/rust-api" }

Note that the relative path needs to be updated depending on the project's location
in mozilla-central.

Registering Threads
-------------------

To be able to see the threads in the profile data, they need to be registered
with the profiler. Also, they need to be unregistered when they are exiting.
It's important to give a unique name to the thread, so they can be filtered easily.

Registering and unregistering a thread is straightforward:

.. code-block:: rust

    // Register it with a given name.
    gecko_profiler::register_thread("Thread Name");
    // After doing some work, and right before exiting the thread, unregister it.
    gecko_profiler::unregister_thread();

For example, here's how to register and unregister a simple thread:

.. code-block:: rust

    let thread_name = "New Thread";
    std::thread::Builder::new()
        .name(thread_name.into())
        .spawn(move || {
            gecko_profiler::register_thread(thread_name);
            // DO SOME WORK
            gecko_profiler::unregister_thread();
        })
        .unwrap();

Or with a thread pool:

.. code-block:: rust

    let worker = rayon::ThreadPoolBuilder::new()
        .thread_name(move |idx| format!("Worker#{}", idx))
        .start_handler(move |idx| {
            gecko_profiler::register_thread(&format!("Worker#{}", idx));
        })
        .exit_handler(|_idx| {
            gecko_profiler::unregister_thread();
        })
        .build();

.. note::
    Registering a thread only will not make it appear in the profile data. In
    addition, it needs to be added to the "Threads" filter in about:profiling.
    This filter input is a comma-separated list. It matches partial names and
    supports the wildcard ``*``.

Adding Stack Frame Labels
-------------------------

Stack frame labels are useful for annotating a part of the call stack with a
category. The category will appear in the various places on the Firefox Profiler
analysis page like timeline, call tree tab, flame graph tab, etc.

``gecko_profiler_label!`` macro is used to add a new label frame. The added label
frame will exist between the call of this macro and the end of the current scope.

Adding a stack frame label:

.. code-block:: rust

    // Marking the stack as "Layout" category, no subcategory provided.
    gecko_profiler_label!(Layout);
    // Marking the stack as "JavaScript" category and "Parsing" subcategory.
    gecko_profiler_label!(JavaScript, Parsing);

    // Or the entire function scope can be marked with a procedural macro. This is
    // essentially a syntactical sugar and it expands into a function with a
    // gecko_profiler_label! call at the very start:
    #[gecko_profiler_fn_label(DOM)]
    fn foo(bar: u32) -> u32 {
        bar
    }

See the list of all profiling categories in the `profiling_categories.yaml`_ file.

Adding 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 Rust markers in depth. To learn more about how to add a
    marker in C++ or JavaScript, please take a look at their documentation
    in :doc:`markers-guide` or :doc:`instrumenting-javascript` respectively.

Examples
^^^^^^^^

Short examples, details are below.

.. code-block:: rust

    // Record a simple marker with the category of Graphics, DisplayListBuilding.
    gecko_profiler::add_untyped_marker(
        // Name of the marker as a string.
        "Marker Name",
        // Category with an optional sub-category.
        gecko_profiler_category!(Graphics, DisplayListBuilding),
        // MarkerOptions that keeps options like marker timing and marker stack.
        // It will be a point in type by default.
        Default::default(),
    );

.. code-block:: rust

    // Create a marker with some additional text information.
    let info = "info about this marker";
    gecko_profiler::add_text_marker(
        // Name of the marker as a string.
        "Marker Name",
        // Category with an optional sub-category.
        gecko_profiler_category!(DOM),
        // MarkerOptions that keeps options like marker timing and marker stack.
        MarkerOptions {
            timing: MarkerTiming::instant_now(),
            ..Default::default()
        },
        // Additional information as a string.
        info,
    );

.. code-block:: rust

    // Record a custom marker of type `ExampleNumberMarker` (see definition below).
    gecko_profiler::add_marker(
        // Name of the marker as a string.
        "Marker Name",
        // Category with an optional sub-category.
        gecko_profiler_category!(Graphics, DisplayListBuilding),
        // MarkerOptions that keeps options like marker timing and marker stack.
        Default::default(),
        // Marker payload.
        ExampleNumberMarker { number: 5 },
    );

    ....

    // Marker type definition. It needs to derive Serialize, Deserialize.
    #[derive(Serialize, Deserialize, Debug)]
    pub struct ExampleNumberMarker {
        number: i32,
    }

    // Marker payload needs to implement the ProfilerMarker trait.
    impl gecko_profiler::ProfilerMarker for ExampleNumberMarker {
        // Unique marker type name.
        fn marker_type_name() -> &'static str {
            "example number"
        }
        // Data specific to this marker type, serialized to JSON for profiler.firefox.com.
        fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) {
            json_writer.int_property("number", self.number.into());
        }
        // Where and how to display the marker and its data.
        fn marker_type_display() -> gecko_profiler::MarkerSchema {
            use gecko_profiler::marker::schema::*;
            let mut schema = MarkerSchema::new(&[Location::MarkerChart]);
            schema.set_chart_label("Name: {marker.name}");
            schema.add_key_label_format("number", "Number", Format::Integer);
            schema
        }
    }

Untyped Markers
^^^^^^^^^^^^^^^

Untyped markers don't carry any information apart from common marker data:
Name, category, options.

.. code-block:: rust

    gecko_profiler::add_untyped_marker(
        // Name of the marker as a string.
        "Marker Name",
        // Category with an optional sub-category.
        gecko_profiler_category!(Graphics, DisplayListBuilding),
        // MarkerOptions that keeps options like marker timing and marker stack.
        MarkerOptions {
            timing: MarkerTiming::instant_now(),
            ..Default::default()
        },
    );

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 string, or any dynamic string.
2. `Profiling category pair`_
    A category + subcategory pair from the `the list of categories`_.
    ``gecko_profiler_category!`` macro should be used to create a profiling category
    pair since it's easier to use, e.g. ``gecko_profiler_category!(JavaScript, Parsing)``.
    Second parameter can be omitted to use the default subcategory directly.
    ``gecko_profiler_category!`` macro is encouraged to use, but ``ProfilingCategoryPair``
    enum can also be used if needed.
3. `MarkerOptions`_
    See the options below. It can be omitted if there are no arguments with ``Default::default()``.
    Some options can also be omitted, ``MarkerOptions {<options>, ..Default::default()}``,
    with one or more of the following options types:

    * `MarkerTiming`_
        This specifies an instant or interval of time. It defaults to the current instant if
        left unspecified. Otherwise use ``MarkerTiming::instant_at(ProfilerTime)`` or
        ``MarkerTiming::interval(pt1, pt2)``; timestamps are usually captured with
        ``ProfilerTime::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.
    * `MarkerStack`_
        By default, markers do not record a "stack" (or "backtrace"). To record a stack at
        this point, in the most efficient manner, specify ``MarkerStack::Full``. To
        capture a stack without native frames for reduced overhead, specify
        ``MarkerStack::NonNative``.

    *Note: Currently, all C++ marker options are not present in the Rust side. They will
    be added in the future.*

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:

.. code-block:: rust

    let info = "info about this marker";
    gecko_profiler::add_text_marker(
        // Name of the marker as a string.
        "Marker Name",
        // Category with an optional sub-category.
        gecko_profiler_category!(DOM),
        // MarkerOptions that keeps options like marker timing and marker stack.
        MarkerOptions {
            stack: MarkerStack::Full,
            ..Default::default()
        },
        // Additional information as a string.
        info,
    );

As useful as it is, using an expensive ``format!`` operation to generate a complex text
comes with a variety of issues. It can leak potentially sensitive information
such as URLs 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.

Other Typed Markers
^^^^^^^^^^^^^^^^^^^

From Rust code, a marker of some type ``YourMarker`` (details about type definition follow) can be
recorded like this:

.. code-block:: rust

    gecko_profiler::add_marker(
        // Name of the marker as a string.
        "Marker Name",
        // Category with an optional sub-category.
        gecko_profiler_category!(JavaScript),
        // MarkerOptions that keeps options like marker timing and marker stack.
        Default::default(),
        // Marker payload.
        YourMarker { number: 5, text: "some string".to_string() },
    );

After the first three common arguments (like in ``gecko_profiler::add_untyped_marker``),
there is a marker payload struct and it needs to be defined. Let's take a look at
how to define it.

How to Define New Marker Types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Each marker type must be defined once and only once.
The definition is a Rust ``struct``, it's constructed when recording markers of
that type in Rust. Each marker struct holds the data that is required for them
to show in the profiler.firefox.com.
By convention, the suffix "Marker" is recommended to better distinguish them
from non-profiler entities in the source.

Each marker payload must derive ``serde::Serialize`` and ``serde::Deserialize``.
They are also exported from ``gecko-profiler`` crate if a project doesn't have it.
Each marker payload should include its data as its fields like this:

.. code-block:: rust

    #[derive(Serialize, Deserialize, Debug)]
    pub struct YourMarker {
        number: i32,
        text: String,
    }

Each marker struct must also implement the `ProfilerMarker`_ trait.

``ProfilerMarker`` trait
************************

`ProfilerMarker`_ trait must be implemented for all marker types. Its methods are
similar to C++ counterparts, please refer to :ref:`the C++ markers guide to learn
more about them <how-to-define-new-marker-types>`. It includes three methods that
needs to be implemented:

1. ``marker_type_name() -> &'static str``:
    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.)

    E.g.:

    .. code-block:: rust

        fn marker_type_name() -> &'static str {
            "your marker type"
        }

2. ``stream_json_marker_data(&self, json_writer: &mut JSONWriter)``
    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 function ``stream_json_marker_data``.

    It's a member method and takes a ``&mut JSONWriter`` as a parameter,
    it will be used to stream the data as JSON, to later be read by
    profiler.firefox.com. See `JSONWriter object and its methods`_.

    E.g.:

    .. code-block:: rust

        fn stream_json_marker_data(&self, json_writer: &mut JSONWriter) {
            json_writer.int_property("number", self.number.into());
            json_writer.string_property("text", &self.text);
        }

3. ``marker_type_display() -> schema::MarkerSchema``
    Now that how to stream type-specific data (from Firefox to
    profiler.firefox.com) is defined, it needs to be described where and how this
    data will be displayed on profiler.firefox.com.

    The static member function ``marker_type_display`` returns an opaque ``MarkerSchema``
    object, which will be forwarded to profiler.firefox.com.

    See the `MarkerSchema::Location enumeration for the full list`_. Also see the
    `MarkerSchema struct for its possible methods`_.

    E.g.:

    .. code-block:: rust

        fn marker_type_display() -> schema::MarkerSchema {
            // Import MarkerSchema related types for easier use.
            use crate::marker::schema::*;
            // Create a MarkerSchema struct with a list of locations provided.
            // One or more constructor arguments determine where this marker will be displayed in
            // the profiler.firefox.com UI.
            let mut schema = MarkerSchema::new(&[Location::MarkerChart]);

            // Some labels can optionally be specified, to display certain information in different
            // locations: set_chart_label, set_tooltip_label, and set_table_label``; or
            // set_all_labels to define all of them the same way.
            schema.set_all_labels("{marker.name} - {marker.data.number});

            // Next, define the main display of marker data, which will appear in the Marker Chart
            // tooltips and the Marker Table sidebar.
            schema.add_key_label_format("number", "Number", Format::Number);
            schema.add_key_label_format("text", "Text", Format::String);
            schema.add_static_label_value("Help", "This is my own marker type");

            // Lastly, return the created schema.
            schema
        }

    Note that the strings in ``set_all_labels`` may refer to marker data within braces:

    * ``{marker.name}``: Marker name.
    * ``{marker.data.X}``: Type-specific data, as streamed with property name "X"
      from ``stream_json_marker_data``.

    :ref:`See the C++ markers guide for more details about it <marker-type-display-schema>`.

.. _profiling_categories.yaml: https://searchfox.org/mozilla-central/source/mozglue/baseprofiler/build/profiling_categories.yaml
.. _Profiling category pair: https://searchfox.org/mozilla-central/define?q=gecko_profiler::gecko_bindings::profiling_categories::ProfilingCategoryPair
.. _the list of categories: https://searchfox.org/mozilla-central/source/mozglue/baseprofiler/build/profiling_categories.yaml
.. _MarkerOptions: https://searchfox.org/mozilla-central/define?q=gecko_profiler::marker::options::MarkerOptions
.. _MarkerTiming: https://searchfox.org/mozilla-central/define?q=gecko_profiler::marker::options::MarkerTiming
.. _MarkerStack: https://searchfox.org/mozilla-central/define?q=gecko_profiler::marker::options::MarkerStack
.. _ProfilerMarker: https://searchfox.org/mozilla-central/define?q=gecko_profiler::marker::ProfilerMarker
.. _MarkerSchema::Location enumeration for the full list: https://searchfox.org/mozilla-central/define?q=T_mozilla%3A%3AMarkerSchema%3A%3ALocation
.. _JSONWriter object and its methods: https://searchfox.org/mozilla-central/define?q=gecko_profiler::json_writer::JSONWriter
.. _MarkerSchema struct for its possible methods: https://searchfox.org/mozilla-central/define?q=gecko_profiler::marker::schema::MarkerSchema