From a757ecd8d57f3d32210d9fb313e190ecd93a846f Mon Sep 17 00:00:00 2001 From: Markus Stange <markus.stange@gmail.com> Date: Fri, 31 Jan 2025 17:04:23 -0500 Subject: [PATCH] Make StaticSchemaMarker interface more ergonomic. --- fxprof-processed-profile/src/lib.rs | 7 +- fxprof-processed-profile/src/markers.rs | 660 +++++++++++------- fxprof-processed-profile/src/profile.rs | 46 +- .../tests/integration_tests/main.rs | 111 ++- samply/src/linux_shared/converter.rs | 28 +- samply/src/shared/jit_function_add_marker.rs | 37 +- samply/src/shared/per_cpu.rs | 77 +- samply/src/shared/process_sample_data.rs | 193 ++--- samply/src/windows/coreclr.rs | 132 ++-- samply/src/windows/profile_context.rs | 54 +- 10 files changed, 688 insertions(+), 657 deletions(-) diff --git a/fxprof-processed-profile/src/lib.rs b/fxprof-processed-profile/src/lib.rs index 20b01119..729b21a1 100644 --- a/fxprof-processed-profile/src/lib.rs +++ b/fxprof-processed-profile/src/lib.rs @@ -68,9 +68,10 @@ pub use global_lib_table::{LibraryHandle, UsedLibraryAddressesIterator}; pub use lib_mappings::LibMappings; pub use library_info::{LibraryInfo, Symbol, SymbolTable}; pub use markers::{ - GraphColor, Marker, MarkerFieldFormat, MarkerFieldFormatKind, MarkerFieldSchema, - MarkerGraphSchema, MarkerGraphType, MarkerHandle, MarkerLocation, MarkerSchema, - MarkerStaticField, MarkerTiming, MarkerTypeHandle, StaticSchemaMarker, + GraphColor, Marker, MarkerFieldFlags, MarkerFieldFormat, MarkerFieldFormatKind, + MarkerGraphType, MarkerHandle, MarkerLocations, MarkerTiming, MarkerTypeHandle, + RuntimeSchemaMarkerField, RuntimeSchemaMarkerGraph, RuntimeSchemaMarkerSchema, + StaticSchemaMarker, StaticSchemaMarkerField, StaticSchemaMarkerGraph, }; pub use process::ThreadHandle; pub use profile::{FrameHandle, Profile, SamplingInterval, StackHandle, StringHandle}; diff --git a/fxprof-processed-profile/src/markers.rs b/fxprof-processed-profile/src/markers.rs index 3f13218f..4904309f 100644 --- a/fxprof-processed-profile/src/markers.rs +++ b/fxprof-processed-profile/src/markers.rs @@ -2,6 +2,7 @@ * 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/. */ +use bitflags::bitflags; use serde::ser::{SerializeMap, SerializeSeq}; use serde::Serialize; use serde_derive::Serialize; @@ -46,7 +47,7 @@ pub enum MarkerTiming { /// The marker trait. You'll likely want to implement [`StaticSchemaMarker`] instead. /// /// Markers have a type, a name, a category, and an arbitrary number of fields. -/// The fields of a marker type are defined by the marker type's schema, see [`MarkerSchema`]. +/// The fields of a marker type are defined by the marker type's schema, see [`RuntimeSchemaMarkerSchema`]. /// The timestamps are not part of the marker; they are supplied separately to /// [`Profile::add_marker`] when a marker is added to the profile. /// @@ -68,30 +69,30 @@ pub trait Marker { /// The category of this marker. The marker chart groups marker rows by category. fn category(&self, profile: &mut Profile) -> CategoryHandle; - /// Called for any fields defined in the schema whose [`format`](MarkerFieldSchema::format) is + /// Called for any fields defined in the schema whose [`format`](RuntimeSchemaMarkerField::format) is /// of [kind](MarkerFieldFormat::kind) [`MarkerFieldFormatKind::String`]. /// - /// `field_index` is an index into the schema's [`fields`](MarkerSchema::fields). + /// `field_index` is an index into the schema's [`fields`](RuntimeSchemaMarkerSchema::fields). /// /// You can panic for any unexpected field indexes, for example /// using `unreachable!()`. You can even panic unconditionally if this /// marker type doesn't have any string fields. /// /// If you do see unexpected calls to this method, make sure you're not registering - /// multiple different schemas with the same [`MarkerSchema::type_name`]. + /// multiple different schemas with the same [`RuntimeSchemaMarkerSchema::type_name`]. fn string_field_value(&self, field_index: u32) -> StringHandle; - /// Called for any fields defined in the schema whose [`format`](MarkerFieldSchema::format) is + /// Called for any fields defined in the schema whose [`format`](RuntimeSchemaMarkerField::format) is /// of [kind](MarkerFieldFormat::kind) [`MarkerFieldFormatKind::Number`]. /// - /// `field_index` is an index into the schema's [`fields`](MarkerSchema::fields). + /// `field_index` is an index into the schema's [`fields`](RuntimeSchemaMarkerSchema::fields). /// /// You can panic for any unexpected field indexes, for example /// using `unreachable!()`. You can even panic unconditionally if this /// marker type doesn't have any number fields. /// /// If you do see unexpected calls to this method, make sure you're not registering - /// multiple different schemas with the same [`MarkerSchema::type_name`]. + /// multiple different schemas with the same [`RuntimeSchemaMarkerSchema::type_name`]. fn number_field_value(&self, field_index: u32) -> f64; } @@ -99,7 +100,7 @@ pub trait Marker { /// [`StaticSchemaMarker`] automatically implements the [`Marker`] trait via a blanket impl. /// /// Markers have a type, a name, a category, and an arbitrary number of fields. -/// The fields of a marker type are defined by the marker type's schema, see [`MarkerSchema`]. +/// The fields of a marker type are defined by the marker type's schema, see [`RuntimeSchemaMarkerSchema`]. /// The timestamps are not part of the marker; they are supplied separately to /// [`Profile::add_marker`] when a marker is added to the profile. /// @@ -107,7 +108,7 @@ pub trait Marker { /// /// ``` /// use fxprof_processed_profile::{ -/// Profile, Marker, MarkerLocation, MarkerFieldFormat, MarkerSchema, MarkerFieldSchema, +/// Profile, Marker, MarkerLocations, MarkerFieldFlags, MarkerFieldFormat, StaticSchemaMarkerField, /// StaticSchemaMarker, CategoryHandle, StringHandle, /// }; /// @@ -121,23 +122,16 @@ pub trait Marker { /// impl StaticSchemaMarker for TextMarker { /// const UNIQUE_MARKER_TYPE_NAME: &'static str = "Text"; /// -/// fn schema() -> MarkerSchema { -/// MarkerSchema { -/// type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), -/// locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], -/// chart_label: Some("{marker.data.text}".into()), -/// tooltip_label: None, -/// table_label: Some("{marker.name} - {marker.data.text}".into()), -/// fields: vec![MarkerFieldSchema { -/// key: "text".into(), -/// label: "Contents".into(), -/// format: MarkerFieldFormat::String, -/// searchable: true, -/// }], -/// static_fields: vec![], -/// graphs: vec![], -/// } -/// } +/// const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART.union(MarkerLocations::MARKER_TABLE); +/// const CHART_LABEL: Option<&'static str> = Some("{marker.data.text}"); +/// const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.text}"); +/// +/// const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { +/// key: "text", +/// label: "Contents", +/// format: MarkerFieldFormat::String, +/// flags: MarkerFieldFlags::SEARCHABLE, +/// }]; /// /// fn name(&self, _profile: &mut Profile) -> StringHandle { /// self.name @@ -158,11 +152,52 @@ pub trait Marker { /// ``` pub trait StaticSchemaMarker { /// A unique string name for this marker type. Has to match the - /// [`MarkerSchema::type_name`] of this type's schema. + /// [`RuntimeSchemaMarkerSchema::type_name`] of this type's schema. const UNIQUE_MARKER_TYPE_NAME: &'static str; - /// The [`MarkerSchema`] for this marker type. - fn schema() -> MarkerSchema; + /// An optional description string. Applies to all markers of this type. + const DESCRIPTION: Option<&'static str> = None; + + /// Set of marker display locations. + const LOCATIONS: MarkerLocations = + MarkerLocations::MARKER_CHART.union(MarkerLocations::MARKER_TABLE); + + /// A template string defining the label shown within each marker's box in the marker chart. + /// + /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`. + /// + /// If set to `None`, the boxes in the marker chart will be empty. + const CHART_LABEL: Option<&'static str> = None; + + /// A template string defining the label shown in the first row of the marker's tooltip. + /// + /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`. + /// + /// Defaults to `{marker.name}` if set to `None`. + const TOOLTIP_LABEL: Option<&'static str> = None; + + /// A template string defining the label shown within each marker's box in the marker chart. + /// + /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`. + /// + /// Defaults to `{marker.name}` if set to `None`. + const TABLE_LABEL: Option<&'static str> = None; + + /// The marker fields. The values are supplied by each marker, in the marker's + /// implementations of the `string_field_value` and `number_field_value` trait methods. + const FIELDS: &'static [StaticSchemaMarkerField]; + + /// Any graph lines / segments created from markers of this type. + /// + /// If this is non-empty, the Firefox Profiler will create one graph track per + /// marker *name*, per thread, based on the markers it finds on that thread. + /// The marker name becomes the track's label. + /// + /// The elements in the graphs array describe individual graph lines or bar + /// chart segments which are all drawn inside the same track, stacked on top of + /// each other, in the order that they're listed here, with the first entry + /// becoming the bottom-most graph within the track. + const GRAPHS: &'static [StaticSchemaMarkerGraph] = &[]; /// The name of this marker, as an interned string handle. /// @@ -173,30 +208,30 @@ pub trait StaticSchemaMarker { /// The category of this marker. The marker chart groups marker rows by category. fn category(&self, profile: &mut Profile) -> CategoryHandle; - /// Called for any fields defined in the schema whose [`format`](MarkerFieldSchema::format) is + /// Called for any fields defined in the schema whose [`format`](RuntimeSchemaMarkerField::format) is /// of [kind](MarkerFieldFormat::kind) [`MarkerFieldFormatKind::String`]. /// - /// `field_index` is an index into the schema's [`fields`](MarkerSchema::fields). + /// `field_index` is an index into the schema's [`fields`](RuntimeSchemaMarkerSchema::fields). /// /// You can panic for any unexpected field indexes, for example /// using `unreachable!()`. You can even panic unconditionally if this /// marker type doesn't have any string fields. /// /// If you do see unexpected calls to this method, make sure you're not registering - /// multiple different schemas with the same [`MarkerSchema::type_name`]. + /// multiple different schemas with the same [`RuntimeSchemaMarkerSchema::type_name`]. fn string_field_value(&self, field_index: u32) -> StringHandle; - /// Called for any fields defined in the schema whose [`format`](MarkerFieldSchema::format) is + /// Called for any fields defined in the schema whose [`format`](RuntimeSchemaMarkerField::format) is /// of [kind](MarkerFieldFormat::kind) [`MarkerFieldFormatKind::Number`]. /// - /// `field_index` is an index into the schema's [`fields`](MarkerSchema::fields). + /// `field_index` is an index into the schema's [`fields`](RuntimeSchemaMarkerSchema::fields). /// /// You can panic for any unexpected field indexes, for example /// using `unreachable!()`. You can even panic unconditionally if this /// marker type doesn't have any number fields. /// /// If you do see unexpected calls to this method, make sure you're not registering - /// multiple different schemas with the same [`MarkerSchema::type_name`]. + /// multiple different schemas with the same [`RuntimeSchemaMarkerSchema::type_name`]. fn number_field_value(&self, field_index: u32) -> f64; } @@ -223,282 +258,184 @@ impl<T: StaticSchemaMarker> Marker for T { } /// Describes a marker type, including the names and types of the marker's fields. +/// You only need this if you don't know the schema until runtime. Otherwise, use +/// [`StaticSchemaMarker`] instead. /// /// Example: /// /// ``` /// use fxprof_processed_profile::{ -/// Profile, Marker, MarkerLocation, MarkerFieldFormat, MarkerSchema, MarkerFieldSchema, -/// MarkerStaticField, StaticSchemaMarker, CategoryHandle, StringHandle, +/// Profile, Marker, MarkerLocations, MarkerFieldFlags, MarkerFieldFormat, RuntimeSchemaMarkerSchema, RuntimeSchemaMarkerField, +/// CategoryHandle, StringHandle, /// }; /// /// # fn fun() { -/// let schema = MarkerSchema { +/// let schema = RuntimeSchemaMarkerSchema { /// type_name: "custom".into(), -/// locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], +/// locations: MarkerLocations::MARKER_CHART | MarkerLocations::MARKER_TABLE, /// chart_label: Some("{marker.data.eventName}".into()), /// tooltip_label: Some("Custom {marker.name} marker".into()), /// table_label: Some("{marker.name} - {marker.data.eventName} with allocation size {marker.data.allocationSize} (latency: {marker.data.latency})".into()), /// fields: vec![ -/// MarkerFieldSchema { +/// RuntimeSchemaMarkerField { /// key: "eventName".into(), /// label: "Event name".into(), /// format: MarkerFieldFormat::String, -/// searchable: true, +/// flags: MarkerFieldFlags::SEARCHABLE, /// }, -/// MarkerFieldSchema { +/// RuntimeSchemaMarkerField { /// key: "allocationSize".into(), /// label: "Allocation size".into(), /// format: MarkerFieldFormat::Bytes, -/// searchable: true, +/// flags: MarkerFieldFlags::SEARCHABLE, /// }, -/// MarkerFieldSchema { +/// RuntimeSchemaMarkerField { /// key: "url".into(), /// label: "URL".into(), /// format: MarkerFieldFormat::Url, -/// searchable: true, +/// flags: MarkerFieldFlags::SEARCHABLE, /// }, -/// MarkerFieldSchema { +/// RuntimeSchemaMarkerField { /// key: "latency".into(), /// label: "Latency".into(), /// format: MarkerFieldFormat::Duration, -/// searchable: true, +/// flags: MarkerFieldFlags::SEARCHABLE, /// }, /// ], -/// static_fields: vec![MarkerStaticField { -/// label: "Description".into(), -/// value: "This is a test marker with a custom schema.".into(), -/// }], +/// description: Some("This is a test marker with a custom schema.".into()), /// graphs: vec![], /// }; /// # } /// ``` #[derive(Debug, Clone)] -pub struct MarkerSchema { +pub struct RuntimeSchemaMarkerSchema { /// The unique name of this marker type. There must not be any other schema /// with the same name. pub type_name: String, - /// List of marker display locations. - pub locations: Vec<MarkerLocation>, + /// An optional description string. Applies to all markers of this type. + pub description: Option<String>, + + /// Set of marker display locations. + pub locations: MarkerLocations, /// A template string defining the label shown within each marker's box in the marker chart. /// - /// Usable template literals are `{marker.name}` and `{marker.data.fieldname}`. + /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`. /// /// If set to `None`, the boxes in the marker chart will be empty. pub chart_label: Option<String>, /// A template string defining the label shown in the first row of the marker's tooltip. /// - /// Usable template literals are `{marker.name}` and `{marker.data.fieldname}`. + /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`. /// - /// Defaults to `{marker.name}` if set to `None`. (TODO: verify this is true) + /// Defaults to `{marker.name}` if set to `None`. pub tooltip_label: Option<String>, /// A template string defining the label shown within each marker's box in the marker chart. /// - /// Usable template literals are `{marker.name}` and `{marker.data.fieldname}`. + /// Usable template literals are `{marker.name}` and `{marker.data.fieldkey}`. /// - /// Defaults to `{marker.name}` if set to `None`. (TODO: verify this is true) + /// Defaults to `{marker.name}` if set to `None`. pub table_label: Option<String>, /// The marker fields. The values are supplied by each marker, in the marker's /// implementations of the `string_field_value` and `number_field_value` trait methods. - pub fields: Vec<MarkerFieldSchema>, - - /// The static fields of this marker type, with fixed values that apply to all markers of this type. - /// These are usually used for things like a human readable marker type description. - pub static_fields: Vec<MarkerStaticField>, + pub fields: Vec<RuntimeSchemaMarkerField>, /// Any graph lines / segments created from markers of this type. /// /// If this is non-empty, the Firefox Profiler will create one graph track per - /// marker *name*, per thread, based on the markers it sees on that thread. + /// marker *name*, per thread, based on the markers it finds on that thread. /// The marker name becomes the track's label. /// /// The elements in the graphs array describe individual graph lines or bar /// chart segments which are all drawn inside the same track, stacked on top of /// each other, in the order that they're listed here, with the first entry - /// becoming the bottom-most graph segment within the track. - pub graphs: Vec<MarkerGraphSchema>, + /// becoming the bottom-most graph within the track. + pub graphs: Vec<RuntimeSchemaMarkerGraph>, } -#[derive(Debug, Clone)] -pub struct InternalMarkerSchema { - /// The name of this marker type. - type_name: String, - - /// List of marker display locations. - locations: Vec<MarkerLocation>, - - chart_label: Option<String>, - tooltip_label: Option<String>, - table_label: Option<String>, - - /// The marker fields. These can be specified on each marker. - fields: Vec<MarkerFieldSchema>, - - /// Any graph tracks created from markers of this type - graphs: Vec<MarkerGraphSchema>, - - string_field_count: usize, - number_field_count: usize, - - /// The static fields of this marker type, with fixed values that apply to all markers. - /// These are usually used for things like a human readable marker type description. - static_fields: Vec<MarkerStaticField>, -} - -impl From<MarkerSchema> for InternalMarkerSchema { - fn from(schema: MarkerSchema) -> Self { - let string_field_count = schema - .fields - .iter() - .filter(|f| f.format.kind() == MarkerFieldFormatKind::String) - .count(); - let number_field_count = schema - .fields - .iter() - .filter(|f| f.format.kind() == MarkerFieldFormatKind::Number) - .count(); - Self { - type_name: schema.type_name, - locations: schema.locations, - chart_label: schema.chart_label, - tooltip_label: schema.tooltip_label, - table_label: schema.table_label, - fields: schema.fields, - graphs: schema.graphs, - string_field_count, - number_field_count, - static_fields: schema.static_fields, - } +bitflags! { + /// Locations in the profiler UI where markers can be displayed. + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] + pub struct MarkerLocations: u32 { + /// Show the marker in the "marker chart" panel. + const MARKER_CHART = 1 << 0; + /// Show the marker in the marker table. + const MARKER_TABLE = 1 << 1; + /// This adds markers to the main marker timeline in the header, but only + /// for main threads and for threads that were specifically asked to show + /// these markers using [`Profile::set_thread_show_markers_in_timeline`]. + const TIMELINE_OVERVIEW = 1 << 2; + /// In the timeline, this is a section that breaks out markers that are + /// related to memory. When memory counters are used, this is its own + /// track, otherwise it is displayed with the main thread. + const TIMELINE_MEMORY = 1 << 3; + /// This adds markers to the IPC timeline area in the header. + const TIMELINE_IPC = 1 << 4; + /// This adds markers to the FileIO timeline area in the header. + const TIMELINE_FILEIO = 1 << 5; } } -impl InternalMarkerSchema { - pub fn type_name(&self) -> &str { - &self.type_name - } - pub fn fields(&self) -> &[MarkerFieldSchema] { - &self.fields - } - pub fn string_field_count(&self) -> usize { - self.string_field_count - } - pub fn number_field_count(&self) -> usize { - self.number_field_count - } - fn serialize_self<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - let mut map = serializer.serialize_map(None)?; - map.serialize_entry("name", &self.type_name)?; - map.serialize_entry("display", &self.locations)?; - if let Some(label) = &self.chart_label { - map.serialize_entry("chartLabel", label)?; - } - if let Some(label) = &self.tooltip_label { - map.serialize_entry("tooltipLabel", label)?; - } - if let Some(label) = &self.table_label { - map.serialize_entry("tableLabel", label)?; - } - map.serialize_entry("data", &SerializableSchemaFields(self))?; - if !self.graphs.is_empty() { - map.serialize_entry("graphs", &self.graphs)?; - } - map.end() - } - - fn serialize_fields<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - let mut seq = - serializer.serialize_seq(Some(self.fields.len() + self.static_fields.len()))?; - for field in &self.fields { - seq.serialize_element(field)?; - } - for field in &self.static_fields { - seq.serialize_element(field)?; - } - seq.end() - } -} +/// The field definition of a marker field, used in [`StaticSchemaMarker::FIELDS`]. +/// +/// For each marker which uses this schema, the value for this field is supplied by the +/// marker's implementation of [`number_field_value`](Marker::number_field_value) / +/// [`string_field_value`](Marker::string_field_value), depending on this field +/// format's [kind](MarkerFieldFormat::kind). +/// +/// Used with runtime-generated marker schemas. Use [`RuntimeSchemaMarkerField`] +/// when using [`RuntimeSchemaMarkerSchema`]. +pub struct StaticSchemaMarkerField { + /// The field key. Must not be `type` or `cause`. + pub key: &'static str, -impl Serialize for InternalMarkerSchema { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - self.serialize_self(serializer) - } -} + /// The user-visible label of this field. + pub label: &'static str, -struct SerializableSchemaFields<'a>(&'a InternalMarkerSchema); + /// The format of this field. + pub format: MarkerFieldFormat, -impl Serialize for SerializableSchemaFields<'_> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - self.0.serialize_fields(serializer) - } + /// Additional field flags. + pub flags: MarkerFieldFlags, } -// /// The location of markers with this type. +/// The field definition of a marker field, used in [`RuntimeSchemaMarkerSchema::fields`]. /// -/// Markers can be shown in different parts of the Firefox Profiler UI. +/// For each marker which uses this schema, the value for this field is supplied by the +/// marker's implementation of [`number_field_value`](Marker::number_field_value) / +/// [`string_field_value`](Marker::string_field_value), depending on this field +/// format's [kind](MarkerFieldFormat::kind). /// -/// Multiple [`MarkerLocation`]s can be specified for a single marker type. -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum MarkerLocation { - MarkerChart, - MarkerTable, - /// This adds markers to the main marker timeline in the header, but only - /// for main threads and for threads that were specifically asked to show - /// these markers using [`Profile::set_thread_show_markers_in_timeline`]. - TimelineOverview, - /// In the timeline, this is a section that breaks out markers that are - /// related to memory. When memory counters are enabled, this is its own - /// track, otherwise it is displayed with the main thread. - TimelineMemory, - /// This adds markers to the IPC timeline area in the header. - TimelineIPC, - /// This adds markers to the FileIO timeline area in the header. - #[serde(rename = "timeline-fileio")] - TimelineFileIO, - /// TODO - This is not supported yet. - StackChart, -} - -/// The field description of a marker field which has the same key and value on all markers with this schema. -#[derive(Debug, Clone, Serialize)] -pub struct MarkerStaticField { - pub label: String, - pub value: String, -} - -/// The field description of a marker field. The value for this field is supplied by the marker's implementation -/// of [`number_field_value`](Marker::number_field_value) / [`string_field_value`](Marker::string_field_value). -#[derive(Debug, Clone, Serialize)] -pub struct MarkerFieldSchema { +/// Used with runtime-generated marker schemas. Use [`StaticSchemaMarkerField`] +/// when using [`StaticSchemaMarker`]. +#[derive(Debug, Clone)] +pub struct RuntimeSchemaMarkerField { /// The field key. Must not be `type` or `cause`. pub key: String, /// The user-visible label of this field. - #[serde(skip_serializing_if = "str::is_empty")] pub label: String, /// The format of this field. pub format: MarkerFieldFormat, /// Whether this field's value should be matched against search terms. - pub searchable: bool, + pub flags: MarkerFieldFlags, +} + +impl From<&StaticSchemaMarkerField> for RuntimeSchemaMarkerField { + fn from(schema: &StaticSchemaMarkerField) -> Self { + Self { + key: schema.key.into(), + label: schema.label.into(), + format: schema.format.clone(), + flags: schema.flags, + } + } } /// The field format of a marker field. @@ -613,6 +550,60 @@ impl MarkerFieldFormat { } } +bitflags! { + /// Marker field flags, used in the marker schema. + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] + pub struct MarkerFieldFlags: u32 { + /// Whether this field's value should be matched against search terms. + const SEARCHABLE = 0b00000001; + } +} + +/// A graph within a marker graph track, used in [`StaticSchemaMarker::GRAPHS`]. +/// +/// Used with runtime-generated marker schemas. Use [`RuntimeSchemaMarkerGraph`] +/// when using [`RuntimeSchemaMarkerSchema`]. +pub struct StaticSchemaMarkerGraph { + /// The key of a number field that's declared in the marker schema. + /// + /// The values of this field are the values of this graph line / + /// bar graph segment. + pub key: &'static str, + /// Whether this marker graph segment is a line or a bar graph segment. + pub graph_type: MarkerGraphType, + /// The color of the graph segment. If `None`, the choice is up to the front-end. + pub color: Option<GraphColor>, +} + +/// A graph within a marker graph track, used in [`RuntimeSchemaMarkerSchema::graphs`]. +/// +/// Used with runtime-generated marker schemas. Use [`StaticSchemaMarkerGraph`] +/// when using [`StaticSchemaMarker`]. +#[derive(Clone, Debug, Serialize)] +pub struct RuntimeSchemaMarkerGraph { + /// The key of a number field that's declared in the marker schema. + /// + /// The values of this field are the values of this graph line / + /// bar graph segment. + pub key: String, + /// Whether this marker graph segment is a line or a bar graph segment. + #[serde(rename = "type")] + pub graph_type: MarkerGraphType, + /// The color of the graph segment. If `None`, the choice is up to the front-end. + #[serde(skip_serializing_if = "Option::is_none")] + pub color: Option<GraphColor>, +} + +impl From<&StaticSchemaMarkerGraph> for RuntimeSchemaMarkerGraph { + fn from(schema: &StaticSchemaMarkerGraph) -> Self { + Self { + key: schema.key.into(), + graph_type: schema.graph_type, + color: schema.color, + } + } +} + /// The type of a graph segment within a marker graph. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize)] #[serde(rename_all = "kebab-case")] @@ -641,18 +632,215 @@ pub enum GraphColor { Yellow, } -/// One segment within a marker graph track. -#[derive(Clone, Debug, Serialize)] -pub struct MarkerGraphSchema { - /// The key of a number field that's declared in the marker schema. - /// - /// The values of this field are the values of this graph line / - /// bar graph segment. - pub key: &'static str, - /// Whether this marker graph segment is a line or a bar graph segment. - #[serde(rename = "type")] - pub graph_type: MarkerGraphType, - /// The color of the graph segment. If `None`, the choice is up to the front-end. - #[serde(skip_serializing_if = "Option::is_none")] - pub color: Option<GraphColor>, +#[derive(Debug, Clone)] +pub struct InternalMarkerSchema { + /// The name of this marker type. + type_name: String, + + /// List of marker display locations. + locations: MarkerLocations, + + chart_label: Option<String>, + tooltip_label: Option<String>, + table_label: Option<String>, + + /// The marker fields. These can be specified on each marker. + fields: Vec<RuntimeSchemaMarkerField>, + + /// Any graph tracks created from markers of this type + graphs: Vec<RuntimeSchemaMarkerGraph>, + + string_field_count: usize, + number_field_count: usize, + + description: Option<String>, +} + +impl From<RuntimeSchemaMarkerSchema> for InternalMarkerSchema { + fn from(schema: RuntimeSchemaMarkerSchema) -> Self { + Self::from_runtime_schema(schema) + } +} + +impl InternalMarkerSchema { + pub fn from_runtime_schema(schema: RuntimeSchemaMarkerSchema) -> Self { + let string_field_count = schema + .fields + .iter() + .filter(|f| f.format.kind() == MarkerFieldFormatKind::String) + .count(); + let number_field_count = schema + .fields + .iter() + .filter(|f| f.format.kind() == MarkerFieldFormatKind::Number) + .count(); + Self { + type_name: schema.type_name, + locations: schema.locations, + chart_label: schema.chart_label, + tooltip_label: schema.tooltip_label, + table_label: schema.table_label, + fields: schema.fields, + graphs: schema.graphs, + string_field_count, + number_field_count, + description: schema.description, + } + } + + pub fn from_static_schema<T: StaticSchemaMarker>() -> Self { + let string_field_count = T::FIELDS + .iter() + .filter(|f| f.format.kind() == MarkerFieldFormatKind::String) + .count(); + let number_field_count = T::FIELDS + .iter() + .filter(|f| f.format.kind() == MarkerFieldFormatKind::Number) + .count(); + Self { + type_name: T::UNIQUE_MARKER_TYPE_NAME.into(), + locations: T::LOCATIONS, + chart_label: T::CHART_LABEL.map(Into::into), + tooltip_label: T::TOOLTIP_LABEL.map(Into::into), + table_label: T::TABLE_LABEL.map(Into::into), + fields: T::FIELDS.iter().map(Into::into).collect(), + string_field_count, + number_field_count, + description: T::DESCRIPTION.map(Into::into), + graphs: T::GRAPHS.iter().map(Into::into).collect(), + } + } + + pub fn type_name(&self) -> &str { + &self.type_name + } + pub fn fields(&self) -> &[RuntimeSchemaMarkerField] { + &self.fields + } + pub fn string_field_count(&self) -> usize { + self.string_field_count + } + pub fn number_field_count(&self) -> usize { + self.number_field_count + } + fn serialize_self<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("name", &self.type_name)?; + map.serialize_entry("display", &SerializableSchemaDisplay(self.locations))?; + if let Some(label) = &self.chart_label { + map.serialize_entry("chartLabel", label)?; + } + if let Some(label) = &self.tooltip_label { + map.serialize_entry("tooltipLabel", label)?; + } + if let Some(label) = &self.table_label { + map.serialize_entry("tableLabel", label)?; + } + map.serialize_entry("data", &SerializableSchemaFields(self))?; + if !self.graphs.is_empty() { + map.serialize_entry("graphs", &self.graphs)?; + } + map.end() + } + + fn serialize_fields<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + for field in &self.fields { + seq.serialize_element(&SerializableSchemaField(field))?; + } + if let Some(description) = &self.description { + seq.serialize_element(&SerializableDescriptionStaticField(description))?; + } + seq.end() + } +} + +impl Serialize for InternalMarkerSchema { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + self.serialize_self(serializer) + } +} + +struct SerializableSchemaFields<'a>(&'a InternalMarkerSchema); + +impl Serialize for SerializableSchemaFields<'_> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + self.0.serialize_fields(serializer) + } +} + +struct SerializableSchemaField<'a>(&'a RuntimeSchemaMarkerField); + +impl Serialize for SerializableSchemaField<'_> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("key", &self.0.key)?; + if !self.0.label.is_empty() { + map.serialize_entry("label", &self.0.label)?; + } + map.serialize_entry("format", &self.0.format)?; + if self.0.flags.contains(MarkerFieldFlags::SEARCHABLE) { + map.serialize_entry("searchable", &true)?; + } + map.end() + } +} + +struct SerializableDescriptionStaticField<'a>(&'a str); + +impl Serialize for SerializableDescriptionStaticField<'_> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("label", "Description")?; + map.serialize_entry("value", self.0)?; + map.end() + } +} + +struct SerializableSchemaDisplay(MarkerLocations); + +impl Serialize for SerializableSchemaDisplay { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + if self.0.contains(MarkerLocations::MARKER_CHART) { + seq.serialize_element("marker-chart")?; + } + if self.0.contains(MarkerLocations::MARKER_TABLE) { + seq.serialize_element("marker-table")?; + } + if self.0.contains(MarkerLocations::TIMELINE_OVERVIEW) { + seq.serialize_element("timeline-overview")?; + } + if self.0.contains(MarkerLocations::TIMELINE_MEMORY) { + seq.serialize_element("timeline-memory")?; + } + if self.0.contains(MarkerLocations::TIMELINE_IPC) { + seq.serialize_element("timeline-ipc")?; + } + if self.0.contains(MarkerLocations::TIMELINE_FILEIO) { + seq.serialize_element("timeline-fileio")?; + } + seq.end() + } } diff --git a/fxprof-processed-profile/src/profile.rs b/fxprof-processed-profile/src/profile.rs index 06b2fd89..7e71b20a 100644 --- a/fxprof-processed-profile/src/profile.rs +++ b/fxprof-processed-profile/src/profile.rs @@ -16,8 +16,8 @@ use crate::global_lib_table::{GlobalLibTable, LibraryHandle, UsedLibraryAddresse use crate::lib_mappings::LibMappings; use crate::library_info::{LibraryInfo, SymbolTable}; use crate::markers::{ - GraphColor, InternalMarkerSchema, Marker, MarkerHandle, MarkerSchema, MarkerTiming, - MarkerTypeHandle, StaticSchemaMarker, + GraphColor, InternalMarkerSchema, Marker, MarkerHandle, MarkerTiming, MarkerTypeHandle, + RuntimeSchemaMarkerSchema, StaticSchemaMarker, }; use crate::process::{Process, ThreadHandle}; use crate::reference_timestamp::ReferenceTimestamp; @@ -423,7 +423,7 @@ impl Profile { self.threads[thread.0].set_tid(tid); } - /// Set whether to show a timeline view displaying [`MarkerLocation::TimelineOverview`](crate::MarkerLocation::TimelineOverview) + /// Set whether to show a timeline which displays [`MarkerLocations::TIMELINE_OVERVIEW`](crate::MarkerLocations::TIMELINE_OVERVIEW) /// markers for this thread. /// /// Main threads always have such a timeline view and always display such markers, @@ -670,7 +670,7 @@ impl Profile { ); } - /// Registers a marker type, given the type's [`MarkerSchema`]. Usually you only need to call this for + /// Registers a marker type for a [`RuntimeSchemaMarkerSchema`]. You only need to call this for /// marker types whose schema is dynamically created at runtime. /// /// After you register the marker type, you'll save its [`MarkerTypeHandle`] somewhere, and then @@ -678,8 +678,8 @@ impl Profile { /// handle from its implementation of [`Marker::marker_type`]. /// /// For marker types whose schema is known at compile time, you'll want to implement - /// [`StaticSchemaMarker`] instead. - pub fn register_marker_type(&mut self, schema: MarkerSchema) -> MarkerTypeHandle { + /// [`StaticSchemaMarker`] instead, and you don't need to call this method. + pub fn register_marker_type(&mut self, schema: RuntimeSchemaMarkerSchema) -> MarkerTypeHandle { let handle = MarkerTypeHandle(self.marker_schemas.len()); self.marker_schemas.push(schema.into()); handle @@ -697,7 +697,8 @@ impl Profile { Entry::Occupied(entry) => *entry.get(), Entry::Vacant(entry) => { let handle = MarkerTypeHandle(self.marker_schemas.len()); - self.marker_schemas.push(T::schema().into()); + let schema = InternalMarkerSchema::from_static_schema::<T>(); + self.marker_schemas.push(schema); entry.insert(handle); handle } @@ -710,9 +711,8 @@ impl Profile { /// /// ``` /// use fxprof_processed_profile::{ - /// Profile, Marker, MarkerTiming, MarkerLocation, MarkerFieldFormat, MarkerSchema, - /// MarkerFieldSchema, StaticSchemaMarker, CategoryHandle, StringHandle, ThreadHandle, - /// Timestamp, + /// Profile, CategoryHandle, Marker, MarkerFieldFlags, MarkerFieldFormat, MarkerTiming, + /// StaticSchemaMarker, StaticSchemaMarkerField, StringHandle, ThreadHandle, Timestamp, /// }; /// /// # fn fun() { @@ -735,23 +735,15 @@ impl Profile { /// impl StaticSchemaMarker for TextMarker { /// const UNIQUE_MARKER_TYPE_NAME: &'static str = "Text"; /// - /// fn schema() -> MarkerSchema { - /// MarkerSchema { - /// type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - /// locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - /// chart_label: Some("{marker.data.text}".into()), - /// tooltip_label: None, - /// table_label: Some("{marker.name} - {marker.data.text}".into()), - /// fields: vec![MarkerFieldSchema { - /// key: "text".into(), - /// label: "Contents".into(), - /// format: MarkerFieldFormat::String, - /// searchable: true, - /// }], - /// static_fields: vec![], - /// graphs: vec![], - /// } - /// } + /// const CHART_LABEL: Option<&'static str> = Some("{marker.data.text}"); + /// const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.text}"); + /// + /// const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + /// key: "text", + /// label: "Contents", + /// format: MarkerFieldFormat::String, + /// flags: MarkerFieldFlags::SEARCHABLE, + /// }]; /// /// fn name(&self, _profile: &mut Profile) -> StringHandle { /// self.name diff --git a/fxprof-processed-profile/tests/integration_tests/main.rs b/fxprof-processed-profile/tests/integration_tests/main.rs index d91e8b7d..e2a84f9d 100644 --- a/fxprof-processed-profile/tests/integration_tests/main.rs +++ b/fxprof-processed-profile/tests/integration_tests/main.rs @@ -5,9 +5,9 @@ use assert_json_diff::assert_json_eq; use debugid::DebugId; use fxprof_processed_profile::{ CategoryColor, CategoryHandle, CpuDelta, Frame, FrameFlags, FrameInfo, GraphColor, LibraryInfo, - MarkerFieldFormat, MarkerFieldSchema, MarkerGraphSchema, MarkerGraphType, MarkerLocation, - MarkerSchema, MarkerStaticField, MarkerTiming, Profile, ReferenceTimestamp, SamplingInterval, - StaticSchemaMarker, StringHandle, Symbol, SymbolTable, Timestamp, WeightType, + MarkerFieldFlags, MarkerFieldFormat, MarkerGraphType, MarkerTiming, Profile, + ReferenceTimestamp, SamplingInterval, StaticSchemaMarker, StaticSchemaMarkerField, + StaticSchemaMarkerGraph, StringHandle, Symbol, SymbolTable, Timestamp, WeightType, }; use serde_json::json; @@ -22,24 +22,14 @@ pub struct TextMarker { impl StaticSchemaMarker for TextMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "Text"; - - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.name}".into()), - tooltip_label: None, - table_label: Some("{marker.name} - {marker.data.name}".into()), - fields: vec![MarkerFieldSchema { - key: "name".into(), - label: "Details".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![], - graphs: vec![], - } - } + const CHART_LABEL: Option<&'static str> = Some("{marker.data.name}"); + const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.name}"); + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "name", + label: "Details", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, _profile: &mut Profile) -> StringHandle { self.name @@ -68,52 +58,43 @@ fn profile_without_js() { } impl StaticSchemaMarker for CustomMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "custom"; + const TOOLTIP_LABEL: Option<&'static str> = Some("Custom tooltip label"); - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: None, - tooltip_label: Some("Custom tooltip label".into()), - table_label: None, - fields: vec![ - MarkerFieldSchema { - key: "eventName".into(), - label: "Event name".into(), - format: MarkerFieldFormat::String, - searchable: true, - }, - MarkerFieldSchema { - key: "allocationSize".into(), - label: "Allocation size".into(), - format: MarkerFieldFormat::Bytes, - searchable: true, - }, - MarkerFieldSchema { - key: "url".into(), - label: "URL".into(), - format: MarkerFieldFormat::Url, - searchable: true, - }, - MarkerFieldSchema { - key: "latency".into(), - label: "Latency".into(), - format: MarkerFieldFormat::Duration, - searchable: true, - }, - ], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "This is a test marker with a custom schema.".into(), - }], + const FIELDS: &'static [StaticSchemaMarkerField] = &[ + StaticSchemaMarkerField { + key: "eventName", + label: "Event name", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }, + StaticSchemaMarkerField { + key: "allocationSize", + label: "Allocation size", + format: MarkerFieldFormat::Bytes, + flags: MarkerFieldFlags::SEARCHABLE, + }, + StaticSchemaMarkerField { + key: "url", + label: "URL", + format: MarkerFieldFormat::Url, + flags: MarkerFieldFlags::SEARCHABLE, + }, + StaticSchemaMarkerField { + key: "latency", + label: "Latency", + format: MarkerFieldFormat::Duration, + flags: MarkerFieldFlags::SEARCHABLE, + }, + ]; - graphs: vec![MarkerGraphSchema { - key: "latency", - graph_type: MarkerGraphType::Line, - color: Some(GraphColor::Green), - }], - } - } + const DESCRIPTION: Option<&'static str> = + Some("This is a test marker with a custom schema."); + + const GRAPHS: &'static [StaticSchemaMarkerGraph] = &[StaticSchemaMarkerGraph { + key: "latency", + graph_type: MarkerGraphType::Line, + color: Some(GraphColor::Green), + }]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("CustomName") diff --git a/samply/src/linux_shared/converter.rs b/samply/src/linux_shared/converter.rs index 063808c2..3f8a15da 100644 --- a/samply/src/linux_shared/converter.rs +++ b/samply/src/linux_shared/converter.rs @@ -8,8 +8,8 @@ use debugid::DebugId; use framehop::{ExplicitModuleSectionInfo, FrameAddress, Module, Unwinder}; use fxprof_processed_profile::{ CategoryColor, CategoryHandle, CategoryPairHandle, CpuDelta, LibraryHandle, LibraryInfo, - MarkerFieldFormat, MarkerFieldSchema, MarkerLocation, MarkerSchema, MarkerTiming, Profile, - ReferenceTimestamp, SamplingInterval, StaticSchemaMarker, StringHandle, SymbolTable, + MarkerFieldFlags, MarkerFieldFormat, MarkerTiming, Profile, ReferenceTimestamp, + SamplingInterval, StaticSchemaMarker, StaticSchemaMarkerField, StringHandle, SymbolTable, ThreadHandle, }; use linux_perf_data::linux_perf_event_reader::TaskWasPreempted; @@ -1902,23 +1902,13 @@ struct MmapMarker(StringHandle); impl StaticSchemaMarker for MmapMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "mmap"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.name}".into()), - tooltip_label: Some("{marker.name} - {marker.data.name}".into()), - table_label: Some("{marker.name} - {marker.data.name}".into()), - fields: vec![MarkerFieldSchema { - key: "name".into(), - label: "Details".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![], - graphs: vec![], - } - } + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "name", + label: "Details", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("mmap") diff --git a/samply/src/shared/jit_function_add_marker.rs b/samply/src/shared/jit_function_add_marker.rs index 2e56c424..95d4b27f 100644 --- a/samply/src/shared/jit_function_add_marker.rs +++ b/samply/src/shared/jit_function_add_marker.rs @@ -1,6 +1,6 @@ use fxprof_processed_profile::{ - CategoryHandle, MarkerFieldFormat, MarkerFieldSchema, MarkerLocation, MarkerSchema, - MarkerStaticField, Profile, StaticSchemaMarker, StringHandle, + CategoryHandle, MarkerFieldFlags, MarkerFieldFormat, Profile, StaticSchemaMarker, + StaticSchemaMarkerField, StringHandle, }; #[derive(Debug, Clone)] @@ -9,26 +9,19 @@ pub struct JitFunctionAddMarker(pub StringHandle); impl StaticSchemaMarker for JitFunctionAddMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "JitFunctionAdd"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.n}".into()), - tooltip_label: Some("{marker.data.n}".into()), - table_label: Some("{marker.data.n}".into()), - fields: vec![MarkerFieldSchema { - key: "n".into(), - label: "Function".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "Emitted when a JIT function is added to the process.".into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = + Some("Emitted when a JIT function is added to the process."); + + const CHART_LABEL: Option<&'static str> = Some("{marker.data.n}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.n}"); + const TABLE_LABEL: Option<&'static str> = Some("{marker.data.n}"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "n", + label: "Function", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("JitFunctionAdd") diff --git a/samply/src/shared/per_cpu.rs b/samply/src/shared/per_cpu.rs index aa975ee7..a3ef3145 100644 --- a/samply/src/shared/per_cpu.rs +++ b/samply/src/shared/per_cpu.rs @@ -1,6 +1,6 @@ use fxprof_processed_profile::{ - CategoryHandle, Frame, FrameFlags, FrameInfo, MarkerFieldFormat, MarkerFieldSchema, - MarkerLocation, MarkerSchema, MarkerTiming, ProcessHandle, Profile, StaticSchemaMarker, + CategoryHandle, Frame, FrameFlags, FrameInfo, MarkerFieldFlags, MarkerFieldFormat, + MarkerTiming, ProcessHandle, Profile, StaticSchemaMarker, StaticSchemaMarkerField, StringHandle, ThreadHandle, Timestamp, }; @@ -157,23 +157,16 @@ pub struct ThreadNameMarkerForCpuTrack(pub StringHandle, pub StringHandle); impl StaticSchemaMarker for ThreadNameMarkerForCpuTrack { const UNIQUE_MARKER_TYPE_NAME: &'static str = "ContextSwitch"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.thread}".into()), - tooltip_label: Some("{marker.data.thread}".into()), - table_label: Some("{marker.name} - {marker.data.thread}".into()), - fields: vec![MarkerFieldSchema { - key: "thread".into(), - label: "Thread".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![], - graphs: vec![], - } - } + const CHART_LABEL: Option<&'static str> = Some("{marker.data.thread}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.thread}"); + const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.thread}"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "thread", + label: "Thread", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, _profile: &mut Profile) -> StringHandle { self.0 @@ -202,33 +195,25 @@ pub struct OnCpuMarkerForThreadTrack { impl StaticSchemaMarker for OnCpuMarkerForThreadTrack { const UNIQUE_MARKER_TYPE_NAME: &'static str = "OnCpu"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.cpu}".into()), - tooltip_label: Some("{marker.data.cpu}".into()), - table_label: Some( - "{marker.name} - {marker.data.cpu}, switch-out reason: {marker.data.outwhy}".into(), - ), - fields: vec![ - MarkerFieldSchema { - key: "cpu".into(), - label: "CPU".into(), - format: MarkerFieldFormat::String, - searchable: true, - }, - MarkerFieldSchema { - key: "outwhy".into(), - label: "Switch-out reason".into(), - format: MarkerFieldFormat::String, - searchable: true, - }, - ], - static_fields: vec![], - graphs: vec![], - } - } + const CHART_LABEL: Option<&'static str> = Some("{marker.data.cpu}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.cpu}"); + const TABLE_LABEL: Option<&'static str> = + Some("{marker.name} - {marker.data.cpu}, switch-out reason: {marker.data.outwhy}"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[ + StaticSchemaMarkerField { + key: "cpu", + label: "CPU", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }, + StaticSchemaMarkerField { + key: "outwhy", + label: "Switch-out reason", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }, + ]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("Running on CPU") diff --git a/samply/src/shared/process_sample_data.rs b/samply/src/shared/process_sample_data.rs index 45419628..61d24d76 100644 --- a/samply/src/shared/process_sample_data.rs +++ b/samply/src/shared/process_sample_data.rs @@ -1,7 +1,7 @@ use fxprof_processed_profile::{ - CategoryHandle, CategoryPairHandle, LibMappings, MarkerFieldFormat, MarkerFieldSchema, - MarkerLocation, MarkerSchema, MarkerStaticField, MarkerTiming, Profile, StaticSchemaMarker, - StringHandle, ThreadHandle, Timestamp, + CategoryHandle, CategoryPairHandle, LibMappings, MarkerFieldFlags, MarkerFieldFormat, + MarkerTiming, Profile, StaticSchemaMarker, StaticSchemaMarkerField, StringHandle, ThreadHandle, + Timestamp, }; use super::lib_mappings::{LibMappingInfo, LibMappingOpQueue, LibMappingsHierarchy}; @@ -144,36 +144,28 @@ impl RssStatMarker { impl StaticSchemaMarker for RssStatMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "RSS Anon"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.totalBytes}".into()), - tooltip_label: Some("{marker.data.totalBytes}".into()), - table_label: Some( - "Total: {marker.data.totalBytes}, delta: {marker.data.deltaBytes}".into(), - ), - fields: vec![ - MarkerFieldSchema { - key: "totalBytes".into(), - label: "Total bytes".into(), - format: MarkerFieldFormat::Bytes, - searchable: true, - }, - MarkerFieldSchema { - key: "deltaBytes".into(), - label: "Delta".into(), - format: MarkerFieldFormat::Bytes, - searchable: true, - }, - ], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "Emitted when the kmem:rss_stat tracepoint is hit.".into(), - }], - graphs: vec![], - } - } + const CHART_LABEL: Option<&'static str> = Some("{marker.data.totalBytes}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.totalBytes}"); + const TABLE_LABEL: Option<&'static str> = + Some("Total: {marker.data.totalBytes}, delta: {marker.data.deltaBytes}"); + + const DESCRIPTION: Option<&'static str> = + Some("Emitted when the kmem:rss_stat tracepoint is hit."); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[ + StaticSchemaMarkerField { + key: "totalBytes", + label: "Total bytes", + format: MarkerFieldFormat::Bytes, + flags: MarkerFieldFlags::SEARCHABLE, + }, + StaticSchemaMarkerField { + key: "deltaBytes", + label: "Delta", + format: MarkerFieldFormat::Bytes, + flags: MarkerFieldFlags::SEARCHABLE, + }, + ]; fn name(&self, _profile: &mut Profile) -> StringHandle { self.name @@ -202,23 +194,10 @@ pub struct OtherEventMarker(pub StringHandle); impl StaticSchemaMarker for OtherEventMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "Other event"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: None, - tooltip_label: None, - table_label: None, - fields: vec![], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: - "Emitted for any records in a perf.data file which don't map to a known event." - .into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = + Some("Emitted for any records in a perf.data file which don't map to a known event."); + + const FIELDS: &'static [fxprof_processed_profile::StaticSchemaMarkerField] = &[]; fn name(&self, _profile: &mut Profile) -> StringHandle { self.0 @@ -243,26 +222,19 @@ pub struct UserTimingMarker(pub StringHandle); impl StaticSchemaMarker for UserTimingMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "UserTiming"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.name}".into()), - tooltip_label: Some("{marker.data.name}".into()), - table_label: Some("{marker.data.name}".into()), - fields: vec![MarkerFieldSchema { - key: "name".into(), - label: "Name".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "Emitted for performance.mark and performance.measure.".into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = + Some("Emitted for performance.mark and performance.measure."); + + const CHART_LABEL: Option<&'static str> = Some("{marker.data.name}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.name}"); + const TABLE_LABEL: Option<&'static str> = Some("{marker.data.name}"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "name", + label: "Name", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("UserTiming") @@ -286,21 +258,10 @@ pub struct SchedSwitchMarkerOnCpuTrack; impl StaticSchemaMarker for SchedSwitchMarkerOnCpuTrack { const UNIQUE_MARKER_TYPE_NAME: &'static str = "sched_switch"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: None, - tooltip_label: None, - table_label: None, - fields: vec![], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "Emitted just before a running thread gets moved off-cpu.".into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = + Some("Emitted just before a running thread gets moved off-cpu."); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("sched_switch") @@ -327,26 +288,15 @@ pub struct SchedSwitchMarkerOnThreadTrack { impl StaticSchemaMarker for SchedSwitchMarkerOnThreadTrack { const UNIQUE_MARKER_TYPE_NAME: &'static str = "sched_switch"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: None, - tooltip_label: None, - table_label: None, - fields: vec![MarkerFieldSchema { - key: "cpu".into(), - label: "cpu".into(), - format: MarkerFieldFormat::Integer, - searchable: true, - }], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "Emitted just before a running thread gets moved off-cpu.".into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = + Some("Emitted just before a running thread gets moved off-cpu."); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "cpu", + label: "cpu", + format: MarkerFieldFormat::Integer, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("sched_switch") @@ -371,26 +321,19 @@ pub struct SimpleMarker(pub StringHandle); impl StaticSchemaMarker for SimpleMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "SimpleMarker"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.name}".into()), - tooltip_label: Some("{marker.data.name}".into()), - table_label: Some("{marker.data.name}".into()), - fields: vec![MarkerFieldSchema { - key: "name".into(), - label: "Name".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "Emitted for marker spans in a markers text file.".into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = + Some("Emitted for marker spans in a markers text file."); + + const CHART_LABEL: Option<&'static str> = Some("{marker.data.name}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.name}"); + const TABLE_LABEL: Option<&'static str> = Some("{marker.data.name}"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "name", + label: "Name", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("SimpleMarker") diff --git a/samply/src/windows/coreclr.rs b/samply/src/windows/coreclr.rs index 7ae7a88c..2d9b341e 100644 --- a/samply/src/windows/coreclr.rs +++ b/samply/src/windows/coreclr.rs @@ -212,40 +212,32 @@ pub struct CoreClrGcAllocMarker(StringHandle, f64, CategoryHandle); impl StaticSchemaMarker for CoreClrGcAllocMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "GC Alloc"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![ - MarkerLocation::MarkerChart, - MarkerLocation::MarkerTable, - MarkerLocation::TimelineMemory, - ], - chart_label: Some("GC Alloc".into()), - tooltip_label: Some( - "GC Alloc: {marker.data.clrtype} ({marker.data.size} bytes)".into(), - ), - table_label: Some("GC Alloc".into()), - fields: vec![ - MarkerFieldSchema { - key: "clrtype".into(), - label: "CLR Type".into(), - format: MarkerFieldFormat::String, - searchable: true, - }, - MarkerFieldSchema { - key: "size".into(), - label: "Size".into(), - format: MarkerFieldFormat::Bytes, - searchable: false, - }, - ], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "GC Allocation.".into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = Some("GC Allocation."); + + const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART + .union(MarkerLocations::MARKER_TABLE) + .union(MarkerLocations::TIMELINE_MEMORY); + + const CHART_LABEL: Option<&'static str> = Some("GC Alloc"); + const TOOLTIP_LABEL: Option<&'static str> = + Some("GC Alloc: {marker.data.clrtype} ({marker.data.size} bytes)"); + const TABLE_LABEL: Option<&'static str> = + Some("GC Alloc: {marker.data.clrtype} ({marker.data.size} bytes)"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[ + StaticSchemaMarkerField { + key: "clrtype", + label: "CLR Type", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }, + StaticSchemaMarkerField { + key: "size", + label: "Size", + format: MarkerFieldFormat::Bytes, + flags: MarkerFieldFlags::empty(), + }, + ]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("GC Alloc") @@ -270,30 +262,22 @@ pub struct CoreClrGcEventMarker(StringHandle, StringHandle, CategoryHandle); impl StaticSchemaMarker for CoreClrGcEventMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "GC Event"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![ - MarkerLocation::MarkerChart, - MarkerLocation::MarkerTable, - MarkerLocation::TimelineMemory, - ], - chart_label: Some("{marker.data.event}".into()), - tooltip_label: Some("{marker.data.event}".into()), - table_label: Some("{marker.data.event}".into()), - fields: vec![MarkerFieldSchema { - key: "event".into(), - label: "Event".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "Generic GC Event.".into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = Some("Generic GC Event."); + + const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART + .union(MarkerLocations::MARKER_TABLE) + .union(MarkerLocations::TIMELINE_MEMORY); + + const CHART_LABEL: Option<&'static str> = Some("{marker.data.event}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.event}"); + const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.event}"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "event", + label: "Event", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, _profile: &mut Profile) -> StringHandle { self.0 @@ -758,26 +742,18 @@ pub struct OtherClrMarker(StringHandle, StringHandle); impl StaticSchemaMarker for OtherClrMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "OtherClrMarker"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.name}".into()), - tooltip_label: Some("{marker.data.name}".into()), - table_label: Some("{marker.data.name}".into()), - fields: vec![MarkerFieldSchema { - key: "name".into(), - label: "Name".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![MarkerStaticField { - label: "Description".into(), - value: "CoreCLR marker of unknown type.".into(), - }], - graphs: vec![], - } - } + const DESCRIPTION: Option<&'static str> = Some("CoreCLR marker of unknown type."); + + const CHART_LABEL: Option<&'static str> = Some("{marker.data.name}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.name}"); + const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.name}"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "name", + label: "Name", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, _profile: &mut Profile) -> StringHandle { self.0 diff --git a/samply/src/windows/profile_context.rs b/samply/src/windows/profile_context.rs index a20cd4fd..1dd668e7 100644 --- a/samply/src/windows/profile_context.rs +++ b/samply/src/windows/profile_context.rs @@ -4,9 +4,9 @@ use std::path::Path; use debugid::DebugId; use fxprof_processed_profile::{ CategoryColor, CategoryHandle, CounterHandle, CpuDelta, Frame, FrameFlags, FrameInfo, - LibraryHandle, LibraryInfo, Marker, MarkerFieldFormat, MarkerFieldSchema, MarkerHandle, - MarkerLocation, MarkerSchema, MarkerTiming, ProcessHandle, Profile, SamplingInterval, - StaticSchemaMarker, StringHandle, ThreadHandle, Timestamp, + LibraryHandle, LibraryInfo, Marker, MarkerFieldFlags, MarkerFieldFormat, MarkerHandle, + MarkerLocations, MarkerTiming, ProcessHandle, Profile, SamplingInterval, StaticSchemaMarker, + StaticSchemaMarkerField, StringHandle, ThreadHandle, Timestamp, }; use shlex::Shlex; use wholesym::PeCodeId; @@ -1563,22 +1563,11 @@ impl ProfileContext { impl StaticSchemaMarker for VSyncMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "Vsync"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![ - MarkerLocation::MarkerChart, - MarkerLocation::MarkerTable, - MarkerLocation::TimelineOverview, - ], - chart_label: Some("{marker.data.name}".into()), - tooltip_label: None, - table_label: Some("{marker.name}".into()), - fields: vec![], - static_fields: vec![], - graphs: vec![], - } - } + const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART + .union(MarkerLocations::MARKER_TABLE) + .union(MarkerLocations::TIMELINE_OVERVIEW); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[]; fn name(&self, profile: &mut Profile) -> StringHandle { profile.intern_string("Vsync") @@ -2243,23 +2232,16 @@ pub struct FreeformMarker(StringHandle, StringHandle, CategoryHandle); impl StaticSchemaMarker for FreeformMarker { const UNIQUE_MARKER_TYPE_NAME: &'static str = "FreeformMarker"; - fn schema() -> MarkerSchema { - MarkerSchema { - type_name: Self::UNIQUE_MARKER_TYPE_NAME.into(), - locations: vec![MarkerLocation::MarkerChart, MarkerLocation::MarkerTable], - chart_label: Some("{marker.data.values}".into()), - tooltip_label: Some("{marker.name} - {marker.data.values}".into()), - table_label: Some("{marker.data.values}".into()), - fields: vec![MarkerFieldSchema { - key: "values".into(), - label: "Values".into(), - format: MarkerFieldFormat::String, - searchable: true, - }], - static_fields: vec![], - graphs: vec![], - } - } + const CHART_LABEL: Option<&'static str> = Some("{marker.data.values}"); + const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.values}"); + const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.values"); + + const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField { + key: "values", + label: "Values", + format: MarkerFieldFormat::String, + flags: MarkerFieldFlags::SEARCHABLE, + }]; fn name(&self, _profile: &mut Profile) -> StringHandle { self.0