summaryrefslogtreecommitdiffstats
path: root/tools/profiler/rust-api/src/marker/mod.rs
blob: 1981c0a322ab471316fe03883abf8fe2b4da4ea0 (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
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

//! ## Gecko profiler marker support
//!
//! This marker API has a few different functions that you can use to mark a part of your code.
//! There are three main marker functions to use from Rust: [`add_untyped_marker`],
//! [`add_text_marker`] and [`add_marker`]. They are similar to what we have on
//! the C++ side. Please take a look at the marker documentation in the Firefox
//! source docs to learn more about them:
//! https://firefox-source-docs.mozilla.org/tools/profiler/markers-guide.html
//!
//! ### Simple marker without any additional data
//!
//! The simplest way to add a marker without any additional information is the
//! [`add_untyped_marker`] API. You can use it to mark a part of the code with
//! only a name. E.g.:
//!
//! ```
//! 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.
//!     Default::default(),
//! );
//! ```
//!
//! Please see the [`gecko_profiler_category!`], [`MarkerOptions`],[`MarkerTiming`]
//! and [`MarkerStack`] to learn more about these.
//!
//! You can also give explicit [`MarkerOptions`] value like these:
//!
//! ```
//! // With both timing and stack fields:
//! MarkerOptions { timing: MarkerTiming::instant_now(), stack: MarkerStack::Full }
//! // Or with some fields as default:
//! MarkerOptions { timing: MarkerTiming::instant_now(), ..Default::default() }
//! ```
//!
//! ### Marker with only an additional text for more information:
//!
//! The next and slightly more advanced API is [`add_text_marker`].
//! This is used to add a marker name + a string value for extra information.
//! E.g.:
//!
//! ```
//! 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,
//! );
//! ```
//!
//! ### Marker with a more complex payload and different visualization in the profiler front-end.
//!
//! [`add_marker`] is the most advanced API that you can use to add different types
//! of values as data to your marker and customize the visualization of that marker
//! in the profiler front-end (profiler.firefox.com).
//!
//! To be able to add a a marker, first you need to create your marker payload
//! struct in your codebase and implement the [`ProfilerMarker`] trait like this:
//!
//! ```
//! #[derive(Serialize, Deserialize, Debug)]
//! pub struct TestMarker {
//!     a: u32,
//!     b: String,
//! }
//!
//! // Please see the documentation of [`ProfilerMarker`].
//! impl gecko_profiler::ProfilerMarker for TestMarker {
//!     fn marker_type_name() -> &'static str {
//!         "marker type from rust"
//!     }
//!     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.set_tooltip_label("{marker.data.a}");
//!         schema.add_key_label_format("a", "A Value", Format::Integer);
//!         schema.add_key_label_format("b", "B Value", Format::String);
//!         schema
//!     }
//!     fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) {
//!         json_writer.int_property("a", self.a.into());
//!         json_writer.string_property("b", &self.b);
//!     }
//! }
//! ```
//!
//! Once you've created this payload and implemented the [`ProfilerMarker`], you
//! can now add this marker in the code that you would like to measure. E.g.:
//!
//! ```
//! 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.
//!     TestMarker {a: 12, b: "hello".to_owned()},
//! );
//! ```

pub(crate) mod deserializer_tags_state;
pub mod options;
pub mod schema;

pub use options::*;
pub use schema::MarkerSchema;

use crate::gecko_bindings::{bindings, profiling_categories::ProfilingCategoryPair};
use crate::json_writer::JSONWriter;
use crate::marker::deserializer_tags_state::get_or_insert_deserializer_tag;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::os::raw::c_char;

/// Marker API to add a new simple marker without any payload.
/// Please see the module documentation on how to add a marker with this API.
pub fn add_untyped_marker(name: &str, category: ProfilingCategoryPair, mut options: MarkerOptions) {
    if !crate::profiler_state::can_accept_markers() {
        // Nothing to do.
        return;
    }

    unsafe {
        bindings::gecko_profiler_add_marker_untyped(
            name.as_ptr() as *const c_char,
            name.len(),
            category.to_cpp_enum_value(),
            options.timing.0.as_mut_ptr(),
            options.stack,
        )
    }
}

/// Marker API to add a new marker with additional text for details.
/// Please see the module documentation on how to add a marker with this API.
pub fn add_text_marker(
    name: &str,
    category: ProfilingCategoryPair,
    mut options: MarkerOptions,
    text: &str,
) {
    if !crate::profiler_state::can_accept_markers() {
        // Nothing to do.
        return;
    }

    unsafe {
        bindings::gecko_profiler_add_marker_text(
            name.as_ptr() as *const c_char,
            name.len(),
            category.to_cpp_enum_value(),
            options.timing.0.as_mut_ptr(),
            options.stack,
            text.as_ptr() as *const c_char,
            text.len(),
        )
    }
}

/// Trait that every profiler marker payload struct needs to implement.
/// This will tell the profiler back-end how to serialize it as json and
/// the front-end how to display the marker.
/// Please also see the documentation here:
/// https://firefox-source-docs.mozilla.org/tools/profiler/markers-guide.html#how-to-define-new-marker-types
///
/// - `marker_type_name`: Returns a static string as the marker type name. This
/// should be unique and it is used to keep track of the type of markers in the
/// profiler storage, and to identify them uniquely on the profiler front-end.
/// - `marker_type_display`: Where and how to display the marker and its data.
/// Returns a `MarkerSchema` object which will be forwarded to the profiler
/// front-end.
/// - `stream_json_marker_data`: Data specific to this marker type should be
/// serialized to JSON for the profiler front-end. All the common marker data
/// like marker name, category, timing will be serialized automatically. But
/// marker specific data should be serialized here.
pub trait ProfilerMarker: Serialize + DeserializeOwned {
    /// A static method that returns the name of the marker type.
    fn marker_type_name() -> &'static str;
    /// A static method that returns a `MarkerSchema`, which contains all the
    /// information needed to stream the display schema associated with a
    /// marker type.
    fn marker_type_display() -> MarkerSchema;
    /// A method that streams the marker payload data as JSON object properties.
    /// Please see the [JSONWriter] struct to see its methods.
    fn stream_json_marker_data(&self, json_writer: &mut JSONWriter);
}

/// A function that deserializes the marker payload and streams it to the JSON.
unsafe fn transmute_and_stream<T>(
    payload: *const u8,
    payload_size: usize,
    json_writer: &mut JSONWriter,
) where
    T: ProfilerMarker,
{
    let payload_slice = std::slice::from_raw_parts(payload, payload_size);
    let payload: T = bincode::deserialize(&payload_slice).unwrap();
    payload.stream_json_marker_data(json_writer);
}

/// Main marker API to add a new marker to profiler buffer.
/// Please see the module documentation on how to add a marker with this API.
pub fn add_marker<T>(
    name: &str,
    category: ProfilingCategoryPair,
    mut options: MarkerOptions,
    payload: T,
) where
    T: ProfilerMarker,
{
    if !crate::profiler_state::can_accept_markers() {
        // Nothing to do.
        return;
    }

    let encoded_payload: Vec<u8> = bincode::serialize(&payload).unwrap();
    let payload_size = encoded_payload.len();
    let maker_tag = get_or_insert_deserializer_tag::<T>();

    unsafe {
        bindings::gecko_profiler_add_marker(
            name.as_ptr() as *const c_char,
            name.len(),
            category.to_cpp_enum_value(),
            options.timing.0.as_mut_ptr(),
            options.stack,
            maker_tag,
            encoded_payload.as_ptr(),
            payload_size,
        )
    }
}

/// Tracing marker type for Rust code.
/// This must be kept in sync with the `mozilla::baseprofiler::markers::Tracing`
/// C++ counterpart.
#[derive(Serialize, Deserialize, Debug)]
pub struct Tracing(pub String);

impl ProfilerMarker for Tracing {
    fn marker_type_name() -> &'static str {
        "tracing"
    }

    fn stream_json_marker_data(&self, json_writer: &mut JSONWriter) {
        if self.0.len() != 0 {
            json_writer.string_property("category", &self.0);
        }
    }

    // Tracing marker is a bit special because we have the same schema in the
    // C++ side. This function will only get called when no Tracing markers are
    // generated from the C++ side. But, most of the time, this will not be called
    // when there is another C++ Tracing marker.
    fn marker_type_display() -> MarkerSchema {
        use crate::marker::schema::*;
        let mut schema = MarkerSchema::new(&[
            Location::MarkerChart,
            Location::MarkerTable,
            Location::TimelineOverview,
        ]);
        schema.add_key_label_format("category", "Type", Format::String);
        schema
    }
}