|
| 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