mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
Upstream the perfetto crate.
This is a step towards upstreaming Perfetto support (adding the Perfetto Rust wrappers). These wrappers won't compile until we have the Perfetto source & libraries in place. BUG=b:277138899 TEST=n/a Change-Id: Ic9cc86285383b114cecf7cfe574b3e2511d7afeb Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4428221 Reviewed-by: Steven Moreland <smoreland@google.com> Reviewed-by: Morg <morg@chromium.org> Reviewed-by: Dennis Kempin <denniskempin@google.com> Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org> Commit-Queue: Noah Gold <nkgold@google.com>
This commit is contained in:
parent
c6a26e7b41
commit
f7095ff46a
7 changed files with 1164 additions and 0 deletions
|
@ -106,6 +106,7 @@ exclude = [
|
|||
"tools/audio_streams_conformance_test",
|
||||
"tools/examples/baremetal",
|
||||
"tube_transporter",
|
||||
"perfetto",
|
||||
"win_audio",
|
||||
"win_util",
|
||||
]
|
||||
|
|
32
perfetto/Cargo.toml
Normal file
32
perfetto/Cargo.toml
Normal file
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "perfetto"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
# Bypassing default features requires specifying either openssl or
|
||||
# pure-rust-hashes.
|
||||
default = ["openssl"]
|
||||
|
||||
# Use a pure Rust crate for hashing instead of openssl.
|
||||
pure-rust-hashes = ["sha2"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "*"
|
||||
cfg-if = "1.0.0"
|
||||
cros_tracing_types = { path = "../cros_tracing_types" }
|
||||
data_model = { path = "../common/data_model" }
|
||||
protobuf = "3.2"
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
sha2 = { version = "*", optional = true }
|
||||
openssl = { version = "*", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies]
|
||||
sync = { path = "../common/sync" }
|
||||
once_cell = "1.7"
|
||||
base = { path = "../base" }
|
||||
cros_tracing_types = { path = "../cros_tracing_types" }
|
||||
|
||||
[build-dependencies]
|
||||
proto_build_tools = { path = "../proto_build_tools" }
|
21
perfetto/README.md
Normal file
21
perfetto/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Perfetto Rust wrapper
|
||||
|
||||
The following instructions are based on `tools/impl/bindgen-common.sh`
|
||||
|
||||
When the Perfetto C API is updated, in order to regenerate the bindings:
|
||||
|
||||
1. [Download the bindgen cmdline tool.](https://rust-lang.github.io/rust-bindgen/command-line-usage.html)
|
||||
1. Run the bindgen command. If you are in crosvm:
|
||||
```
|
||||
$ bindgen third_party/perfetto/include/perfetto/tracing/ctrace.h --disable-header-comment --no-layout-tests --no-doc-comments --with-derive-default --size_t-is-usize -o ./perfetto/src/bindings.rs
|
||||
```
|
||||
1. Add the following to the top of the new bindings.rs file:
|
||||
```
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
```
|
||||
1. Finally, add a copyright header to the bindings.
|
62
perfetto/build.rs
Normal file
62
perfetto/build.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
#[cfg(windows)]
|
||||
main_windows();
|
||||
|
||||
#[cfg(unix)]
|
||||
main_unix();
|
||||
|
||||
// TODO: enable once Perfetto is in third_party/perfetto.
|
||||
/*
|
||||
let proto_files = vec![proto_path(&["config", "perfetto_config.proto"])];
|
||||
let mut out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR env does not exist."));
|
||||
out_dir.push("perfetto_protos");
|
||||
proto_build_tools::build_protos(&out_dir, proto_files.as_slice());
|
||||
*/
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn main_windows() {
|
||||
// TODO: enable once we have Perfetto libraries available on Windows,
|
||||
// download them with prebuilts::download_prebuilts.
|
||||
//
|
||||
// Ideally paths will be identical in the long term and we
|
||||
// can have a single version of this code.
|
||||
/*
|
||||
println!("cargo:rustc-link-lib=dylib=cperfetto");
|
||||
#[cfg(debug_assertions)]
|
||||
println!("cargo:rustc-link-search=..\\..\\libs\\debug");
|
||||
#[cfg(all(windows, not(debug_assertions)))]
|
||||
println!("cargo:rustc-link-search=..\\..\\libs\\release");
|
||||
*/
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn main_unix() {
|
||||
// TODO: enable once we have Perfetto libraries available on unix. We may
|
||||
// want to use a prebuilt here too, in which case this would be identical
|
||||
// to the Windows version above. The paths will need to be adjusted to
|
||||
// wherever we make the Perfetto binary available.
|
||||
/*
|
||||
println!("cargo:rustc-link-lib=dylib=cperfetto");
|
||||
#[cfg(debug_assertions)]
|
||||
println!("cargo:rustc-link-search=../../libs/debug");
|
||||
#[cfg(not(debug_assertions))]
|
||||
println!("cargo:rustc-link-search=../../libs/release");
|
||||
*/
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn proto_path(path: &[&str]) -> PathBuf {
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let mut full_path = manifest_dir;
|
||||
full_path.extend(["..", "third_party", "perfetto", "protos", "perfetto"]);
|
||||
full_path.extend(path);
|
||||
full_path
|
||||
}
|
213
perfetto/src/bindings.rs
Normal file
213
perfetto/src/bindings.rs
Normal file
|
@ -0,0 +1,213 @@
|
|||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub const __bool_true_false_are_defined: u32 = 1;
|
||||
pub const true_: u32 = 1;
|
||||
pub const false_: u32 = 0;
|
||||
pub const _VCRT_COMPILER_PREPROCESSOR: u32 = 1;
|
||||
pub const _SAL_VERSION: u32 = 20;
|
||||
pub const __SAL_H_VERSION: u32 = 180000000;
|
||||
pub const _USE_DECLSPECS_FOR_SAL: u32 = 0;
|
||||
pub const _USE_ATTRIBUTES_FOR_SAL: u32 = 0;
|
||||
pub const _CRT_PACKING: u32 = 8;
|
||||
pub const _HAS_EXCEPTIONS: u32 = 1;
|
||||
pub const _STL_LANG: u32 = 0;
|
||||
pub const _HAS_CXX17: u32 = 0;
|
||||
pub const _HAS_CXX20: u32 = 0;
|
||||
pub const _HAS_NODISCARD: u32 = 0;
|
||||
pub const WCHAR_MIN: u32 = 0;
|
||||
pub const WCHAR_MAX: u32 = 65535;
|
||||
pub const WINT_MIN: u32 = 0;
|
||||
pub const WINT_MAX: u32 = 65535;
|
||||
pub const CTRACE_API_VERSION: u32 = 1;
|
||||
pub const _CTRACE_TYPE_SLICE_BEGIN: u32 = 1;
|
||||
pub const _CTRACE_TYPE_SLICE_END: u32 = 2;
|
||||
pub type wchar_t = ::std::os::raw::c_ushort;
|
||||
pub type max_align_t = f64;
|
||||
pub type va_list = *mut ::std::os::raw::c_char;
|
||||
extern "C" {
|
||||
pub fn __va_start(arg1: *mut *mut ::std::os::raw::c_char, ...);
|
||||
}
|
||||
pub type __vcrt_bool = bool;
|
||||
extern "C" {
|
||||
pub fn __security_init_cookie();
|
||||
}
|
||||
extern "C" {
|
||||
pub fn __security_check_cookie(_StackCookie: usize);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn __report_gsfailure(_StackCookie: usize) -> !;
|
||||
}
|
||||
extern "C" {
|
||||
pub static mut __security_cookie: usize;
|
||||
}
|
||||
pub type int_least8_t = ::std::os::raw::c_schar;
|
||||
pub type int_least16_t = ::std::os::raw::c_short;
|
||||
pub type int_least32_t = ::std::os::raw::c_int;
|
||||
pub type int_least64_t = ::std::os::raw::c_longlong;
|
||||
pub type uint_least8_t = ::std::os::raw::c_uchar;
|
||||
pub type uint_least16_t = ::std::os::raw::c_ushort;
|
||||
pub type uint_least32_t = ::std::os::raw::c_uint;
|
||||
pub type uint_least64_t = ::std::os::raw::c_ulonglong;
|
||||
pub type int_fast8_t = ::std::os::raw::c_schar;
|
||||
pub type int_fast16_t = ::std::os::raw::c_int;
|
||||
pub type int_fast32_t = ::std::os::raw::c_int;
|
||||
pub type int_fast64_t = ::std::os::raw::c_longlong;
|
||||
pub type uint_fast8_t = ::std::os::raw::c_uchar;
|
||||
pub type uint_fast16_t = ::std::os::raw::c_uint;
|
||||
pub type uint_fast32_t = ::std::os::raw::c_uint;
|
||||
pub type uint_fast64_t = ::std::os::raw::c_ulonglong;
|
||||
pub type intmax_t = ::std::os::raw::c_longlong;
|
||||
pub type uintmax_t = ::std::os::raw::c_ulonglong;
|
||||
pub const BackendType_CTRACE_UNSPECIFIED_BACKEND: BackendType = 0;
|
||||
pub const BackendType_CTRACE_IN_PROCESS_BACKEND: BackendType = 1;
|
||||
pub const BackendType_CTRACE_SYSTEM_BACKEND: BackendType = 2;
|
||||
pub type BackendType = ::std::os::raw::c_int;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ctrace_category {
|
||||
pub client_index: u64,
|
||||
pub instances_callback:
|
||||
::std::option::Option<unsafe extern "C" fn(instances: u32, client_index: u64)>,
|
||||
pub name: *const ::std::os::raw::c_char,
|
||||
pub description: *const ::std::os::raw::c_char,
|
||||
pub tags: [*const ::std::os::raw::c_char; 4usize],
|
||||
}
|
||||
impl Default for ctrace_category {
|
||||
fn default() -> Self {
|
||||
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
|
||||
unsafe {
|
||||
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
|
||||
s.assume_init()
|
||||
}
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct ctrace_clock {
|
||||
pub clock_id: u32,
|
||||
pub timestamp: u64,
|
||||
pub is_incremental: bool,
|
||||
pub unit_multiplier_ns: u64,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct ctrace_clock_snapshot {
|
||||
pub clocks: [ctrace_clock; 2usize],
|
||||
}
|
||||
extern "C" {
|
||||
pub fn ctrace_register_categories(c_cats: *const *const ctrace_category, max: u64) -> u64;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn ctrace_add_clock_snapshot(snapshot: *mut ctrace_clock_snapshot);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct ctrace_init_args {
|
||||
pub api_version: u32,
|
||||
pub backend: u32,
|
||||
pub shmem_size_hint_kb: u32,
|
||||
pub shmem_page_size_hint_kb: u32,
|
||||
pub shmem_batch_commits_duration_ms: u32,
|
||||
}
|
||||
extern "C" {
|
||||
pub fn ctrace_init(arg1: *const ctrace_init_args);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn trace_event_begin(
|
||||
category_index: u64,
|
||||
instances: u32,
|
||||
name: *const ::std::os::raw::c_char,
|
||||
);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn trace_event_end(category_index: u64, instances: u32);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn trace_event_instant(
|
||||
category_index: u64,
|
||||
instances: u32,
|
||||
name: *const ::std::os::raw::c_char,
|
||||
);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn trace_counter(
|
||||
category_index: u64,
|
||||
instances: u32,
|
||||
track: *const ::std::os::raw::c_char,
|
||||
value: i64,
|
||||
);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn trace_create_async(
|
||||
category_index: u64,
|
||||
instances: u32,
|
||||
name: *const ::std::os::raw::c_char,
|
||||
) -> u64;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn trace_begin_async(
|
||||
category_index: u64,
|
||||
instances: u32,
|
||||
name: *const ::std::os::raw::c_char,
|
||||
terminating_flow_id: u64,
|
||||
);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn trace_pause_async(category_index: u64, instances: u32) -> u64;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn trace_end_async(category_index: u64, instances: u32);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct ctrace_trace_config {
|
||||
pub duration_ms: u32,
|
||||
pub buffer_size_kb: u32,
|
||||
}
|
||||
pub type ctrace_trace_session_handle = *mut ::std::os::raw::c_void;
|
||||
extern "C" {
|
||||
pub fn ctrace_trace_start(arg1: *const ctrace_trace_config) -> ctrace_trace_session_handle;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn ctrace_trace_start_from_config_proto(
|
||||
arg1: *mut ::std::os::raw::c_void,
|
||||
arg2: u64,
|
||||
) -> ctrace_trace_session_handle;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn ctrace_trace_stop(
|
||||
arg1: ctrace_trace_session_handle,
|
||||
arg2: *const ::std::os::raw::c_char,
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ctrace_trace_buffer {
|
||||
pub std_vec: *mut ::std::os::raw::c_void,
|
||||
pub data: *mut ::std::os::raw::c_void,
|
||||
pub size: u64,
|
||||
}
|
||||
impl Default for ctrace_trace_buffer {
|
||||
fn default() -> Self {
|
||||
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
|
||||
unsafe {
|
||||
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
|
||||
s.assume_init()
|
||||
}
|
||||
}
|
||||
}
|
||||
extern "C" {
|
||||
pub fn ctrace_free_trace_buffer(arg1: *mut ctrace_trace_buffer);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn ctrace_trace_stop_to_buffer(arg1: ctrace_trace_session_handle) -> ctrace_trace_buffer;
|
||||
}
|
28
perfetto/src/hashes.rs
Normal file
28
perfetto/src/hashes.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Wraps various methods for calculating hashes (sha256).
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "pure-rust-hashes")] {
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
|
||||
pub fn sha256(bytes: [u8]) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(bytes);
|
||||
trace_hash.finalize()[0..32].try_into().unwrap()
|
||||
}
|
||||
} else if #[cfg(feature = "openssl")] {
|
||||
use openssl::sha::sha256;
|
||||
|
||||
pub fn sha256(bytes: [u8]) -> [u8; 32] {
|
||||
// We don't just re-export the library. This way, if openssl's Rust
|
||||
// interface changes, we will get an obvious compile error here.
|
||||
sha256(bytes)
|
||||
}
|
||||
} else {
|
||||
compile_error!("Either openssl or pure-rust-hashes must be selected.");
|
||||
}
|
||||
}
|
807
perfetto/src/lib.rs
Normal file
807
perfetto/src/lib.rs
Normal file
|
@ -0,0 +1,807 @@
|
|||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
mod bindings;
|
||||
mod hashes;
|
||||
|
||||
pub mod protos {
|
||||
include!(concat!(env!("OUT_DIR"), "/perfetto_protos/generated.rs"));
|
||||
}
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::c_void;
|
||||
use std::path::Path;
|
||||
use std::slice;
|
||||
use std::time::Duration;
|
||||
|
||||
pub use bindings::*;
|
||||
pub use cros_tracing_types::static_strings::StaticString;
|
||||
use cros_tracing_types::TraceDuration;
|
||||
use data_model::DataInit;
|
||||
use protobuf::Message;
|
||||
|
||||
use protos::perfetto_config::trace_config::BufferConfig;
|
||||
use protos::perfetto_config::trace_config::DataSource;
|
||||
use protos::perfetto_config::trace_config::IncrementalStateConfig;
|
||||
use protos::perfetto_config::DataSourceConfig;
|
||||
use protos::perfetto_config::TraceConfig;
|
||||
use protos::perfetto_config::TrackEventConfig;
|
||||
|
||||
/// Randomly generated GUID to help locate the AOT header.
|
||||
const HEADER_MAGIC: &[u8; 16] = b"\x8d\x10\xa3\xee\x79\x1f\x47\x25\xb2\xb8\xb8\x9f\x85\xe7\xd6\x7c";
|
||||
|
||||
/// The optional header written ahead of the trace data.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct TraceHeader {
|
||||
magic: [u8; 16],
|
||||
data_size: u64,
|
||||
data_checksum_sha256: [u8; 32],
|
||||
}
|
||||
|
||||
// Safe because TraceHeader has no implicit padding.
|
||||
unsafe impl DataInit for TraceHeader {}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! zero {
|
||||
($x:ident) => {
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper macro for perfetto_tags
|
||||
#[macro_export]
|
||||
macro_rules! tag_or_empty_string {
|
||||
() => {
|
||||
"\0".as_ptr() as *const i8
|
||||
};
|
||||
($tag:expr) => {
|
||||
concat!($tag, "\0").as_ptr() as *const i8
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for creating an array of const char * for perfetto tags.
|
||||
#[macro_export]
|
||||
macro_rules! perfetto_tags {
|
||||
() => {
|
||||
[
|
||||
tag_or_empty_string!(),
|
||||
tag_or_empty_string!(),
|
||||
tag_or_empty_string!(),
|
||||
tag_or_empty_string!(),
|
||||
]
|
||||
};
|
||||
($tag0:expr) => {
|
||||
[
|
||||
tag_or_empty_string!($tag0),
|
||||
tag_or_empty_string!(),
|
||||
tag_or_empty_string!(),
|
||||
tag_or_empty_string!(),
|
||||
]
|
||||
};
|
||||
($tag0:expr, $tag1:expr) => {
|
||||
[
|
||||
tag_or_empty_string!($tag0),
|
||||
tag_or_empty_string!($tag1),
|
||||
tag_or_empty_string!(),
|
||||
tag_or_empty_string!(),
|
||||
]
|
||||
};
|
||||
($tag0:expr, $tag1:expr, $tag2:expr) => {
|
||||
[
|
||||
tag_or_empty_string!($tag0),
|
||||
tag_or_empty_string!($tag1),
|
||||
tag_or_empty_string!($tag2),
|
||||
tag_or_empty_string!(),
|
||||
]
|
||||
};
|
||||
($tag0:expr, $tag1:expr, $tag2:expr, $tag3:expr) => {
|
||||
[
|
||||
tag_or_empty_string!($tag0),
|
||||
tag_or_empty_string!($tag1),
|
||||
tag_or_empty_string!($tag2),
|
||||
tag_or_empty_string!($tag3),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/// Main macro to be called by any crate wanting to use perfetto tracing. It
|
||||
/// should be called once in your crate outside of any function.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `module_path` - is the module path where this
|
||||
/// * The remaining arguments are an arbitrary list of triples that describe
|
||||
/// the tracing categories. They are supplied flattened
|
||||
/// (e.g. ((a, b, c), (d, e, f)) => (a, b, c, d, e, f).
|
||||
/// Each triple contains:
|
||||
/// - the category name (this is the same name/ident that will be passed
|
||||
/// to trace point macros).
|
||||
/// - a free form text description of the category.
|
||||
/// - the tag set for this category (generated by calling
|
||||
/// perfetto_tags).
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// setup_perfetto!(
|
||||
/// tracing,
|
||||
/// mycrate,
|
||||
/// "General trace points for my crate",
|
||||
/// perfetto_tags!(),
|
||||
/// debug,
|
||||
/// "Debug trace points",
|
||||
/// perfetto_tags!("debug"))
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! setup_perfetto {
|
||||
($mod:ident, $($cat:ident, $description:expr, $tags:expr),+) => {
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PerfettoCategory {
|
||||
$($cat,)+
|
||||
// Hacky way to get the count of the perfetto categories.
|
||||
CATEGORY_COUNT,
|
||||
}
|
||||
|
||||
/// Const array of perfetto categories that will be passed to perfetto api.
|
||||
pub const CATEGORIES: [&ctrace_category; PerfettoCategory::CATEGORY_COUNT as usize] = [
|
||||
$(
|
||||
&ctrace_category {
|
||||
client_index: PerfettoCategory::$cat as u64,
|
||||
instances_callback: Some(instances_callback),
|
||||
name: concat!(stringify!($cat), "\0").as_ptr() as *const i8,
|
||||
description: concat!($description, "\0").as_ptr() as *const i8,
|
||||
tags: $tags
|
||||
},
|
||||
)+
|
||||
];
|
||||
|
||||
/// Base offset into the global list of categories where our categories live.
|
||||
pub static PERFETTO_CATEGORY_BASE: std::sync::atomic::AtomicU64 =
|
||||
std::sync::atomic::AtomicU64::new(0);
|
||||
|
||||
/// Active trace instance bitmaps for each of our categories. We use a u32 because the
|
||||
/// cperfetto API uses a u32, but really only 8 traces can be active at a time.
|
||||
pub static PERFETTO_CATEGORY_INSTANCES:
|
||||
[std::sync::atomic::AtomicU32; PerfettoCategory::CATEGORY_COUNT as usize] = [
|
||||
$(
|
||||
// Note, we pass $cat to the zero! macro here, which always just returns
|
||||
// 0, because it's impossible to iterate over $cat unless $cat is used.
|
||||
std::sync::atomic::AtomicU32::new($crate::zero!($cat)),
|
||||
)+
|
||||
];
|
||||
|
||||
/// Register the perfetto categories defined by this macro with the perfetto shared
|
||||
/// library. This should be called once at process startup.
|
||||
pub fn register_categories() {
|
||||
PERFETTO_CATEGORY_BASE.store(
|
||||
unsafe {
|
||||
ctrace_register_categories(
|
||||
CATEGORIES.as_ptr() as *const *const ctrace_category,
|
||||
CATEGORIES.len() as u64,
|
||||
)
|
||||
},
|
||||
std::sync::atomic::Ordering::SeqCst,
|
||||
);
|
||||
}
|
||||
|
||||
/// Callback from the perfetto shared library when the set of active trace instances for
|
||||
/// a given category has changed. Index is the client index of one of our registered
|
||||
/// categories.
|
||||
extern "C" fn instances_callback(instances: u32, index: u64) {
|
||||
PERFETTO_CATEGORY_INSTANCES[index as usize].store(
|
||||
instances, std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
for cb in PERFETTO_PER_TRACE_CALLBACKS.lock().iter() {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static PERFETTO_PER_TRACE_CALLBACKS: once_cell::sync::Lazy<sync::Mutex<Vec<fn()>>> =
|
||||
once_cell::sync::Lazy::new(|| sync::Mutex::new(Vec::new()));
|
||||
|
||||
|
||||
pub fn add_per_trace_callback(callback: fn()) {
|
||||
PERFETTO_PER_TRACE_CALLBACKS.lock().push(callback);
|
||||
}
|
||||
|
||||
/// Create and return a scoped named trace event, which will start at construction and end when
|
||||
/// the event goes out of scope and is dropped. Will return None if tracing is disabled for
|
||||
/// this category.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// {
|
||||
/// let _trace = trace_event!(my_category, "trace_point_name");
|
||||
/// do_some_work();
|
||||
/// } // _trace dropped here & records the span.
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! trace_event {
|
||||
($category:ident, $name:expr) => {
|
||||
{
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$mod::PerfettoCategory::$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $mod::PerfettoCategory::$category as u64;
|
||||
Some($crate::TraceEvent::new(
|
||||
category_index,
|
||||
instances,
|
||||
concat!($name, "\0").as_ptr() as *const i8,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal macro used to begin a trace event. Not intended for direct
|
||||
/// use by library consumers.
|
||||
#[macro_export]
|
||||
macro_rules! trace_event_begin {
|
||||
($category:ident, $name:expr) => {
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$mod::PerfettoCategory::$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $mod::PerfettoCategory::$category as u64;
|
||||
|
||||
unsafe {
|
||||
$crate::trace_event_begin(
|
||||
category_index,
|
||||
instances,
|
||||
concat!($name, "\0").as_ptr() as *const i8,
|
||||
)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Ends the currently active trace event. Not intended for direct use
|
||||
/// by library consumers.
|
||||
#[macro_export]
|
||||
macro_rules! trace_event_end {
|
||||
($category:ident) => {
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$mod::PerfettoCategory::$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $mod::PerfettoCategory::$category as u64;
|
||||
unsafe { $crate::trace_event_end(category_index, instances) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates an async flow but does not start it.
|
||||
///
|
||||
/// Internal wrapper for use by cros_async_trace.
|
||||
#[macro_export]
|
||||
macro_rules! trace_create_async {
|
||||
($category:expr, $name:expr) => {
|
||||
{
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $category as u64;
|
||||
|
||||
let trace_point_name: $crate::StaticString = $name;
|
||||
unsafe {
|
||||
Some($crate::trace_create_async(
|
||||
category_index,
|
||||
instances,
|
||||
trace_point_name.as_ptr(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts an existing async flow.
|
||||
///
|
||||
/// Internal wrapper for use by cros_async_trace.
|
||||
#[macro_export]
|
||||
macro_rules! trace_begin_async {
|
||||
($category:expr, $name:expr, $optional_terminating_flow_id:expr) => {
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $category as u64;
|
||||
|
||||
if let Some(terminating_flow_id) = $optional_terminating_flow_id {
|
||||
let trace_point_name: $crate::StaticString = $name;
|
||||
// Safe because we guarantee $name is a StaticString (which enforces static
|
||||
// a lifetime for the underlying CString).
|
||||
unsafe {
|
||||
$crate::trace_begin_async(
|
||||
category_index,
|
||||
instances,
|
||||
trace_point_name.as_ptr(),
|
||||
terminating_flow_id,
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pauses a running async flow.
|
||||
///
|
||||
/// Internal wrapper for use by cros_async_trace.
|
||||
#[macro_export]
|
||||
macro_rules! trace_pause_async {
|
||||
($category:expr) => {
|
||||
{
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $category as u64;
|
||||
|
||||
unsafe {
|
||||
// Safe because we are only passing primitives in.
|
||||
Some($crate::trace_pause_async(
|
||||
category_index,
|
||||
instances,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ends a running async flow.
|
||||
///
|
||||
/// Internal wrapper for use by cros_async_trace.
|
||||
#[macro_export]
|
||||
macro_rules! trace_end_async {
|
||||
($category:expr) => {
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $category as u64;
|
||||
|
||||
// Safe because we are only passing primitives in.
|
||||
unsafe {
|
||||
$crate::trace_end_async(
|
||||
category_index,
|
||||
instances,
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Emits a counter with the specified name and value. Note that
|
||||
/// Perfetto does NOT average or sample this data, so a high volume of
|
||||
/// calls will very quickly fill the trace buffer.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// trace_counter!(my_category, "counter_name", 500);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! trace_counter {
|
||||
($category:ident, $name:literal, $value:expr) => {
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$mod::PerfettoCategory::$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $mod::PerfettoCategory::$category as u64;
|
||||
|
||||
// Safe because the counter name is a 'static string.
|
||||
unsafe {
|
||||
$crate::trace_counter(
|
||||
category_index,
|
||||
instances,
|
||||
concat!($name, "\0").as_ptr() as *const i8,
|
||||
$value,
|
||||
)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
($category:ident, $name:expr, $value:expr) => {
|
||||
// Required for safety when calling trace_counter.
|
||||
let trace_point_name: $crate::StaticString = $name;
|
||||
|
||||
let instances = $mod::PERFETTO_CATEGORY_INSTANCES
|
||||
[$mod::PerfettoCategory::$category as usize]
|
||||
.load(std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
if instances != 0 {
|
||||
let category_index = $mod::PERFETTO_CATEGORY_BASE
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
+ $mod::PerfettoCategory::$category as u64;
|
||||
|
||||
// Safe because we guarantee $name is a StaticString (which enforces static a
|
||||
// lifetime for the underlying CString).
|
||||
unsafe {
|
||||
$crate::trace_counter(
|
||||
category_index,
|
||||
instances,
|
||||
trace_point_name.as_ptr(),
|
||||
$value,
|
||||
)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Perfetto supports two backends, a system backend that runs in a dedicated
|
||||
/// process, and an in process backend. These are selected using this enum.
|
||||
pub enum BackendType {
|
||||
InProcess = BackendType_CTRACE_IN_PROCESS_BACKEND as isize,
|
||||
System = BackendType_CTRACE_SYSTEM_BACKEND as isize,
|
||||
}
|
||||
|
||||
/// Initializes the tracing system. Should not be called directly (use
|
||||
/// `setup_perfetto` instead).
|
||||
pub fn init_tracing(backend: BackendType) {
|
||||
let args = ctrace_init_args {
|
||||
api_version: 1,
|
||||
backend: backend as u32,
|
||||
shmem_size_hint_kb: 0,
|
||||
shmem_page_size_hint_kb: 0,
|
||||
shmem_batch_commits_duration_ms: 0,
|
||||
};
|
||||
unsafe { ctrace_init(&args) }
|
||||
}
|
||||
|
||||
/// Rust wrapper for running traces.
|
||||
pub struct Trace {
|
||||
session: ctrace_trace_session_handle,
|
||||
trace_stopped: bool,
|
||||
}
|
||||
|
||||
// Safe because the trace session handle can be sent between threads without ill effect.
|
||||
unsafe impl Sync for Trace {}
|
||||
unsafe impl Send for Trace {}
|
||||
|
||||
impl Trace {
|
||||
/// Starts a trace.
|
||||
pub fn start(
|
||||
duration: TraceDuration,
|
||||
buffer_size_kb: u32,
|
||||
clear_period: Duration,
|
||||
categories: Option<Vec<String>>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut config = TraceConfig::new();
|
||||
let mut incremental_state_config = IncrementalStateConfig::new();
|
||||
incremental_state_config.set_clear_period_ms(clear_period.as_millis().try_into()?);
|
||||
config.incremental_state_config = Some(incremental_state_config).into();
|
||||
|
||||
let mut buffer = BufferConfig::new();
|
||||
buffer.set_size_kb(buffer_size_kb);
|
||||
config.buffers.push(buffer);
|
||||
|
||||
let mut data_source = DataSource::new();
|
||||
let mut data_source_config = DataSourceConfig::new();
|
||||
data_source_config.name = Some("track_event".to_owned());
|
||||
|
||||
if let Some(categories) = categories {
|
||||
let mut track_event_config = TrackEventConfig::new();
|
||||
track_event_config.enabled_categories = categories;
|
||||
track_event_config.disabled_categories.push("*".to_string());
|
||||
data_source_config.track_event_config = Some(track_event_config).into();
|
||||
}
|
||||
|
||||
data_source.config = Some(data_source_config).into();
|
||||
|
||||
if let TraceDuration::StopIn(trace_duration) = duration {
|
||||
config.set_duration_ms(trace_duration.as_millis().try_into()?);
|
||||
}
|
||||
|
||||
config.data_sources.push(data_source);
|
||||
|
||||
Ok(Self {
|
||||
session: start_trace_from_proto(config)?,
|
||||
trace_stopped: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Ends a trace and writes the results to the provided file path.
|
||||
pub fn end(mut self, output: &Path) {
|
||||
// Safe because the session is guaranteed to be valid by self.
|
||||
unsafe { end_trace(self.session, output) }
|
||||
self.trace_stopped = true;
|
||||
}
|
||||
|
||||
/// Ends a trace and returns the trace data. Prepends a magic value & data length to the trace
|
||||
/// data.
|
||||
pub fn end_to_buffer(mut self) -> Vec<u8> {
|
||||
// Safe because the session is guaranteed to be valid by self, and trace_data is disposed
|
||||
// by later calling ctrace_free_trace_buffer.
|
||||
let mut trace_data = unsafe { end_trace_to_buffer(self.session) };
|
||||
|
||||
// Safe because:
|
||||
// 1. trace_data is valid from 0..size.
|
||||
// 2. trace_data lives as long as this slice.
|
||||
let trace_data_slice =
|
||||
unsafe { slice::from_raw_parts(trace_data.data as *mut u8, trace_data.size as usize) };
|
||||
|
||||
let header = TraceHeader {
|
||||
magic: *HEADER_MAGIC,
|
||||
data_size: trace_data.size,
|
||||
data_checksum_sha256: hashes::sha256(trace_data_slice),
|
||||
};
|
||||
let mut trace_vec: Vec<u8> =
|
||||
Vec::with_capacity(size_of::<TraceHeader>() + trace_data.size as usize);
|
||||
trace_vec.extend_from_slice(header.as_slice());
|
||||
trace_vec.extend_from_slice(trace_data_slice);
|
||||
|
||||
// Safe because trace data is a valid buffer created by ctrace_stop_trace_to_buffer and
|
||||
// there are no other references to it.
|
||||
unsafe { ctrace_free_trace_buffer(&mut trace_data) };
|
||||
|
||||
self.trace_stopped = true;
|
||||
trace_vec
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Trace {
|
||||
fn drop(&mut self) {
|
||||
if !self.trace_stopped {
|
||||
panic!("Trace must be stopped before it is dropped.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a perfetto trace of duration `duration` and write the output to `output`.
|
||||
pub fn run_trace(duration: Duration, buffer_size: u32, output: &Path) {
|
||||
let output = output.to_owned();
|
||||
std::thread::spawn(move || {
|
||||
let session = start_trace(duration, buffer_size);
|
||||
std::thread::sleep(duration);
|
||||
unsafe { end_trace(session, output.as_path()) };
|
||||
});
|
||||
}
|
||||
|
||||
/// Starts a Perfetto trace with the provided config.
|
||||
pub fn start_trace_from_proto(config: TraceConfig) -> anyhow::Result<ctrace_trace_session_handle> {
|
||||
let mut config_bytes = config.write_to_bytes()?;
|
||||
|
||||
// Safe because config_bytes points to valid memory & we pass its size as required.
|
||||
Ok(unsafe {
|
||||
ctrace_trace_start_from_config_proto(
|
||||
config_bytes.as_mut_ptr() as *mut c_void,
|
||||
config_bytes.len() as u64,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Starts a trace with the given "duration", where duration specifies how much history to hold in
|
||||
/// the ring buffer; in other words, duration is the lookback period when the trace results are
|
||||
/// dumped.
|
||||
pub fn start_trace(duration: Duration, buffer_size_kb: u32) -> ctrace_trace_session_handle {
|
||||
unsafe {
|
||||
ctrace_trace_start(&ctrace_trace_config {
|
||||
duration_ms: duration.as_millis() as u32,
|
||||
buffer_size_kb,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// End the given trace session and write the results to `output`.
|
||||
/// Safety: trace_session must be a valid trace session from `start_trace`.
|
||||
pub unsafe fn end_trace(trace_session: ctrace_trace_session_handle, output: &Path) {
|
||||
let path_c_str = CString::new(output.as_os_str().to_str().unwrap()).unwrap();
|
||||
ctrace_trace_stop(trace_session, path_c_str.as_ptr());
|
||||
}
|
||||
|
||||
/// End the given trace session returns the trace data.
|
||||
///
|
||||
/// Safety: trace_session must be a valid trace session from `start_trace`.
|
||||
pub unsafe fn end_trace_to_buffer(
|
||||
trace_session: ctrace_trace_session_handle,
|
||||
) -> ctrace_trace_buffer {
|
||||
ctrace_trace_stop_to_buffer(trace_session)
|
||||
}
|
||||
|
||||
/// Add a clock snapshot to the current trace.
|
||||
///
|
||||
/// This function does not not do any inline checking if a trace is active,
|
||||
/// and thus should only be called in a per-trace callback registered via
|
||||
/// the add_per_trace_callback! macro.
|
||||
pub fn snapshot_clock(mut snapshot: ClockSnapshot) {
|
||||
unsafe { ctrace_add_clock_snapshot(&mut snapshot.snapshot) };
|
||||
}
|
||||
|
||||
/// Represents a Perfetto trace span.
|
||||
pub struct TraceEvent {
|
||||
category_index: u64,
|
||||
instances: u32,
|
||||
}
|
||||
|
||||
impl TraceEvent {
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub fn new(category_index: u64, instances: u32, name: *const i8) -> Self {
|
||||
unsafe {
|
||||
trace_event_begin(
|
||||
category_index,
|
||||
instances,
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
name,
|
||||
)
|
||||
};
|
||||
Self {
|
||||
category_index,
|
||||
instances,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TraceEvent {
|
||||
fn drop(&mut self) {
|
||||
unsafe { trace_event_end(self.category_index, self.instances) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension of the Perfetto enum (protos::perfetto_config::BuiltinClock) which
|
||||
/// includes the proposed (but not yet official) TSC clock item.
|
||||
pub enum BuiltinClock {
|
||||
Unknown = 0,
|
||||
Realtime = 1,
|
||||
Coarse = 2,
|
||||
Monotonic = 3,
|
||||
MonotonicCoarse = 4,
|
||||
MonotonicRaw = 5,
|
||||
Boottime = 6,
|
||||
Tsc = 9,
|
||||
}
|
||||
|
||||
/// Wrapper struct around a ctrace_clock_snapshot.
|
||||
pub struct ClockSnapshot {
|
||||
pub snapshot: ctrace_clock_snapshot,
|
||||
}
|
||||
|
||||
impl ClockSnapshot {
|
||||
pub fn new(first: &Clock, second: &Clock) -> ClockSnapshot {
|
||||
ClockSnapshot {
|
||||
snapshot: ctrace_clock_snapshot {
|
||||
clocks: [first.clock, second.clock],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder wrapper for a ctrace_clock.
|
||||
pub struct Clock {
|
||||
clock: ctrace_clock,
|
||||
}
|
||||
|
||||
impl Clock {
|
||||
pub fn new(clock_id: u32, timestamp: u64) -> Clock {
|
||||
Clock {
|
||||
clock: ctrace_clock {
|
||||
clock_id,
|
||||
timestamp,
|
||||
is_incremental: false,
|
||||
unit_multiplier_ns: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_multiplier(&mut self, multiplier: u64) -> &mut Clock {
|
||||
self.clock.unit_multiplier_ns = multiplier;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_is_incremental(&mut self, is_incremental: bool) -> &mut Clock {
|
||||
self.clock.is_incremental = is_incremental;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// If running tests in debug mode, ie. `cargo test -p perfetto`,
|
||||
// the cperfetto.dll needs to be imported into the `target` directory.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(dead_code)]
|
||||
use cros_tracing_types::static_strings::StaticString;
|
||||
|
||||
use super::*;
|
||||
|
||||
const AOT_BUFFER_SIZE_KB: u32 = 1024;
|
||||
const AOT_BUFFER_CLEAR_PERIOD: Duration = Duration::from_secs(1);
|
||||
setup_perfetto!(tests, future, "Async ftrace points", perfetto_tags!());
|
||||
#[test]
|
||||
fn test_async_trace_builds_and_runs() {
|
||||
tests::register_categories();
|
||||
init_tracing(BackendType::InProcess);
|
||||
let trace = Trace::start(
|
||||
TraceDuration::AlwaysOn,
|
||||
AOT_BUFFER_SIZE_KB,
|
||||
AOT_BUFFER_CLEAR_PERIOD,
|
||||
Some(vec!["future".to_string()]),
|
||||
)
|
||||
.expect("Failed to start trace");
|
||||
|
||||
let static_name = StaticString::register("future_1");
|
||||
let future_category = PerfettoCategory::future;
|
||||
|
||||
let flow_id = tests::trace_create_async!(future_category, static_name);
|
||||
assert!(flow_id.is_some());
|
||||
|
||||
tests::trace_begin_async!(future_category, static_name, flow_id);
|
||||
|
||||
let flow_id = tests::trace_pause_async!(future_category);
|
||||
assert!(flow_id.is_some());
|
||||
|
||||
tests::trace_begin_async!(future_category, static_name, flow_id);
|
||||
|
||||
tests::trace_end_async!(future_category);
|
||||
|
||||
trace.end_to_buffer();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tags_macro_all_empty() {
|
||||
let all_tags_empty = perfetto_tags!();
|
||||
|
||||
// SAFETY: strings from perfetto_tags have static lifetime.
|
||||
unsafe {
|
||||
assert_eq!(*(all_tags_empty[0] as *const char), '\0');
|
||||
assert_eq!(*(all_tags_empty[1] as *const char), '\0');
|
||||
assert_eq!(*(all_tags_empty[2] as *const char), '\0');
|
||||
assert_eq!(*(all_tags_empty[3] as *const char), '\0');
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tags_macro_two_used() {
|
||||
let two_used_tags = perfetto_tags!("tag0", "tag1");
|
||||
|
||||
// SAFETY: strings from perfetto_tags have static lifetime.
|
||||
let tag0 = unsafe { CStr::from_ptr(two_used_tags[0] as *mut c_char) };
|
||||
// SAFETY: strings from perfetto_tags have static lifetime.
|
||||
let tag1 = unsafe { CStr::from_ptr(two_used_tags[1] as *mut c_char) };
|
||||
assert_eq!(tag0.to_str().unwrap(), "tag0");
|
||||
assert_eq!(tag1.to_str().unwrap(), "tag1");
|
||||
|
||||
// SAFETY: strings have static lifetime.
|
||||
unsafe {
|
||||
assert_eq!(*(two_used_tags[2] as *const char), '\0');
|
||||
assert_eq!(*(two_used_tags[3] as *const char), '\0');
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue