fxprof_processed_profile/
markers.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use bitflags::bitflags;
6use serde::ser::{Serialize, SerializeMap, SerializeSeq};
7use serde_derive::Serialize;
8
9use super::profile::StringHandle;
10use super::timestamp::Timestamp;
11use crate::{CategoryHandle, Profile};
12
13/// The handle for a marker. Returned from [`Profile::add_marker`].
14///
15/// This allows adding a stack to marker after the marker has been added.
16#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
17pub struct MarkerHandle(pub(crate) usize);
18
19/// The handle for a marker type. Returned from [`Profile::register_marker_type`].
20#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
21pub struct MarkerTypeHandle(pub(crate) usize);
22
23/// Specifies timestamps for a marker.
24#[derive(Debug, Clone)]
25pub enum MarkerTiming {
26    /// Instant markers describe a single point in time.
27    Instant(Timestamp),
28    /// Interval markers describe a time interval with a start and end timestamp.
29    Interval(Timestamp, Timestamp),
30    /// A marker for just the start of an actual marker. Can be paired with an
31    /// `IntervalEnd` marker of the same name; if no end marker is supplied, this
32    /// creates a marker that extends to the end of the profile.
33    ///
34    /// This can be used for long-running markers for pieces of activity that may
35    /// not have completed by the time the profile is captured.
36    IntervalStart(Timestamp),
37    /// A marker for just the end of an actual marker. Can be paired with an
38    /// `IntervalStart` marker of the same name; if no start marker is supplied,
39    /// this creates a marker which started before the beginning of the profile.
40    ///
41    /// This can be used to mark pieces of activity which started before profiling
42    /// began.
43    IntervalEnd(Timestamp),
44}
45
46/// The marker trait. You'll likely want to implement [`StaticSchemaMarker`] instead.
47///
48/// Markers have a type, a name, a category, and an arbitrary number of fields.
49/// The fields of a marker type are defined by the marker type's schema, see [`RuntimeSchemaMarkerSchema`].
50/// The timestamps are not part of the marker; they are supplied separately to
51/// [`Profile::add_marker`] when a marker is added to the profile.
52///
53/// You can implement this trait manually if the schema of your marker type is only
54/// known at runtime. If the schema is known at compile time, you'll want to implement
55/// [`StaticSchemaMarker`] instead - there is a blanket impl which implements [`Marker`]
56/// for any type that implements [`StaticSchemaMarker`].
57pub trait Marker {
58    /// The [`MarkerTypeHandle`] of this marker type. Created with [`Profile::register_marker_type`] or
59    /// with [`Profile::static_schema_marker_type`].
60    fn marker_type(&self, profile: &mut Profile) -> MarkerTypeHandle;
61
62    /// The name of this marker, as an interned string handle.
63    ///
64    /// The name is shown as the row label in the marker chart. It can also be
65    /// used as `{marker.name}` in the various `label` template strings in the schema.
66    fn name(&self, profile: &mut Profile) -> StringHandle;
67
68    /// The category of this marker. The marker chart groups marker rows by category.
69    fn category(&self, profile: &mut Profile) -> CategoryHandle;
70
71    /// Called for any fields defined in the schema whose [`format`](RuntimeSchemaMarkerField::format) is
72    /// of [kind](MarkerFieldFormat::kind) [`MarkerFieldFormatKind::String`].
73    ///
74    /// `field_index` is an index into the schema's [`fields`](RuntimeSchemaMarkerSchema::fields).
75    ///
76    /// You can panic for any unexpected field indexes, for example
77    /// using `unreachable!()`. You can even panic unconditionally if this
78    /// marker type doesn't have any string fields.
79    ///
80    /// If you do see unexpected calls to this method, make sure you're not registering
81    /// multiple different schemas with the same [`RuntimeSchemaMarkerSchema::type_name`].
82    fn string_field_value(&self, field_index: u32) -> StringHandle;
83
84    /// Called for any fields defined in the schema whose [`format`](RuntimeSchemaMarkerField::format) is
85    /// of [kind](MarkerFieldFormat::kind) [`MarkerFieldFormatKind::Number`].
86    ///
87    /// `field_index` is an index into the schema's [`fields`](RuntimeSchemaMarkerSchema::fields).
88    ///
89    /// You can panic for any unexpected field indexes, for example
90    /// using `unreachable!()`. You can even panic unconditionally if this
91    /// marker type doesn't have any number fields.
92    ///
93    /// If you do see unexpected calls to this method, make sure you're not registering
94    /// multiple different schemas with the same [`RuntimeSchemaMarkerSchema::type_name`].
95    fn number_field_value(&self, field_index: u32) -> f64;
96}
97
98/// The trait for markers whose schema is known at compile time. Any type which implements
99/// [`StaticSchemaMarker`] automatically implements the [`Marker`] trait via a blanket impl.
100///
101/// Markers have a type, a name, a category, and an arbitrary number of fields.
102/// The fields of a marker type are defined by the marker type's schema, see [`RuntimeSchemaMarkerSchema`].
103/// The timestamps are not part of the marker; they are supplied separately to
104/// [`Profile::add_marker`] when a marker is added to the profile.
105///
106/// In [`StaticSchemaMarker`], the schema is returned from a static `schema` method.
107///
108/// ```
109/// use fxprof_processed_profile::{
110///     Profile, Marker, MarkerLocations, MarkerFieldFlags, MarkerFieldFormat, StaticSchemaMarkerField,
111///     StaticSchemaMarker, CategoryHandle, StringHandle,
112/// };
113///
114/// /// An example marker type with a name and some text content.
115/// #[derive(Debug, Clone)]
116/// pub struct TextMarker {
117///   pub name: StringHandle,
118///   pub text: StringHandle,
119/// }
120///
121/// impl StaticSchemaMarker for TextMarker {
122///     const UNIQUE_MARKER_TYPE_NAME: &'static str = "Text";
123///
124///     const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART.union(MarkerLocations::MARKER_TABLE);
125///     const CHART_LABEL: Option<&'static str> = Some("{marker.data.text}");
126///     const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.text}");
127///
128///     const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField {
129///         key: "text",
130///         label: "Contents",
131///         format: MarkerFieldFormat::String,
132///         flags: MarkerFieldFlags::SEARCHABLE,
133///     }];
134///
135///     fn name(&self, _profile: &mut Profile) -> StringHandle {
136///         self.name
137///     }
138///
139///     fn category(&self, _profile: &mut Profile) -> CategoryHandle {
140///         CategoryHandle::OTHER
141///     }
142///
143///     fn string_field_value(&self, _field_index: u32) -> StringHandle {
144///         self.text
145///     }
146///
147///     fn number_field_value(&self, _field_index: u32) -> f64 {
148///         unreachable!()
149///     }
150/// }
151/// ```
152pub trait StaticSchemaMarker {
153    /// A unique string name for this marker type. Has to match the
154    /// [`RuntimeSchemaMarkerSchema::type_name`] of this type's schema.
155    const UNIQUE_MARKER_TYPE_NAME: &'static str;
156
157    /// An optional description string. Applies to all markers of this type.
158    const DESCRIPTION: Option<&'static str> = None;
159
160    /// Set of marker display locations.
161    const LOCATIONS: MarkerLocations =
162        MarkerLocations::MARKER_CHART.union(MarkerLocations::MARKER_TABLE);
163
164    /// A template string defining the label shown within each marker's box in the marker chart.
165    ///
166    /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`.
167    ///
168    /// If set to `None`, the boxes in the marker chart will be empty.
169    const CHART_LABEL: Option<&'static str> = None;
170
171    /// A template string defining the label shown in the first row of the marker's tooltip.
172    ///
173    /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`.
174    ///
175    /// Defaults to `{marker.name}` if set to `None`.
176    const TOOLTIP_LABEL: Option<&'static str> = None;
177
178    /// A template string defining the label shown within each marker's box in the marker chart.
179    ///
180    /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`.
181    ///
182    /// Defaults to `{marker.name}` if set to `None`.
183    const TABLE_LABEL: Option<&'static str> = None;
184
185    /// The marker fields. The values are supplied by each marker, in the marker's
186    /// implementations of the `string_field_value` and `number_field_value` trait methods.
187    const FIELDS: &'static [StaticSchemaMarkerField];
188
189    /// Any graph lines / segments created from markers of this type.
190    ///
191    /// If this is non-empty, the Firefox Profiler will create one graph track per
192    /// marker *name*, per thread, based on the markers it finds on that thread.
193    /// The marker name becomes the track's label.
194    ///
195    /// The elements in the graphs array describe individual graph lines or bar
196    /// chart segments which are all drawn inside the same track, stacked on top of
197    /// each other, in the order that they're listed here, with the first entry
198    /// becoming the bottom-most graph within the track.
199    const GRAPHS: &'static [StaticSchemaMarkerGraph] = &[];
200
201    /// The name of this marker, as an interned string handle.
202    ///
203    /// The name is shown as the row label in the marker chart. It can also be
204    /// used as `{marker.name}` in the various `label` template strings in the schema.
205    fn name(&self, profile: &mut Profile) -> StringHandle;
206
207    /// The category of this marker. The marker chart groups marker rows by category.
208    fn category(&self, profile: &mut Profile) -> CategoryHandle;
209
210    /// Called for any fields defined in the schema whose [`format`](RuntimeSchemaMarkerField::format) is
211    /// of [kind](MarkerFieldFormat::kind) [`MarkerFieldFormatKind::String`].
212    ///
213    /// `field_index` is an index into the schema's [`fields`](RuntimeSchemaMarkerSchema::fields).
214    ///
215    /// You can panic for any unexpected field indexes, for example
216    /// using `unreachable!()`. You can even panic unconditionally if this
217    /// marker type doesn't have any string fields.
218    ///
219    /// If you do see unexpected calls to this method, make sure you're not registering
220    /// multiple different schemas with the same [`RuntimeSchemaMarkerSchema::type_name`].
221    fn string_field_value(&self, field_index: u32) -> StringHandle;
222
223    /// Called for any fields defined in the schema whose [`format`](RuntimeSchemaMarkerField::format) is
224    /// of [kind](MarkerFieldFormat::kind) [`MarkerFieldFormatKind::Number`].
225    ///
226    /// `field_index` is an index into the schema's [`fields`](RuntimeSchemaMarkerSchema::fields).
227    ///
228    /// You can panic for any unexpected field indexes, for example
229    /// using `unreachable!()`. You can even panic unconditionally if this
230    /// marker type doesn't have any number fields.
231    ///
232    /// If you do see unexpected calls to this method, make sure you're not registering
233    /// multiple different schemas with the same [`RuntimeSchemaMarkerSchema::type_name`].
234    fn number_field_value(&self, field_index: u32) -> f64;
235}
236
237impl<T: StaticSchemaMarker> Marker for T {
238    fn marker_type(&self, profile: &mut Profile) -> MarkerTypeHandle {
239        profile.static_schema_marker_type::<Self>()
240    }
241
242    fn name(&self, profile: &mut Profile) -> StringHandle {
243        <T as StaticSchemaMarker>::name(self, profile)
244    }
245
246    fn category(&self, profile: &mut Profile) -> CategoryHandle {
247        <T as StaticSchemaMarker>::category(self, profile)
248    }
249
250    fn string_field_value(&self, field_index: u32) -> StringHandle {
251        <T as StaticSchemaMarker>::string_field_value(self, field_index)
252    }
253
254    fn number_field_value(&self, field_index: u32) -> f64 {
255        <T as StaticSchemaMarker>::number_field_value(self, field_index)
256    }
257}
258
259/// Describes a marker type, including the names and types of the marker's fields.
260/// You only need this if you don't know the schema until runtime. Otherwise, use
261/// [`StaticSchemaMarker`] instead.
262///
263/// Example:
264///
265/// ```
266/// use fxprof_processed_profile::{
267///     Profile, Marker, MarkerLocations, MarkerFieldFlags, MarkerFieldFormat, RuntimeSchemaMarkerSchema, RuntimeSchemaMarkerField,
268///     CategoryHandle, StringHandle,
269/// };
270///
271/// # fn fun() {
272/// let schema = RuntimeSchemaMarkerSchema {
273///     type_name: "custom".into(),
274///     locations: MarkerLocations::MARKER_CHART | MarkerLocations::MARKER_TABLE,
275///     chart_label: Some("{marker.data.eventName}".into()),
276///     tooltip_label: Some("Custom {marker.name} marker".into()),
277///     table_label: Some("{marker.name} - {marker.data.eventName} with allocation size {marker.data.allocationSize} (latency: {marker.data.latency})".into()),
278///     fields: vec![
279///         RuntimeSchemaMarkerField {
280///             key: "eventName".into(),
281///             label: "Event name".into(),
282///             format: MarkerFieldFormat::String,
283///             flags: MarkerFieldFlags::SEARCHABLE,
284///         },
285///         RuntimeSchemaMarkerField {
286///             key: "allocationSize".into(),
287///             label: "Allocation size".into(),
288///             format: MarkerFieldFormat::Bytes,
289///             flags: MarkerFieldFlags::SEARCHABLE,
290///         },
291///         RuntimeSchemaMarkerField {
292///             key: "url".into(),
293///             label: "URL".into(),
294///             format: MarkerFieldFormat::Url,
295///             flags: MarkerFieldFlags::SEARCHABLE,
296///         },
297///         RuntimeSchemaMarkerField {
298///             key: "latency".into(),
299///             label: "Latency".into(),
300///             format: MarkerFieldFormat::Duration,
301///             flags: MarkerFieldFlags::SEARCHABLE,
302///         },
303///     ],
304///     description: Some("This is a test marker with a custom schema.".into()),
305///     graphs: vec![],
306/// };
307/// # }
308/// ```
309#[derive(Debug, Clone)]
310pub struct RuntimeSchemaMarkerSchema {
311    /// The unique name of this marker type. There must not be any other schema
312    /// with the same name.
313    pub type_name: String,
314
315    /// An optional description string. Applies to all markers of this type.
316    pub description: Option<String>,
317
318    /// Set of marker display locations.
319    pub locations: MarkerLocations,
320
321    /// A template string defining the label shown within each marker's box in the marker chart.
322    ///
323    /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`.
324    ///
325    /// If set to `None`, the boxes in the marker chart will be empty.
326    pub chart_label: Option<String>,
327
328    /// A template string defining the label shown in the first row of the marker's tooltip.
329    ///
330    /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`.
331    ///
332    /// Defaults to `{marker.name}` if set to `None`.
333    pub tooltip_label: Option<String>,
334
335    /// A template string defining the label shown within each marker's box in the marker chart.
336    ///
337    /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`.
338    ///
339    /// Defaults to `{marker.name}` if set to `None`.
340    pub table_label: Option<String>,
341
342    /// The marker fields. The values are supplied by each marker, in the marker's
343    /// implementations of the `string_field_value` and `number_field_value` trait methods.
344    pub fields: Vec<RuntimeSchemaMarkerField>,
345
346    /// Any graph lines / segments created from markers of this type.
347    ///
348    /// If this is non-empty, the Firefox Profiler will create one graph track per
349    /// marker *name*, per thread, based on the markers it finds on that thread.
350    /// The marker name becomes the track's label.
351    ///
352    /// The elements in the graphs array describe individual graph lines or bar
353    /// chart segments which are all drawn inside the same track, stacked on top of
354    /// each other, in the order that they're listed here, with the first entry
355    /// becoming the bottom-most graph within the track.
356    pub graphs: Vec<RuntimeSchemaMarkerGraph>,
357}
358
359bitflags! {
360    /// Locations in the profiler UI where markers can be displayed.
361    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
362    pub struct MarkerLocations: u32 {
363        /// Show the marker in the "marker chart" panel.
364        const MARKER_CHART = 1 << 0;
365        /// Show the marker in the marker table.
366        const MARKER_TABLE = 1 << 1;
367        /// This adds markers to the main marker timeline in the header, but only
368        /// for main threads and for threads that were specifically asked to show
369        /// these markers using [`Profile::set_thread_show_markers_in_timeline`].
370        const TIMELINE_OVERVIEW = 1 << 2;
371        /// In the timeline, this is a section that breaks out markers that are
372        /// related to memory. When memory counters are used, this is its own
373        /// track, otherwise it is displayed with the main thread.
374        const TIMELINE_MEMORY = 1 << 3;
375        /// This adds markers to the IPC timeline area in the header.
376        const TIMELINE_IPC = 1 << 4;
377        /// This adds markers to the FileIO timeline area in the header.
378        const TIMELINE_FILEIO = 1 << 5;
379    }
380}
381
382/// The field definition of a marker field, used in [`StaticSchemaMarker::FIELDS`].
383///
384/// For each marker which uses this schema, the value for this field is supplied by the
385/// marker's implementation of [`number_field_value`](Marker::number_field_value) /
386/// [`string_field_value`](Marker::string_field_value), depending on this field
387/// format's [kind](MarkerFieldFormat::kind).
388///
389/// Used with runtime-generated marker schemas. Use [`RuntimeSchemaMarkerField`]
390/// when using [`RuntimeSchemaMarkerSchema`].
391pub struct StaticSchemaMarkerField {
392    /// The field key. Must not be `type` or `cause`.
393    pub key: &'static str,
394
395    /// The user-visible label of this field.
396    pub label: &'static str,
397
398    /// The format of this field.
399    pub format: MarkerFieldFormat,
400
401    /// Additional field flags.
402    pub flags: MarkerFieldFlags,
403}
404
405/// The field definition of a marker field, used in [`RuntimeSchemaMarkerSchema::fields`].
406///
407/// For each marker which uses this schema, the value for this field is supplied by the
408/// marker's implementation of [`number_field_value`](Marker::number_field_value) /
409/// [`string_field_value`](Marker::string_field_value), depending on this field
410/// format's [kind](MarkerFieldFormat::kind).
411///
412/// Used with runtime-generated marker schemas. Use [`StaticSchemaMarkerField`]
413/// when using [`StaticSchemaMarker`].
414#[derive(Debug, Clone)]
415pub struct RuntimeSchemaMarkerField {
416    /// The field key. Must not be `type` or `cause`.
417    pub key: String,
418
419    /// The user-visible label of this field.
420    pub label: String,
421
422    /// The format of this field.
423    pub format: MarkerFieldFormat,
424
425    /// Whether this field's value should be matched against search terms.
426    pub flags: MarkerFieldFlags,
427}
428
429impl From<&StaticSchemaMarkerField> for RuntimeSchemaMarkerField {
430    fn from(schema: &StaticSchemaMarkerField) -> Self {
431        Self {
432            key: schema.key.into(),
433            label: schema.label.into(),
434            format: schema.format.clone(),
435            flags: schema.flags,
436        }
437    }
438}
439
440/// The field format of a marker field.
441#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
442#[serde(rename_all = "kebab-case")]
443pub enum MarkerFieldFormat {
444    // ----------------------------------------------------
445    // String types.
446    /// A URL, supports PII sanitization
447    Url,
448
449    /// A file path, supports PII sanitization.
450    FilePath,
451
452    /// A regular string, supports PII sanitization.
453    /// Concretely this means that these strings are stripped when uploading
454    /// profiles if you uncheck "Include resource URLs and paths".
455    SanitizedString,
456
457    /// A plain String, never sanitized for PII.
458    ///
459    /// Important: Do not put URL or file path information here, as it will not
460    /// be sanitized during profile upload. Please be careful with including
461    /// other types of PII here as well.
462    #[serde(rename = "unique-string")]
463    String,
464
465    // ----------------------------------------------------
466    // Numeric types
467    /// For time data that represents a duration of time.
468    /// The value is given in float milliseconds and will be displayed
469    /// in a unit that is picked based on the magnitude of the number.
470    /// e.g. "Label: 5s, 5ms, 5μs"
471    Duration,
472
473    /// A timestamp, relative to the start of the profile. The value is given in
474    /// float milliseconds.
475    ///
476    ///  e.g. "Label: 15.5s, 20.5ms, 30.5μs"
477    Time,
478
479    /// Display a millisecond value as seconds, regardless of the magnitude of the number.
480    ///
481    /// e.g. "Label: 5s" for a value of 5000.0
482    Seconds,
483
484    /// Display a millisecond value as milliseconds, regardless of the magnitude of the number.
485    ///
486    /// e.g. "Label: 5ms" for a value of 5.0
487    Milliseconds,
488
489    /// Display a millisecond value as microseconds, regardless of the magnitude of the number.
490    ///
491    /// e.g. "Label: 5μs" for a value of 0.0005
492    Microseconds,
493
494    /// Display a millisecond value as seconds, regardless of the magnitude of the number.
495    ///
496    /// e.g. "Label: 5ns" for a value of 0.0000005
497    Nanoseconds,
498
499    /// Display a bytes value in a unit that's appropriate for the number's magnitude.
500    ///
501    /// e.g. "Label: 5.55mb, 5 bytes, 312.5kb"
502    Bytes,
503
504    /// This should be a value between 0 and 1.
505    /// e.g. "Label: 50%" for a value of 0.5
506    Percentage,
507
508    /// A generic integer number.
509    /// Do not use it for time information.
510    ///
511    /// "Label: 52, 5,323, 1,234,567"
512    Integer,
513
514    /// A generic floating point number.
515    /// Do not use it for time information.
516    ///
517    /// "Label: 52.23, 0.0054, 123,456.78"
518    Decimal,
519}
520
521/// The kind of a marker field. Every marker field is either a string or a number.
522#[derive(Debug, Clone, Copy, PartialEq, Eq)]
523pub enum MarkerFieldFormatKind {
524    String,
525    Number,
526}
527
528impl MarkerFieldFormat {
529    /// Whether this field is a number field or a string field.
530    ///
531    /// This determines whether we call `number_field_value` or
532    /// `string_field_value` to get the field values.
533    pub fn kind(&self) -> MarkerFieldFormatKind {
534        match self {
535            Self::Url | Self::FilePath | Self::SanitizedString | Self::String => {
536                MarkerFieldFormatKind::String
537            }
538            Self::Duration
539            | Self::Time
540            | Self::Seconds
541            | Self::Milliseconds
542            | Self::Microseconds
543            | Self::Nanoseconds
544            | Self::Bytes
545            | Self::Percentage
546            | Self::Integer
547            | Self::Decimal => MarkerFieldFormatKind::Number,
548        }
549    }
550}
551
552bitflags! {
553    /// Marker field flags, used in the marker schema.
554    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
555    pub struct MarkerFieldFlags: u32 {
556        /// Whether this field's value should be matched against search terms.
557        const SEARCHABLE = 0b00000001;
558    }
559}
560
561/// A graph within a marker graph track, used in [`StaticSchemaMarker::GRAPHS`].
562///
563/// Used with runtime-generated marker schemas. Use [`RuntimeSchemaMarkerGraph`]
564/// when using [`RuntimeSchemaMarkerSchema`].
565pub struct StaticSchemaMarkerGraph {
566    /// The key of a number field that's declared in the marker schema.
567    ///
568    /// The values of this field are the values of this graph line /
569    /// bar graph segment.
570    pub key: &'static str,
571    /// Whether this marker graph segment is a line or a bar graph segment.
572    pub graph_type: MarkerGraphType,
573    /// The color of the graph segment. If `None`, the choice is up to the front-end.
574    pub color: Option<GraphColor>,
575}
576
577/// A graph within a marker graph track, used in [`RuntimeSchemaMarkerSchema::graphs`].
578///
579/// Used with runtime-generated marker schemas. Use [`StaticSchemaMarkerGraph`]
580/// when using [`StaticSchemaMarker`].
581#[derive(Clone, Debug, Serialize)]
582pub struct RuntimeSchemaMarkerGraph {
583    /// The key of a number field that's declared in the marker schema.
584    ///
585    /// The values of this field are the values of this graph line /
586    /// bar graph segment.
587    pub key: String,
588    /// Whether this marker graph segment is a line or a bar graph segment.
589    #[serde(rename = "type")]
590    pub graph_type: MarkerGraphType,
591    /// The color of the graph segment. If `None`, the choice is up to the front-end.
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub color: Option<GraphColor>,
594}
595
596impl From<&StaticSchemaMarkerGraph> for RuntimeSchemaMarkerGraph {
597    fn from(schema: &StaticSchemaMarkerGraph) -> Self {
598        Self {
599            key: schema.key.into(),
600            graph_type: schema.graph_type,
601            color: schema.color,
602        }
603    }
604}
605
606/// The type of a graph segment within a marker graph.
607#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize)]
608#[serde(rename_all = "kebab-case")]
609pub enum MarkerGraphType {
610    /// As a bar graph.
611    Bar,
612    /// As lines.
613    Line,
614    /// As lines that are colored underneath.
615    LineFilled,
616}
617
618/// The color used for a graph segment within a marker graph.
619#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize)]
620#[serde(rename_all = "lowercase")]
621pub enum GraphColor {
622    Blue,
623    Green,
624    Grey,
625    Ink,
626    Magenta,
627    Orange,
628    Purple,
629    Red,
630    Teal,
631    Yellow,
632}
633
634#[derive(Debug, Clone)]
635pub struct InternalMarkerSchema {
636    /// The name of this marker type.
637    type_name: String,
638
639    /// List of marker display locations.
640    locations: MarkerLocations,
641
642    chart_label: Option<String>,
643    tooltip_label: Option<String>,
644    table_label: Option<String>,
645
646    /// The marker fields. These can be specified on each marker.
647    fields: Vec<RuntimeSchemaMarkerField>,
648
649    /// Any graph tracks created from markers of this type
650    graphs: Vec<RuntimeSchemaMarkerGraph>,
651
652    string_field_count: usize,
653    number_field_count: usize,
654
655    description: Option<String>,
656}
657
658impl From<RuntimeSchemaMarkerSchema> for InternalMarkerSchema {
659    fn from(schema: RuntimeSchemaMarkerSchema) -> Self {
660        Self::from_runtime_schema(schema)
661    }
662}
663
664impl InternalMarkerSchema {
665    pub fn from_runtime_schema(schema: RuntimeSchemaMarkerSchema) -> Self {
666        let string_field_count = schema
667            .fields
668            .iter()
669            .filter(|f| f.format.kind() == MarkerFieldFormatKind::String)
670            .count();
671        let number_field_count = schema
672            .fields
673            .iter()
674            .filter(|f| f.format.kind() == MarkerFieldFormatKind::Number)
675            .count();
676        Self {
677            type_name: schema.type_name,
678            locations: schema.locations,
679            chart_label: schema.chart_label,
680            tooltip_label: schema.tooltip_label,
681            table_label: schema.table_label,
682            fields: schema.fields,
683            graphs: schema.graphs,
684            string_field_count,
685            number_field_count,
686            description: schema.description,
687        }
688    }
689
690    pub fn from_static_schema<T: StaticSchemaMarker>() -> Self {
691        let string_field_count = T::FIELDS
692            .iter()
693            .filter(|f| f.format.kind() == MarkerFieldFormatKind::String)
694            .count();
695        let number_field_count = T::FIELDS
696            .iter()
697            .filter(|f| f.format.kind() == MarkerFieldFormatKind::Number)
698            .count();
699        Self {
700            type_name: T::UNIQUE_MARKER_TYPE_NAME.into(),
701            locations: T::LOCATIONS,
702            chart_label: T::CHART_LABEL.map(Into::into),
703            tooltip_label: T::TOOLTIP_LABEL.map(Into::into),
704            table_label: T::TABLE_LABEL.map(Into::into),
705            fields: T::FIELDS.iter().map(Into::into).collect(),
706            string_field_count,
707            number_field_count,
708            description: T::DESCRIPTION.map(Into::into),
709            graphs: T::GRAPHS.iter().map(Into::into).collect(),
710        }
711    }
712
713    pub fn type_name(&self) -> &str {
714        &self.type_name
715    }
716    pub fn fields(&self) -> &[RuntimeSchemaMarkerField] {
717        &self.fields
718    }
719    pub fn string_field_count(&self) -> usize {
720        self.string_field_count
721    }
722    pub fn number_field_count(&self) -> usize {
723        self.number_field_count
724    }
725    fn serialize_self<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
726    where
727        S: serde::Serializer,
728    {
729        let mut map = serializer.serialize_map(None)?;
730        map.serialize_entry("name", &self.type_name)?;
731        map.serialize_entry("display", &SerializableSchemaDisplay(self.locations))?;
732        if let Some(label) = &self.chart_label {
733            map.serialize_entry("chartLabel", label)?;
734        }
735        if let Some(label) = &self.tooltip_label {
736            map.serialize_entry("tooltipLabel", label)?;
737        }
738        if let Some(label) = &self.table_label {
739            map.serialize_entry("tableLabel", label)?;
740        }
741        if let Some(description) = &self.description {
742            map.serialize_entry("description", description)?;
743        }
744        map.serialize_entry("fields", &SerializableSchemaFields(self))?;
745        if !self.graphs.is_empty() {
746            map.serialize_entry("graphs", &self.graphs)?;
747        }
748        map.end()
749    }
750
751    fn serialize_fields<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
752    where
753        S: serde::Serializer,
754    {
755        let mut seq = serializer.serialize_seq(None)?;
756        for field in &self.fields {
757            seq.serialize_element(&SerializableSchemaField(field))?;
758        }
759        seq.end()
760    }
761}
762
763impl Serialize for InternalMarkerSchema {
764    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
765    where
766        S: serde::Serializer,
767    {
768        self.serialize_self(serializer)
769    }
770}
771
772struct SerializableSchemaFields<'a>(&'a InternalMarkerSchema);
773
774impl Serialize for SerializableSchemaFields<'_> {
775    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
776    where
777        S: serde::Serializer,
778    {
779        self.0.serialize_fields(serializer)
780    }
781}
782
783struct SerializableSchemaField<'a>(&'a RuntimeSchemaMarkerField);
784
785impl Serialize for SerializableSchemaField<'_> {
786    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
787    where
788        S: serde::Serializer,
789    {
790        let mut map = serializer.serialize_map(None)?;
791        map.serialize_entry("key", &self.0.key)?;
792        if !self.0.label.is_empty() {
793            map.serialize_entry("label", &self.0.label)?;
794        }
795        map.serialize_entry("format", &self.0.format)?;
796        if self.0.flags.contains(MarkerFieldFlags::SEARCHABLE) {
797            map.serialize_entry("searchable", &true)?;
798        }
799        map.end()
800    }
801}
802
803struct SerializableSchemaDisplay(MarkerLocations);
804
805impl Serialize for SerializableSchemaDisplay {
806    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
807    where
808        S: serde::Serializer,
809    {
810        let mut seq = serializer.serialize_seq(None)?;
811        if self.0.contains(MarkerLocations::MARKER_CHART) {
812            seq.serialize_element("marker-chart")?;
813        }
814        if self.0.contains(MarkerLocations::MARKER_TABLE) {
815            seq.serialize_element("marker-table")?;
816        }
817        if self.0.contains(MarkerLocations::TIMELINE_OVERVIEW) {
818            seq.serialize_element("timeline-overview")?;
819        }
820        if self.0.contains(MarkerLocations::TIMELINE_MEMORY) {
821            seq.serialize_element("timeline-memory")?;
822        }
823        if self.0.contains(MarkerLocations::TIMELINE_IPC) {
824            seq.serialize_element("timeline-ipc")?;
825        }
826        if self.0.contains(MarkerLocations::TIMELINE_FILEIO) {
827            seq.serialize_element("timeline-fileio")?;
828        }
829        seq.end()
830    }
831}