Rust 代码插桩¶
有多种方法可以使用分析器与 Rust 进行交互。原生栈采样已经包含了 Rust 栈帧,无需特殊处理。存在“原生栈”分析器功能(通过 about:profiling),它可以启用原生代码的栈回溯。对于每个分析器预设,这很可能已经启用。
除此之外,还有一个分析器 Rust API 用于对 Rust 代码进行插桩并向配置文件数据添加更多信息。主要有三个功能可以使用
将 Rust 线程注册到分析器,以便分析器可以记录这些线程。
添加栈帧标签以注释和分类栈的一部分。
添加标记以专门标记时间点或持续时间。这有助于理解代码的特定部分,或记录通常不会出现在样本中的事件。
作为依赖项包含的板条箱¶
分析器 Rust API 位于 gecko-profiler
板条箱中。在使用以下功能之前,需要将其包含在项目依赖项中。
为了能够包含它,需要在项目的 Cargo.toml
文件中添加一个新的依赖项条目,如下所示
[dependencies]
gecko-profiler = { path = "../../tools/profiler/rust-api" }
请注意,根据项目在 mozilla-central 中的位置,需要更新相对路径。
更新 Cargo.toml
后,需要执行 cargo update -p gkrust-shared
来更新 Cargo.lock
文件。
注册线程¶
为了能够在配置文件数据中看到线程,需要将它们注册到分析器中。此外,当它们退出时需要取消注册。为线程提供唯一名称非常重要,以便可以轻松地对其进行过滤。
注册和取消注册线程非常简单
// Register it with a given name.
gecko_profiler::register_thread("Thread Name");
// After doing some work, and right before exiting the thread, unregister it.
gecko_profiler::unregister_thread();
例如,以下是如何注册和取消注册一个简单的线程
let thread_name = "New Thread";
std::thread::Builder::new()
.name(thread_name.into())
.spawn(move || {
gecko_profiler::register_thread(thread_name);
// DO SOME WORK
gecko_profiler::unregister_thread();
})
.unwrap();
或使用线程池
let worker = rayon::ThreadPoolBuilder::new()
.thread_name(move |idx| format!("Worker#{}", idx))
.start_handler(move |idx| {
gecko_profiler::register_thread(&format!("Worker#{}", idx));
})
.exit_handler(|_idx| {
gecko_profiler::unregister_thread();
})
.build();
注意
仅注册线程不会使其出现在配置文件数据中。此外,需要将其添加到 about:profiling 中的“线程”过滤器中。此过滤器输入是一个逗号分隔的列表。它匹配部分名称并支持通配符 *
。
添加栈帧标签¶
栈帧标签用于使用类别注释调用栈的一部分。该类别将显示在 Firefox 分析器分析页面的各个位置,例如时间线、调用树选项卡、火焰图选项卡等。
gecko_profiler_label!
宏用于添加一个新的标签帧。添加的标签帧将存在于此宏的调用与当前作用域结束之间。
添加栈帧标签
// Marking the stack as "Layout" category, no subcategory provided.
gecko_profiler_label!(Layout);
// Marking the stack as "JavaScript" category and "Parsing" subcategory.
gecko_profiler_label!(JavaScript, Parsing);
// Or the entire function scope can be marked with a procedural macro. This is
// essentially a syntactical sugar and it expands into a function with a
// gecko_profiler_label! call at the very start:
#[gecko_profiler_fn_label(DOM)]
fn foo(bar: u32) -> u32 {
bar
}
请参阅 profiling_categories.yaml 文件中所有分析类别的列表。
添加标记¶
标记是 Firefox 代码添加到配置文件中的任意数据包,通常用于指示某个时间点或时间间隔内发生的某些重要事件。
每个标记都有一个名称、一个类别、一些常见的可选信息(时间、回溯等)以及一个可选的特定类型有效负载(包含与该类型相关的任意数据)。
注意
本指南深入解释了 Rust 标记。要了解有关如何在 C++、JavaScript 或 JVM 中添加标记的更多信息,请查看其在 标记 或 JavaScript 代码插桩、Android 代码插桩 中的文档。
示例¶
简短示例,详细信息如下。
// Record a simple marker with the category of Graphics, DisplayListBuilding.
gecko_profiler::add_untyped_marker(
// Name of the marker as a string.
"Marker Name",
// Category with an optional sub-category.
gecko_profiler_category!(Graphics, DisplayListBuilding),
// MarkerOptions that keeps options like marker timing and marker stack.
// It will be a point in type by default.
Default::default(),
);
// Create a marker with some additional text information.
let info = "info about this marker";
gecko_profiler::add_text_marker(
// Name of the marker as a string.
"Marker Name",
// Category with an optional sub-category.
gecko_profiler_category!(DOM),
// MarkerOptions that keeps options like marker timing and marker stack.
MarkerOptions {
timing: MarkerTiming::instant_now(),
..Default::default()
},
// Additional information as a string.
info,
);
// Record a custom marker of type `ExampleNumberMarker` (see definition below).
gecko_profiler::add_marker(
// Name of the marker as a string.
"Marker Name",
// Category with an optional sub-category.
gecko_profiler_category!(Graphics, DisplayListBuilding),
// MarkerOptions that keeps options like marker timing and marker stack.
Default::default(),
// Marker payload.
ExampleNumberMarker { number: 5 },
);
....
// Marker type definition. It needs to derive Serialize, Deserialize.
#[derive(Serialize, Deserialize, Debug)]
pub struct ExampleNumberMarker {
number: i32,
}
// Marker payload needs to implement the ProfilerMarker trait.
impl gecko_profiler::ProfilerMarker for ExampleNumberMarker {
// Unique marker type name.
fn marker_type_name() -> &'static str {
"example number"
}
// Data specific to this marker type, serialized to JSON for profiler.firefox.com.
fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) {
json_writer.int_property("number", self.number.into());
}
// Where and how to display the marker and its data.
fn marker_type_display() -> gecko_profiler::MarkerSchema {
use gecko_profiler::marker::schema::*;
let mut schema = MarkerSchema::new(&[Location::MarkerChart]);
schema.set_chart_label("Name: {marker.name}");
schema.add_key_label_format("number", "Number", Format::Integer);
schema
}
}
无类型标记¶
无类型标记除了常见标记数据外不携带任何信息:名称、类别、选项。
gecko_profiler::add_untyped_marker(
// Name of the marker as a string.
"Marker Name",
// Category with an optional sub-category.
gecko_profiler_category!(Graphics, DisplayListBuilding),
// MarkerOptions that keeps options like marker timing and marker stack.
MarkerOptions {
timing: MarkerTiming::instant_now(),
..Default::default()
},
);
- 标记名称
第一个参数是此标记的名称。这将显示在标记显示的大多数地方。它可以是字面量字符串或任何动态字符串。
- MarkerOptions
请参阅下面的选项。如果没有任何参数,则可以使用
Default::default()
省略它。一些选项也可以省略,MarkerOptions {<options>, ..Default::default()}
,并使用以下一个或多个选项类型- MarkerTiming
这指定了一个时间点或时间间隔。如果未指定,则默认为当前时间点。否则使用
MarkerTiming::instant_at(ProfilerTime)
或MarkerTiming::interval(pt1, pt2)
;时间戳通常使用ProfilerTime::Now()
捕获。也可以仅记录间隔的开始或结束,成对的开始/结束标记将根据其名称进行匹配。
- MarkerStack
默认情况下,标记不会记录“栈”(或“回溯”)。为了以最有效的方式在此处记录栈,请指定
MarkerStack::Full
。为了捕获没有原生帧的栈以减少开销,请指定MarkerStack::NonNative
。
注意:目前,并非所有 C++ 标记选项都存在于 Rust 端。它们将在将来添加。
文本标记¶
文本标记非常常见,除了标记名称之外,它们还携带一个额外的文本作为第四个参数。使用以下宏
let info = "info about this marker";
gecko_profiler::add_text_marker(
// Name of the marker as a string.
"Marker Name",
// Category with an optional sub-category.
gecko_profiler_category!(DOM),
// MarkerOptions that keeps options like marker timing and marker stack.
MarkerOptions {
stack: MarkerStack::Full,
..Default::default()
},
// Additional information as a string.
info,
);
尽管它很有用,但使用代价高昂的 format!
操作来生成复杂的文本会带来各种问题。它可能会泄露潜在的敏感信息,例如在配置文件共享步骤中的 URL。profiler.firefox.com 无法以编程方式访问这些信息。它不会获得内置标记架构的格式化优势。请考虑使用自定义标记类型来分离和更好地呈现数据。
其他类型标记¶
从 Rust 代码中,可以像这样记录某个类型 YourMarker
的标记(类型定义的详细信息如下)
gecko_profiler::add_marker(
// Name of the marker as a string.
"Marker Name",
// Category with an optional sub-category.
gecko_profiler_category!(JavaScript),
// MarkerOptions that keeps options like marker timing and marker stack.
Default::default(),
// Marker payload.
YourMarker { number: 5, text: "some string".to_string() },
);
在前面三个常见参数(如 gecko_profiler::add_untyped_marker
中)之后,有一个标记有效负载结构,需要对其进行定义。让我们看看如何定义它。
如何定义新的标记类型¶
每个标记类型必须且只能定义一次。定义是一个 Rust struct
,它是在 Rust 中记录该类型的标记时构造的。每个标记结构都包含使其在 profiler.firefox.com 上显示所需的数据。按照惯例,建议使用后缀“Marker”来更好地区分源代码中非分析器实体。
每个标记有效负载必须派生 serde::Serialize
和 serde::Deserialize
。如果项目没有它,它们也会从 gecko-profiler
板条箱中导出。每个标记有效负载应将其数据作为其字段包含,如下所示
#[derive(Serialize, Deserialize, Debug)]
pub struct YourMarker {
number: i32,
text: String,
}
每个标记结构还必须实现 ProfilerMarker 特性。
ProfilerMarker
特性¶
所有标记类型都必须实现 ProfilerMarker 特性。其方法类似于 C++ 对应方法,请参考 C++ 标记指南以了解更多信息。它包含三个需要实现的方法
marker_type_name() -> &'static str
:标记类型必须具有唯一的名称,它用于跟踪分析器存储中标记的类型,并在 profiler.firefox.com 上唯一标识它们。(它不需要与结构的名称相同。)
例如:
fn marker_type_name() -> &'static str { "your marker type" }
stream_json_marker_data(&self, json_writer: &mut JSONWriter)
任何类型的标记都有一些共同的数据:名称、类别、选项(如时间等),如前所述。
此外,某个标记类型可能携带零个或多个任意信息,并且对于该类型的全部标记来说,这些信息始终相同。
这些信息定义在特殊的静态成员函数
stream_json_marker_data
中。它是一个成员方法,并接受
&mut JSONWriter
作为参数,它将用于以 JSON 格式流式传输数据,以便稍后由 profiler.firefox.com 读取。请参阅 JSONWriter 对象及其方法。例如:
fn stream_json_marker_data(&self, json_writer: &mut JSONWriter) { json_writer.int_property("number", self.number.into()); json_writer.string_property("text", &self.text); }
marker_type_display() -> schema::MarkerSchema
现在,已经定义了如何流式传输类型特定数据(从 Firefox 到 profiler.firefox.com),需要描述此数据将在 profiler.firefox.com 上显示的位置和方式。
静态成员函数
marker_type_display
返回一个不透明的MarkerSchema
对象,该对象将转发到 profiler.firefox.com。请参阅 MarkerSchema::Location 枚举以获取完整列表。另请参阅 MarkerSchema 结构以获取其可能的方法。
例如:
fn marker_type_display() -> schema::MarkerSchema { // Import MarkerSchema related types for easier use. use crate::marker::schema::*; // Create a MarkerSchema struct with a list of locations provided. // One or more constructor arguments determine where this marker will be displayed in // the profiler.firefox.com UI. let mut schema = MarkerSchema::new(&[Location::MarkerChart]); // Some labels can optionally be specified, to display certain information in different // locations: set_chart_label, set_tooltip_label, and set_table_label``; or // set_all_labels to define all of them the same way. schema.set_all_labels("{marker.name} - {marker.data.number}); // Next, define the main display of marker data, which will appear in the Marker Chart // tooltips and the Marker Table sidebar. schema.add_key_label_format("number", "Number", Format::Number); schema.add_key_label_format("text", "Text", Format::String); schema.add_static_label_value("Help", "This is my own marker type"); // Lastly, return the created schema. schema }
请注意,
set_all_labels
中的字符串可能引用花括号内的标记数据{marker.name}
:标记名称。{marker.data.X}
:从stream_json_marker_data
以属性名“X”流式传输的特定类型数据。