ittapi/
jit.rs

1//! The JIT (Just-In-Time) Profiling API provides functionality to report information about
2//! just-in-time generated code that can be used by performance tools. The [Jit] Rust structure is a
3//! high-level view of a subset of the full functionality available. See the [JIT Profiling API] for
4//! more information.
5//!
6//! [JIT Profiling API]:
7///     https://www.intel.com/content/www/us/en/develop/documentation/vtune-help/top/api-support/jit-profiling-api.html
8use anyhow::Context;
9use std::{ffi::CString, os, ptr};
10
11/// Register JIT-compiled methods with a performance tool (e.g., VTune). This structure assumes
12/// single-threaded access; if your program may be multi-threaded, make sure to guard multi-threaded
13/// access with a mutex.
14#[derive(Default)]
15pub struct Jit {
16    shutdown_complete: bool,
17}
18
19impl Jit {
20    /// Returns a new `MethodId` for use in `MethodLoad` events.
21    #[allow(clippy::unused_self)]
22    #[must_use]
23    pub fn get_method_id(&self) -> MethodId {
24        MethodId(unsafe { ittapi_sys::iJIT_GetNewMethodID() })
25    }
26
27    /// Notifies any `EventType` to VTune.
28    ///
29    /// # Errors
30    ///
31    /// May fail if the underlying call to the ITT library fails.
32    #[allow(clippy::unused_self)]
33    pub fn notify_event(&self, mut event: EventType) -> anyhow::Result<()> {
34        let tag = event.tag();
35        let data = event.data();
36        log::trace!("notify_event: tag={:?}", tag);
37        let res = unsafe { ittapi_sys::iJIT_NotifyEvent(tag, data) };
38        match event {
39            EventType::MethodLoadFinished(_) => {
40                // Documentation of the `iJIT_NotifyEvent` says that the return code is undefined
41                // for this particular event, so we can't distinguish failures from successes.
42                // Hope for the best.
43                Ok(())
44            }
45            EventType::Shutdown => {
46                if res == 1 {
47                    Ok(())
48                } else {
49                    anyhow::bail!("error when notifying event with tag {tag}: return code = {res}");
50                }
51            }
52        }
53    }
54
55    // High-level helpers.
56
57    /// Notifies VTune that a new function described by the `MethodLoadBuilder` has been jitted.
58    ///
59    /// # Errors
60    ///
61    /// May fail if the builder has invalid data or if the ITT library fails to notify the method
62    /// load event.
63    pub fn load_method(&self, builder: MethodLoadBuilder) -> anyhow::Result<()> {
64        let method_id = self.get_method_id();
65        let method_load = builder.build(method_id)?;
66        self.notify_event(method_load)
67    }
68
69    /// Notifies VTune that profiling is being shut down.
70    ///
71    /// # Errors
72    ///
73    /// May fail if the ITT library fails to notify the shutdown event.
74    pub fn shutdown(&mut self) -> anyhow::Result<()> {
75        if self.shutdown_complete {
76            return Ok(());
77        }
78        let res = self.notify_event(EventType::Shutdown);
79        if res.is_ok() {
80            self.shutdown_complete = true;
81        }
82        res
83    }
84}
85
86impl Drop for Jit {
87    fn drop(&mut self) {
88        if !self.shutdown_complete {
89            // There's not much we can do when an error happens here.
90            if let Err(err) = self.shutdown() {
91                log::error!("Error when shutting down VTune: {}", err);
92            }
93        }
94    }
95}
96
97/// Type of event to be dispatched through ittapi's JIT event API.
98pub enum EventType {
99    /// Send this event after a JITted method has been loaded into memory, and possibly JIT
100    /// compiled, but before the code is executed.
101    MethodLoadFinished(MethodLoad),
102
103    /// Send this notification to terminate profiling.
104    Shutdown,
105}
106
107impl EventType {
108    /// Returns the C event type to be used when notifying this event to VTune.
109    fn tag(&self) -> ittapi_sys::iJIT_jvm_event {
110        match self {
111            EventType::MethodLoadFinished(_) => {
112                ittapi_sys::iJIT_jvm_event_iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED
113            }
114            EventType::Shutdown => ittapi_sys::iJIT_jvm_event_iJVM_EVENT_TYPE_SHUTDOWN,
115        }
116    }
117
118    /// Returns a raw C pointer to the event-specific data that must be used when notifying this
119    /// event to VTune.
120    fn data(&mut self) -> *mut os::raw::c_void {
121        match self {
122            EventType::MethodLoadFinished(method_load) => ptr::addr_of_mut!(method_load.0).cast(),
123            EventType::Shutdown => ptr::null_mut(),
124        }
125    }
126}
127
128/// Newtype wrapper for a method id returned by ittapi's `iJIT_GetNewMethodID`, as returned by
129/// `VtuneState::get_method_id` in the high-level API.
130#[derive(Clone, Copy)]
131pub struct MethodId(u32);
132
133/// Newtype wrapper for a JIT method load.
134pub struct MethodLoad(ittapi_sys::_iJIT_Method_Load);
135
136/// Multi-step constructor using the builder pattern for a `MethodLoad` event.
137pub struct MethodLoadBuilder {
138    method_name: String,
139    addr: *const u8,
140    len: usize,
141    class_file_name: Option<String>,
142    source_file_name: Option<String>,
143}
144
145impl MethodLoadBuilder {
146    /// Creates a new `MethodLoadBuilder` from scratch.
147    ///
148    /// `addr` is the pointer to the start of the code region, `len` is the size of this code
149    /// region in bytes.
150    #[must_use]
151    pub fn new(method_name: String, addr: *const u8, len: usize) -> Self {
152        Self {
153            method_name,
154            addr,
155            len,
156            class_file_name: None,
157            source_file_name: None,
158        }
159    }
160
161    /// Attache a class file.
162    #[must_use]
163    pub fn class_file_name(mut self, class_file_name: String) -> Self {
164        self.class_file_name = Some(class_file_name);
165        self
166    }
167
168    /// Attach a source file.
169    #[must_use]
170    pub fn source_file_name(mut self, source_file_name: String) -> Self {
171        self.source_file_name = Some(source_file_name);
172        self
173    }
174
175    /// Build a "method load" event for the given `method_id`.
176    ///
177    /// # Errors
178    ///
179    /// May fail if the various names passed to this builder are not valid C strings.
180    pub fn build(self, method_id: MethodId) -> anyhow::Result<EventType> {
181        Ok(EventType::MethodLoadFinished(MethodLoad(
182            ittapi_sys::_iJIT_Method_Load {
183                method_id: method_id.0,
184                method_name: CString::new(self.method_name)
185                    .context("CString::new failed")?
186                    .into_raw(),
187                method_load_address: self.addr as *mut os::raw::c_void,
188                method_size: self.len.try_into().expect("cannot fit length into 32 bits"),
189                line_number_size: 0,
190                line_number_table: ptr::null_mut(),
191                class_id: 0, // Field officially obsolete in Intel's doc.
192                class_file_name: CString::new(
193                    self.class_file_name
194                        .as_deref()
195                        .unwrap_or("<unknown class file name>"),
196                )
197                .context("CString::new failed")?
198                .into_raw(),
199                source_file_name: CString::new(
200                    self.source_file_name
201                        .as_deref()
202                        .unwrap_or("<unknown source file name>"),
203                )
204                .context("CString::new failed")?
205                .into_raw(),
206            },
207        )))
208    }
209}