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}