Skip to content

Commit

Permalink
rust: add support for Intel PT regions (#140)
Browse files Browse the repository at this point in the history
* rust: add `__itt*` functions in generated bindings

This change adds some bindgen-generated functions, and not just
variables, to the generated bindings. This should resolve #139 by making
the `pt_region_*` functions at least visibile in the `ittapi-sys` crate,
making it possible to add higher-level Rust versions.

We cannot generate bindings for all declared functions because these
cause link errors: they are only present in the dynamic library (`*.so`)
but not in the static library (`libittnotify.a`) that the `ittapi-sys`
crate links to. So, for these dynamically-provided functions we must
just retain the `*_ptr__3_0` data symbols and wait for them to be
resolved to functions pointers at runtime.

* rust: remove unused code

* rust: document static/dynamic linking in `README.md`

It is easy to forget how the `ittapi` system of static + dynamic linking
works (and why!). To answer the underlying question of issues like #139,
this change adds some `README.md` documentation explaining why we can't
just include all function symbols in the generated bindings.

* rust: add `Region` for marking Intel PT regions

In #139, @codecnotsupported pointed out a need for access to
`__itt_mark_pt_region_begin` and `__itt_mark_pt_region_end` for
fine-grained VTune analysis using Intel PT. This change adds a
high-level `Region` structure for easy access to these now-available
functions. Closes #139.
  • Loading branch information
abrown committed Apr 16, 2024
1 parent 9fdc830 commit ec879c3
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 16 deletions.
2 changes: 1 addition & 1 deletion rust/ittapi-sys/Cargo.toml
Expand Up @@ -28,5 +28,5 @@ exclude = [
cc = "1.0.73"

[dev-dependencies]
bindgen = "0.68"
bindgen = "0.69.4"
diff = "0.1"
9 changes: 8 additions & 1 deletion rust/ittapi-sys/README.md
Expand Up @@ -26,9 +26,16 @@ Linux](https://docs.wasmtime.dev/examples-profiling-vtune.html)

```toml
[dependencies]
ittapi-sys = "0.3"
ittapi-sys = "*"
```

Using the symbols in this crate can be tricky: `ittapi` consists of a static part (e.g.,
`libittnotify.a`) linked in this crate _and_ a dynamic part (e.g., `libittnotify_collector.so`,
VTune Profiler). The static part provides the ITT data symbols that may be subsequently resolved to
actual implementations in the dynamic part--the data collector. This crate only provides symbols
like `__itt_task_begin_ptr__3_0`, not `__itt_task_begin`, to avoid link errors; programs using
`ittapi` should compile even if the dynamic part is not present. Using the [high-level Rust crate]
avoids this complexity.

### Build

Expand Down
8 changes: 8 additions & 0 deletions rust/ittapi-sys/src/freebsd/ittnotify_bindings.rs
Expand Up @@ -69,6 +69,14 @@ pub type __itt_pt_region_create_ptr__3_0_t = ::std::option::Option<
extern "C" {
pub static mut __itt_pt_region_create_ptr__3_0: __itt_pt_region_create_ptr__3_0_t;
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
}
pub type __itt_thread_set_name_ptr__3_0_t =
::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
extern "C" {
Expand Down
9 changes: 1 addition & 8 deletions rust/ittapi-sys/src/lib.rs
@@ -1,4 +1,5 @@
//! This library contains OS-specific bindings to the C `ittapi` library.

#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
#![allow(unused)]
#![deny(clippy::all)]
Expand Down Expand Up @@ -32,11 +33,3 @@ include!("windows/jitprofiling_bindings.rs");
include!("freebsd/jitprofiling_bindings.rs");
#[cfg(target_os = "openbsd")]
include!("openbsd/jitprofiling_bindings.rs");

// #[link(name = "ittnotify", kind = "static")]
// extern "C" {
// #[link_name = "__itt_domain_create_init_3_0"]
// pub fn __itt_domain_create_init_3_0(name: *const std::os::raw::c_char) -> *mut __itt_domain;
// // #[link_name = "__itt_domain_create_init_3_0"]
// // pub fn __itt_domain_create(name: *const std::os::raw::c_char) -> *mut __itt_domain;
// }
10 changes: 9 additions & 1 deletion rust/ittapi-sys/src/linux/ittnotify_bindings.rs
@@ -1,4 +1,4 @@
/* automatically generated by rust-bindgen 0.68.1 */
/* automatically generated by rust-bindgen 0.69.4 */

pub const ITT_OS_WIN: u32 = 1;
pub const ITT_OS_LINUX: u32 = 2;
Expand Down Expand Up @@ -59,6 +59,14 @@ pub type __itt_pt_region_create_ptr__3_0_t = ::std::option::Option<
extern "C" {
pub static mut __itt_pt_region_create_ptr__3_0: __itt_pt_region_create_ptr__3_0_t;
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
}
pub type __itt_thread_set_name_ptr__3_0_t =
::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
extern "C" {
Expand Down
2 changes: 1 addition & 1 deletion rust/ittapi-sys/src/linux/jitprofiling_bindings.rs
@@ -1,4 +1,4 @@
/* automatically generated by rust-bindgen 0.68.1 */
/* automatically generated by rust-bindgen 0.69.4 */

#[doc = "<\\brief Send this to shutdown the agent.\n Use NULL for event data."]
pub const iJIT_jvm_event_iJVM_EVENT_TYPE_SHUTDOWN: iJIT_jvm_event = 2;
Expand Down
10 changes: 9 additions & 1 deletion rust/ittapi-sys/src/macos/ittnotify_bindings.rs
@@ -1,4 +1,4 @@
/* automatically generated by rust-bindgen 0.68.1 */
/* automatically generated by rust-bindgen 0.69.4 */

pub const ITT_OS_WIN: u32 = 1;
pub const ITT_OS_LINUX: u32 = 2;
Expand Down Expand Up @@ -59,6 +59,14 @@ pub type __itt_pt_region_create_ptr__3_0_t = ::std::option::Option<
extern "C" {
pub static mut __itt_pt_region_create_ptr__3_0: __itt_pt_region_create_ptr__3_0_t;
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
}
pub type __itt_thread_set_name_ptr__3_0_t =
::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
extern "C" {
Expand Down
2 changes: 1 addition & 1 deletion rust/ittapi-sys/src/macos/jitprofiling_bindings.rs
@@ -1,4 +1,4 @@
/* automatically generated by rust-bindgen 0.68.1 */
/* automatically generated by rust-bindgen 0.69.4 */

#[doc = "<\\brief Send this to shutdown the agent.\n Use NULL for event data."]
pub const iJIT_jvm_event_iJVM_EVENT_TYPE_SHUTDOWN: iJIT_jvm_event = 2;
Expand Down
8 changes: 8 additions & 0 deletions rust/ittapi-sys/src/openbsd/ittnotify_bindings.rs
Expand Up @@ -52,6 +52,14 @@ pub type __itt_pt_region_create_ptr__3_0_t = ::std::option::Option<
extern "C" {
pub static mut __itt_pt_region_create_ptr__3_0: __itt_pt_region_create_ptr__3_0_t;
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
}
pub type __itt_thread_set_name_ptr__3_0_t =
::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
extern "C" {
Expand Down
10 changes: 9 additions & 1 deletion rust/ittapi-sys/src/windows/ittnotify_bindings.rs
@@ -1,4 +1,4 @@
/* automatically generated by rust-bindgen 0.68.1 */
/* automatically generated by rust-bindgen 0.69.4 */

pub const ITT_OS_WIN: u32 = 1;
pub const ITT_OS_LINUX: u32 = 2;
Expand Down Expand Up @@ -65,6 +65,14 @@ pub type __itt_pt_region_createW_ptr__3_0_t =
extern "C" {
pub static mut __itt_pt_region_createW_ptr__3_0: __itt_pt_region_createW_ptr__3_0_t;
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
}
extern "C" {
#[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
}
pub type __itt_thread_set_nameA_ptr__3_0_t =
::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
extern "C" {
Expand Down
2 changes: 1 addition & 1 deletion rust/ittapi-sys/src/windows/jitprofiling_bindings.rs
@@ -1,4 +1,4 @@
/* automatically generated by rust-bindgen 0.68.1 */
/* automatically generated by rust-bindgen 0.69.4 */

#[doc = "<\\brief Send this to shutdown the agent.\n Use NULL for event data."]
pub const iJIT_jvm_event_iJVM_EVENT_TYPE_SHUTDOWN: iJIT_jvm_event = 2;
Expand Down
5 changes: 5 additions & 0 deletions rust/ittapi-sys/tests/bindgen-up-to-date.rs
Expand Up @@ -32,6 +32,11 @@ fn test_ittnotify_bindings_up_to_date() {
.formatter(bindgen::Formatter::Rustfmt)
.allowlist_var("ITT.*")
.allowlist_var("__itt.*")
// Also, note how few functions we allow: if we generate bindings for all the declared
// functions, we run into linking errors. Only some functions are actually defined in
// `libittnotify.a` but most are provided dynamically by the dynamic collection library. See
// the `README.md` for more details.
.allowlist_function("__itt_mark_pt.*")
.header(concat(INCLUDE_PATH, "/ittnotify.h"))
.generate()
.expect("Unable to generate ittnotify bindings.")
Expand Down
11 changes: 11 additions & 0 deletions rust/ittapi/src/domain.rs
Expand Up @@ -43,3 +43,14 @@ impl Domain {
/// [ITT documentation]:
/// https://www.intel.com/content/www/us/en/develop/documentation/vtune-help/top/api-support/instrumentation-and-tracing-technology-apis/instrumentation-tracing-technology-api-reference/domain-api.html
unsafe impl Sync for Domain {}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[should_panic(expected = "unable to create a CString; does it contain a 0 byte?")]
fn zero_byte() {
let _domain = Domain::new("zero\0byte\0name");
}
}
2 changes: 2 additions & 0 deletions rust/ittapi/src/lib.rs
Expand Up @@ -13,12 +13,14 @@ mod collection_control;
mod domain;
mod event;
pub mod jit;
mod region;
mod string;
mod task;
mod util;

pub use collection_control::{detach, pause, resume};
pub use domain::Domain;
pub use event::Event;
pub use region::{MarkedRegion, Region};
pub use string::StringHandle;
pub use task::Task;
94 changes: 94 additions & 0 deletions rust/ittapi/src/region.rs
@@ -0,0 +1,94 @@
use std::ffi::CString;

/// An Intel® Processor Trace region.
pub struct Region(ittapi_sys::__itt_pt_region);
impl Region {
/// Create a new Intel PT region.
///
/// ```
/// # use ittapi::Region;
/// let region = Region::new("test-region");
/// ```
///
/// # Panics
///
/// Panics if the domain name contains a `0` byte.
#[must_use]
pub fn new(name: &str) -> Self {
let c_string =
CString::new(name).expect("unable to create a CString; does it contain a 0 byte?");
#[cfg(unix)]
let create_fn = unsafe { ittapi_sys::__itt_pt_region_create_ptr__3_0 };
#[cfg(windows)]
let create_fn = unsafe { ittapi_sys::__itt_pt_region_createA_ptr__3_0 };
let region = if let Some(create_fn) = create_fn {
unsafe { create_fn(c_string.as_ptr()) }
} else {
// Use this value as a sentinel to indicate that the region was not created.
u8::MAX
};
Self(region)
}

/// Mark a section of code as an Intel PT region using `__itt_mark_pt_region_begin`. This can be
/// used for fine-grained profiling, such as [anomaly detection] (a preview feature of VTune).
///
/// [anomaly detection]:
/// https://www.intel.com/content/www/us/en/docs/vtune-profiler/user-guide/2024-1/anomaly-detection-analysis.html
///
/// ```
/// # use ittapi::Region;
/// let region = Region::new("test-region");
/// // Mark a region for fine-grained measurement, such as a tight loop.
/// for _ in 0..10 {
/// let _marked = region.mark();
/// let _ = 2 + 2;
/// // Marked region ends here, when dropped; use `end()` to end it explicitly.
/// }
/// ```
#[inline]
#[must_use]
pub fn mark(&self) -> MarkedRegion {
unsafe { ittapi_sys::__itt_mark_pt_region_begin(self.0) };
MarkedRegion(self)
}
}

/// A [`MarkedRegion`] is a Rust helper structure for ergonomically ending a marked region using
/// `__itt_mark_pt_region_end`. See [`Region::mark`] for more details.
pub struct MarkedRegion<'a>(&'a Region);
impl<'a> MarkedRegion<'a> {
/// End the marked region.
#[inline]
pub fn end(self) {
// Do nothing; the `Drop` implementation does the work. See discussion at
// https://stackoverflow.com/questions/53254645.
}
}
impl Drop for MarkedRegion<'_> {
#[inline]
fn drop(&mut self) {
unsafe { ittapi_sys::__itt_mark_pt_region_end(self.0 .0) };
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[should_panic(expected = "unable to create a CString; does it contain a 0 byte?")]
fn zero_byte() {
let _region = Region::new("zero\0byte\0name");
}

#[test]
fn sanity() {
let region = Region::new("region");
for _ in 0..10 {
let _marked_region = region.mark();
// Do nothing.
_marked_region.end();
}
}
}

0 comments on commit ec879c3

Please sign in to comment.