Skip to content

Commit b54903c

Browse files
committed
Add bindings for Performance Hint Manager
https://developer.android.com/ndk/reference/group/a-performance-hint Performance Hint Manager allows apps to inform the system about workloads running on threads to more accurately allocate power for them. After the fact, applications should report back how long their operation actually ended up taking. The new work duration API (driven by `AWorkDuration`) also includes GPU timings.
1 parent ec624f2 commit b54903c

File tree

4 files changed

+335
-0
lines changed

4 files changed

+335
-0
lines changed

ndk/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Unreleased
22

33
- image_reader: Add `ImageReader::new_with_data_space()` constructor and `ImageReader::data_space()` getter from API level 34. (#474)
4+
- Add bindings for Performance Hint manager (`APerformanceHintManager`, `APerformanceHintSession`, `AWorkDuration`). (#480)
45

56
# 0.9.0 (2024-04-26)
67

ndk/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ api-level-31 = ["api-level-30"]
3535
api-level-32 = ["api-level-31"]
3636
api-level-33 = ["api-level-32"]
3737
api-level-34 = ["api-level-33"]
38+
api-level-35 = ["api-level-34"]
3839

3940
test = ["ffi/test", "jni", "all"]
4041

ndk/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub mod media;
2626
pub mod media_error;
2727
pub mod native_activity;
2828
pub mod native_window;
29+
pub mod performance_hint;
2930
pub mod shared_memory;
3031
pub mod surface_texture;
3132
pub mod sync;

ndk/src/performance_hint.rs

+332
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
//! Bindings for [`APerformanceHintManager`] and [`APerformanceHintSession`]
2+
//!
3+
//! `APerformanceHint` allows apps to create performance hint sessions for groups of
4+
//! threads, and provide hints to the system about the workload of those threads, to help the
5+
//! system more accurately allocate power for them. It is the NDK counterpart to the [Java
6+
//! PerformanceHintManager SDK API].
7+
//!
8+
//! [`APerformanceHintManager`]: https://developer.android.com/ndk/reference/group/a-performance-hint#aperformancehintmanager
9+
//! [`APerformanceHintSession`]: https://developer.android.com/ndk/reference/group/a-performance-hint#aperformancehintsession
10+
//! [Java PerformanceHintManager SDK API]: https://developer.android.com/reference/android/os/PerformanceHintManager
11+
#![cfg(feature = "api-level-33")]
12+
13+
#[cfg(doc)]
14+
use std::io::ErrorKind;
15+
use std::{io::Result, ptr::NonNull, time::Duration};
16+
17+
use crate::utils::status_to_io_result;
18+
19+
/// An opaque type representing a handle to a performance hint manager.
20+
///
21+
/// To use:
22+
/// - Obtain the performance hint manager instance by calling [`PerformanceHintManager::new()`].
23+
/// - Create a [`PerformanceHintSession`] with [`PerformanceHintManager::new()`].
24+
/// - Get the preferred update rate with [`PerformanceHintManager::preferred_update_rate()`].
25+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26+
#[doc(alias = "APerformanceHintManager")]
27+
pub struct PerformanceHintManager {
28+
ptr: NonNull<ffi::APerformanceHintManager>,
29+
}
30+
31+
// SAFETY: The NDK stores a per-process global singleton that is accessible from any thread, and the
32+
// only public methods perform thread-safe operations (i.e. the underlying AIBinder API to implement
33+
// `IHintManager->createHintSession()` is thread-safe).
34+
unsafe impl Send for PerformanceHintManager {}
35+
unsafe impl Sync for PerformanceHintManager {}
36+
37+
impl PerformanceHintManager {
38+
/// Retrieve a reference to the performance hint manager.
39+
///
40+
/// Returns [`None`] on failure.
41+
#[doc(alias = "APerformanceHint_getManager")]
42+
pub fn new() -> Option<Self> {
43+
NonNull::new(unsafe { ffi::APerformanceHint_getManager() }).map(|ptr| Self { ptr })
44+
}
45+
46+
/// Creates a session for the given set of threads and sets their initial target work duration.
47+
///
48+
/// # Parameters
49+
/// - `thread_ids`: The list of threads to be associated with this session. They must be part of
50+
/// this process' thread group.
51+
/// - `initial_target_work_duration`: The target duration for the new session. This must be
52+
/// positive if using the work duration API, or [`Duration::ZERO`] otherwise.
53+
#[doc(alias = "APerformanceHint_createSession")]
54+
pub fn create_session(
55+
&self,
56+
thread_ids: &[i32],
57+
initial_target_work_duration: Duration,
58+
) -> Option<PerformanceHintSession> {
59+
NonNull::new(unsafe {
60+
ffi::APerformanceHint_createSession(
61+
self.ptr.as_ptr(),
62+
thread_ids.as_ptr(),
63+
thread_ids.len(),
64+
initial_target_work_duration
65+
.as_nanos()
66+
.try_into()
67+
.expect("Duration should be convertible to i64 nanoseconds"),
68+
)
69+
})
70+
.map(|ptr| PerformanceHintSession { ptr })
71+
}
72+
73+
/// Get preferred update rate information for this device.
74+
///
75+
/// Returns the preferred update rate supported by device software.
76+
#[doc(alias = "APerformanceHint_getPreferredUpdateRateNanos")]
77+
pub fn preferred_update_rate(&self) -> Duration {
78+
Duration::from_nanos(unsafe {
79+
ffi::APerformanceHint_getPreferredUpdateRateNanos(self.ptr.as_ptr())
80+
.try_into()
81+
.expect("getPreferredUpdateRateNanos should not return negative")
82+
})
83+
}
84+
}
85+
86+
/// An opaque type representing a handle to a performance hint session. A session can only be
87+
/// acquired from a [`PerformanceHintManager`] with [`PerformanceHintManager::create_session()`].
88+
/// It will be freed automatically on [`drop()`] after use.
89+
///
90+
/// A Session represents a group of threads with an inter-related workload such that hints for their
91+
/// performance should be considered as a unit. The threads in a given session should be long-lived
92+
/// and not created or destroyed dynamically.
93+
///
94+
/// The work duration API can be used with periodic workloads to dynamically adjust
95+
/// thread performance and keep the work on schedule while optimizing the available
96+
/// power budget. When using the work duration API, the starting target duration
97+
/// should be specified while creating the session, and can later be adjusted with
98+
/// [`PerformanceHintSession::update_target_work_duration()`]. While using the work duration API,
99+
/// the client is expected to call [`PerformanceHintSession::report_actual_work_duration()`] each
100+
/// cycle to report the actual time taken to complete to the system.
101+
///
102+
/// All timings should be from [`ffi::CLOCK_MONOTONIC`].
103+
#[derive(Debug, PartialEq, Eq, Hash)]
104+
#[doc(alias = "APerformanceHintSession")]
105+
pub struct PerformanceHintSession {
106+
ptr: NonNull<ffi::APerformanceHintSession>,
107+
}
108+
109+
impl PerformanceHintSession {
110+
/// Updates this session's target duration for each cycle of work.
111+
///
112+
/// `target_duration` is the new desired duration.
113+
///
114+
/// # Returns
115+
/// - [`ErrorKind::InvalidInput`] if `target_duration` is not positive.
116+
/// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed.
117+
#[doc(alias = "APerformanceHint_updateTargetWorkDuration")]
118+
pub fn update_target_work_duration(&self, target_duration: Duration) -> Result<()> {
119+
status_to_io_result(unsafe {
120+
ffi::APerformanceHint_updateTargetWorkDuration(
121+
self.ptr.as_ptr(),
122+
target_duration
123+
.as_nanos()
124+
.try_into()
125+
.expect("Duration should be convertible to i64 nanoseconds"),
126+
)
127+
})
128+
}
129+
130+
/// Reports the actual duration for the last cycle of work.
131+
///
132+
/// The system will attempt to adjust the scheduling and performance of the threads within the
133+
/// thread group to bring the actual duration close to the target duration.
134+
///
135+
/// `actual_duration` is the duration of time the thread group took to complete its last
136+
/// task.
137+
///
138+
/// # Returns
139+
/// - [`ErrorKind::InvalidInput`] if `actual_duration` is not positive.
140+
/// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed.
141+
#[doc(alias = "APerformanceHint_reportActualWorkDuration")]
142+
pub fn report_actual_work_duration(&self, actual_duration: Duration) -> Result<()> {
143+
status_to_io_result(unsafe {
144+
ffi::APerformanceHint_reportActualWorkDuration(
145+
self.ptr.as_ptr(),
146+
actual_duration
147+
.as_nanos()
148+
.try_into()
149+
.expect("Duration should be convertible to i64 nanoseconds"),
150+
)
151+
})
152+
}
153+
154+
/// Set a list of threads to the performance hint session. This operation will replace the
155+
/// current list of threads with the given list of threads.
156+
///
157+
/// `thread_ids` is the list of threads to be associated with this session. They must be part of
158+
/// this app's thread group.
159+
///
160+
/// # Returns
161+
/// - [`ErrorKind::InvalidInput`] if the list of thread ids is empty or if any of the thread ids
162+
/// are not part of the thread group.
163+
/// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed.
164+
/// - [`ErrorKind::PermissionDenied`] if any thread id doesn't belong to the application.
165+
#[cfg(feature = "api-level-34")]
166+
#[doc(alias = "APerformanceHint_setThreads")]
167+
pub fn set_threads(&self, thread_ids: &[i32]) -> Result<()> {
168+
status_to_io_result(unsafe {
169+
ffi::APerformanceHint_setThreads(
170+
self.ptr.as_ptr(),
171+
thread_ids.as_ptr(),
172+
thread_ids.len(),
173+
)
174+
})
175+
}
176+
177+
/// This tells the session that these threads can be safely scheduled to prefer power efficiency
178+
/// over performance.
179+
///
180+
/// `enabled` is the flag which sets whether this session will use power-efficient scheduling.
181+
///
182+
/// # Returns
183+
/// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed.
184+
#[cfg(feature = "api-level-35")]
185+
#[doc(alias = "APerformanceHint_setPreferPowerEfficiency")]
186+
pub fn set_prefer_power_efficiency(&self, enabled: bool) -> Result<()> {
187+
status_to_io_result(unsafe {
188+
ffi::APerformanceHint_setPreferPowerEfficiency(self.ptr.as_ptr(), enabled)
189+
})
190+
}
191+
192+
/// Reports the durations for the last cycle of work.
193+
///
194+
/// The system will attempt to adjust the scheduling and performance of the threads within the
195+
/// thread group to bring the actual duration close to the target duration.
196+
///
197+
/// # Parameters
198+
/// - `work_duration` is the [`WorkDuration`] structure of times the thread group took to
199+
/// complete its last task breaking down into different components.
200+
///
201+
/// The work period start timestamp and actual total duration must be greater than zero.
202+
///
203+
/// The actual CPU and GPU durations must be greater than or equal to [`Duration::ZERO`], and
204+
/// at least one of them must be greater than [`Duration::ZERO`]. When one of them is equal to
205+
/// [`Duration::ZERO`], it means that type of work was not measured for this workload.
206+
///
207+
/// # Returns
208+
/// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed.
209+
#[cfg(feature = "api-level-35")]
210+
#[doc(alias = "APerformanceHint_reportActualWorkDuration2")]
211+
pub fn report_actual_work_duration2(&self, work_duration: &WorkDuration) -> Result<()> {
212+
status_to_io_result(unsafe {
213+
ffi::APerformanceHint_reportActualWorkDuration2(
214+
self.ptr.as_ptr(),
215+
work_duration.ptr.as_ptr(),
216+
)
217+
})
218+
}
219+
}
220+
221+
impl Drop for PerformanceHintSession {
222+
/// Release the performance hint manager pointer acquired via
223+
/// [`PerformanceHintManager::create_session()`].
224+
#[doc(alias = "APerformanceHint_closeSession")]
225+
fn drop(&mut self) {
226+
unsafe { ffi::APerformanceHint_closeSession(self.ptr.as_ptr()) }
227+
}
228+
}
229+
230+
#[cfg(feature = "api-level-35")]
231+
#[derive(Debug, PartialEq, Eq, Hash)]
232+
#[doc(alias = "AWorkDuration")]
233+
pub struct WorkDuration {
234+
ptr: NonNull<ffi::AWorkDuration>,
235+
}
236+
237+
#[cfg(feature = "api-level-35")]
238+
impl WorkDuration {
239+
/// Creates a new [`WorkDuration`]. When the client finishes using [`WorkDuration`], it will
240+
/// automatically be released on [`drop()`].
241+
#[doc(alias = "AWorkDuration_create")]
242+
pub fn new() -> Self {
243+
Self {
244+
ptr: NonNull::new(unsafe { ffi::AWorkDuration_create() })
245+
.expect("AWorkDuration_create should not return NULL"),
246+
}
247+
}
248+
249+
/// Sets the work period start timestamp in nanoseconds.
250+
///
251+
/// `work_period_start_timestamp` is the work period start timestamp based on
252+
/// [`ffi::CLOCK_MONOTONIC`] about when the work starts. This timestamp must be greater than
253+
/// [`Duration::ZERO`].
254+
#[doc(alias = "AWorkDuration_setWorkPeriodStartTimestampNanos")]
255+
pub fn set_work_period_start_timestamp(&self, work_period_start_timestamp: Duration) {
256+
unsafe {
257+
ffi::AWorkDuration_setWorkPeriodStartTimestampNanos(
258+
self.ptr.as_ptr(),
259+
work_period_start_timestamp
260+
.as_nanos()
261+
.try_into()
262+
.expect("Duration should be convertible to i64 nanoseconds"),
263+
)
264+
}
265+
}
266+
267+
/// Sets the actual total work duration in nanoseconds.
268+
///
269+
/// `actual_total_duration` is the actual total work duration. This number must be greater
270+
/// than [`Duration::ZERO`].
271+
#[doc(alias = "AWorkDuration_setActualTotalDurationNanos")]
272+
pub fn set_actual_total_duration(&self, actual_total_duration: Duration) {
273+
unsafe {
274+
ffi::AWorkDuration_setActualTotalDurationNanos(
275+
self.ptr.as_ptr(),
276+
actual_total_duration
277+
.as_nanos()
278+
.try_into()
279+
.expect("Duration should be convertible to i64 nanoseconds"),
280+
)
281+
}
282+
}
283+
284+
/// Sets the actual CPU work duration in nanoseconds.
285+
///
286+
/// `actual_cpu_duration` is the actual CPU work duration. If it is equal to
287+
/// [`Duration::ZERO`], that means the CPU was not measured.
288+
#[doc(alias = "AWorkDuration_setActualCpuDurationNanos")]
289+
pub fn set_actual_cpu_duration(&self, actual_cpu_duration: Duration) {
290+
unsafe {
291+
ffi::AWorkDuration_setActualCpuDurationNanos(
292+
self.ptr.as_ptr(),
293+
actual_cpu_duration
294+
.as_nanos()
295+
.try_into()
296+
.expect("Duration should be convertible to i64 nanoseconds"),
297+
)
298+
}
299+
}
300+
301+
/// Sets the actual GPU work duration in nanoseconds.
302+
///
303+
/// `actual_gpu_duration` is the actual GPU work duration. If it is equal to
304+
/// [`Duration::ZERO`], that means the GPU was not measured.
305+
#[doc(alias = "AWorkDuration_setActualGpuDurationNanos")]
306+
pub fn set_actual_gpu_duration(&self, actual_gpu_duration: Duration) {
307+
unsafe {
308+
ffi::AWorkDuration_setActualGpuDurationNanos(
309+
self.ptr.as_ptr(),
310+
actual_gpu_duration
311+
.as_nanos()
312+
.try_into()
313+
.expect("Duration should be convertible to i64 nanoseconds"),
314+
)
315+
}
316+
}
317+
}
318+
319+
impl Default for WorkDuration {
320+
#[doc(alias = "AWorkDuration_create")]
321+
fn default() -> Self {
322+
Self::new()
323+
}
324+
}
325+
326+
impl Drop for WorkDuration {
327+
/// Destroys [`WorkDuration`] and free all resources associated to it.
328+
#[doc(alias = "AWorkDuration_release")]
329+
fn drop(&mut self) {
330+
unsafe { ffi::AWorkDuration_release(self.ptr.as_ptr()) }
331+
}
332+
}

0 commit comments

Comments
 (0)