Skip to content

Commit 7d41fdb

Browse files
authored
feat(ffi): add DVT bindings for energy_monitor, graphics, notifications (#88)
1 parent 3d46f2c commit 7d41fdb

7 files changed

Lines changed: 559 additions & 17 deletions

File tree

ffi/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ dvt = ["idevice/dvt"]
3838
device_info = ["idevice/device_info"]
3939
application_listing = ["idevice/application_listing"]
4040
condition_inducer = ["idevice/condition_inducer"]
41+
energy_monitor = ["idevice/energy_monitor"]
42+
graphics = ["idevice/graphics"]
4143
network_monitor = ["idevice/network_monitor"]
4244
sysmontap = ["idevice/sysmontap"]
4345
heartbeat = ["idevice/heartbeat"]
@@ -80,6 +82,8 @@ full = [
8082
"device_info",
8183
"application_listing",
8284
"condition_inducer",
85+
"energy_monitor",
86+
"graphics",
8387
"network_monitor",
8488
"sysmontap",
8589
"heartbeat",

ffi/src/dvt/energy_monitor.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// Jackson Coxson
2+
3+
use std::ptr::null_mut;
4+
5+
use idevice::{
6+
ReadWrite,
7+
dvt::energy_monitor::{EnergyMonitorClient, EnergySample},
8+
};
9+
10+
use crate::{IdeviceFfiError, dvt::remote_server::RemoteServerHandle, ffi_err, run_sync};
11+
12+
pub struct EnergyMonitorHandle<'a>(pub EnergyMonitorClient<'a, Box<dyn ReadWrite>>);
13+
14+
/// A parsed per-PID energy sample
15+
#[repr(C)]
16+
pub struct IdeviceEnergySample {
17+
pub pid: u32,
18+
pub timestamp: i64,
19+
pub total_energy: f64,
20+
pub cpu_energy: f64,
21+
pub gpu_energy: f64,
22+
pub networking_energy: f64,
23+
pub display_energy: f64,
24+
pub location_energy: f64,
25+
pub appstate_energy: f64,
26+
}
27+
28+
/// Creates a new EnergyMonitorClient from a RemoteServerClient
29+
///
30+
/// # Safety
31+
/// `server` must be a valid pointer to a handle allocated by this library
32+
/// `handle` must be a valid pointer to a location where the handle will be stored
33+
#[unsafe(no_mangle)]
34+
pub unsafe extern "C" fn energy_monitor_new(
35+
server: *mut RemoteServerHandle,
36+
handle: *mut *mut EnergyMonitorHandle<'static>,
37+
) -> *mut IdeviceFfiError {
38+
if server.is_null() || handle.is_null() {
39+
return ffi_err!(IdeviceError::FfiInvalidArg);
40+
}
41+
42+
let server = unsafe { &mut (*server).0 };
43+
let res = run_sync(async move { EnergyMonitorClient::new(server).await });
44+
45+
match res {
46+
Ok(client) => {
47+
let boxed = Box::new(EnergyMonitorHandle(client));
48+
unsafe { *handle = Box::into_raw(boxed) };
49+
null_mut()
50+
}
51+
Err(e) => ffi_err!(e),
52+
}
53+
}
54+
55+
/// Frees an EnergyMonitorClient handle
56+
///
57+
/// # Safety
58+
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
59+
#[unsafe(no_mangle)]
60+
pub unsafe extern "C" fn energy_monitor_free(handle: *mut EnergyMonitorHandle<'static>) {
61+
if !handle.is_null() {
62+
let _ = unsafe { Box::from_raw(handle) };
63+
}
64+
}
65+
66+
/// Starts energy sampling for the given PIDs.
67+
///
68+
/// # Safety
69+
/// `handle` must be a valid pointer to a handle allocated by this library.
70+
/// If `pids` is non-null it must point to at least `pids_count` readable `u32` values.
71+
#[unsafe(no_mangle)]
72+
pub unsafe extern "C" fn energy_monitor_start_sampling(
73+
handle: *mut EnergyMonitorHandle<'static>,
74+
pids: *const u32,
75+
pids_count: usize,
76+
) -> *mut IdeviceFfiError {
77+
if handle.is_null() {
78+
return ffi_err!(IdeviceError::FfiInvalidArg);
79+
}
80+
81+
let pids_vec: Vec<u32> = if pids.is_null() || pids_count == 0 {
82+
Vec::new()
83+
} else {
84+
unsafe { std::slice::from_raw_parts(pids, pids_count) }.to_vec()
85+
};
86+
87+
let client = unsafe { &mut (*handle).0 };
88+
let res = run_sync(async move { client.start_sampling(&pids_vec).await });
89+
90+
match res {
91+
Ok(_) => null_mut(),
92+
Err(e) => ffi_err!(e),
93+
}
94+
}
95+
96+
/// Stops energy sampling for the given PIDs.
97+
///
98+
/// # Safety
99+
/// `handle` must be a valid pointer to a handle allocated by this library.
100+
/// If `pids` is non-null it must point to at least `pids_count` readable `u32` values.
101+
#[unsafe(no_mangle)]
102+
pub unsafe extern "C" fn energy_monitor_stop_sampling(
103+
handle: *mut EnergyMonitorHandle<'static>,
104+
pids: *const u32,
105+
pids_count: usize,
106+
) -> *mut IdeviceFfiError {
107+
if handle.is_null() {
108+
return ffi_err!(IdeviceError::FfiInvalidArg);
109+
}
110+
111+
let pids_vec: Vec<u32> = if pids.is_null() || pids_count == 0 {
112+
Vec::new()
113+
} else {
114+
unsafe { std::slice::from_raw_parts(pids, pids_count) }.to_vec()
115+
};
116+
117+
let client = unsafe { &mut (*handle).0 };
118+
let res = run_sync(async move { client.stop_sampling(&pids_vec).await });
119+
120+
match res {
121+
Ok(_) => null_mut(),
122+
Err(e) => ffi_err!(e),
123+
}
124+
}
125+
126+
/// Requests a one-shot energy sample and parses the response.
127+
///
128+
/// # Arguments
129+
/// * [`handle`] - The EnergyMonitorClient handle
130+
/// * [`pids`] - Pointer to an array of u32 PIDs to sample
131+
/// * [`pids_count`] - Number of elements in `pids`
132+
/// * [`samples_out`] - On success, set to a heap-allocated array of IdeviceEnergySample
133+
/// * [`samples_count_out`] - On success, set to the number of samples
134+
///
135+
/// # Returns
136+
/// An IdeviceFfiError on error, null on success
137+
///
138+
/// # Safety
139+
/// All output pointers must be valid and non-null. Free the array with
140+
/// `energy_monitor_samples_free`.
141+
#[unsafe(no_mangle)]
142+
pub unsafe extern "C" fn energy_monitor_sample_attributes(
143+
handle: *mut EnergyMonitorHandle<'static>,
144+
pids: *const u32,
145+
pids_count: usize,
146+
samples_out: *mut *mut IdeviceEnergySample,
147+
samples_count_out: *mut usize,
148+
) -> *mut IdeviceFfiError {
149+
if handle.is_null() || samples_out.is_null() || samples_count_out.is_null() {
150+
return ffi_err!(IdeviceError::FfiInvalidArg);
151+
}
152+
153+
let pids_vec: Vec<u32> = if pids.is_null() || pids_count == 0 {
154+
Vec::new()
155+
} else {
156+
unsafe { std::slice::from_raw_parts(pids, pids_count) }.to_vec()
157+
};
158+
159+
let client = unsafe { &mut (*handle).0 };
160+
let bytes = match run_sync(async move { client.sample_attributes(&pids_vec).await }) {
161+
Ok(b) => b,
162+
Err(e) => return ffi_err!(e),
163+
};
164+
165+
let samples = match EnergySample::from_bytes(&bytes) {
166+
Ok(v) => v,
167+
Err(e) => return ffi_err!(e),
168+
};
169+
170+
let mut c_samples: Box<[IdeviceEnergySample]> = samples
171+
.into_iter()
172+
.map(|s| IdeviceEnergySample {
173+
pid: s.pid,
174+
timestamp: s.timestamp,
175+
total_energy: s.total_energy,
176+
cpu_energy: s.cpu_energy,
177+
gpu_energy: s.gpu_energy,
178+
networking_energy: s.networking_energy,
179+
display_energy: s.display_energy,
180+
location_energy: s.location_energy,
181+
appstate_energy: s.appstate_energy,
182+
})
183+
.collect::<Vec<_>>()
184+
.into_boxed_slice();
185+
186+
unsafe {
187+
*samples_out = c_samples.as_mut_ptr();
188+
*samples_count_out = c_samples.len();
189+
}
190+
std::mem::forget(c_samples);
191+
null_mut()
192+
}
193+
194+
/// Frees an array of IdeviceEnergySample allocated by `energy_monitor_sample_attributes`.
195+
///
196+
/// # Safety
197+
/// `samples` must be a pointer returned by this library with the matching `count`, or NULL
198+
#[unsafe(no_mangle)]
199+
pub unsafe extern "C" fn energy_monitor_samples_free(
200+
samples: *mut IdeviceEnergySample,
201+
count: usize,
202+
) {
203+
if samples.is_null() {
204+
return;
205+
}
206+
let _ = unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(samples, count)) };
207+
}

ffi/src/dvt/graphics.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Jackson Coxson
2+
3+
use std::{ffi::CString, ptr::null_mut};
4+
5+
use idevice::{ReadWrite, dvt::graphics::GraphicsClient};
6+
7+
use crate::{IdeviceFfiError, dvt::remote_server::RemoteServerHandle, ffi_err, run_sync};
8+
9+
pub struct GraphicsHandle<'a>(pub GraphicsClient<'a, Box<dyn ReadWrite>>);
10+
11+
/// A graphics sample from tddhe GPU instruments channel
12+
#[repr(C)]
13+
pub struct IdeviceGraphicsSample {
14+
pub timestamp: u64,
15+
pub fps: f64,
16+
pub alloc_system_memory: u64,
17+
pub in_use_system_memory: u64,
18+
pub in_use_system_memory_driver: u64,
19+
pub gpu_bundle_name: *mut std::ffi::c_char,
20+
pub recovery_count: u64,
21+
}
22+
23+
/// Frees an IdeviceGraphicsSample and its heap-allocated string field
24+
///
25+
/// # Safety
26+
/// `sample` must be a valid pointer allocated by this library or NULL
27+
#[unsafe(no_mangle)]
28+
pub unsafe extern "C" fn graphics_sample_free(sample: *mut IdeviceGraphicsSample) {
29+
if sample.is_null() {
30+
return;
31+
}
32+
let s = unsafe { Box::from_raw(sample) };
33+
if !s.gpu_bundle_name.is_null() {
34+
let _ = unsafe { CString::from_raw(s.gpu_bundle_name) };
35+
}
36+
}
37+
38+
/// Creates a new GraphicsClient from a RemoteServerClient
39+
///
40+
/// # Safety
41+
/// `server` must be a valid pointer to a handle allocated by this library
42+
/// `handle` must be a valid pointer to a location where the handle will be stored
43+
#[unsafe(no_mangle)]
44+
pub unsafe extern "C" fn graphics_new(
45+
server: *mut RemoteServerHandle,
46+
handle: *mut *mut GraphicsHandle<'static>,
47+
) -> *mut IdeviceFfiError {
48+
if server.is_null() || handle.is_null() {
49+
return ffi_err!(IdeviceError::FfiInvalidArg);
50+
}
51+
52+
let server = unsafe { &mut (*server).0 };
53+
let res = run_sync(async move { GraphicsClient::new(server).await });
54+
55+
match res {
56+
Ok(client) => {
57+
let boxed = Box::new(GraphicsHandle(client));
58+
unsafe { *handle = Box::into_raw(boxed) };
59+
null_mut()
60+
}
61+
Err(e) => ffi_err!(e),
62+
}
63+
}
64+
65+
/// Frees a GraphicsClient handle
66+
///
67+
/// # Safety
68+
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
69+
#[unsafe(no_mangle)]
70+
pub unsafe extern "C" fn graphics_free(handle: *mut GraphicsHandle<'static>) {
71+
if !handle.is_null() {
72+
let _ = unsafe { Box::from_raw(handle) };
73+
}
74+
}
75+
76+
/// Starts graphics sampling at the given interval. Consumes the device's initial reply internally.
77+
///
78+
/// # Safety
79+
/// `handle` must be a valid pointer to a handle allocated by this library
80+
#[unsafe(no_mangle)]
81+
pub unsafe extern "C" fn graphics_start_sampling(
82+
handle: *mut GraphicsHandle<'static>,
83+
interval: f64,
84+
) -> *mut IdeviceFfiError {
85+
if handle.is_null() {
86+
return ffi_err!(IdeviceError::FfiInvalidArg);
87+
}
88+
89+
let client = unsafe { &mut (*handle).0 };
90+
let res = run_sync(async move { client.start_sampling(interval).await });
91+
92+
match res {
93+
Ok(_) => null_mut(),
94+
Err(e) => ffi_err!(e),
95+
}
96+
}
97+
98+
/// Stops graphics sampling.
99+
///
100+
/// # Safety
101+
/// `handle` must be a valid pointer to a handle allocated by this library
102+
#[unsafe(no_mangle)]
103+
pub unsafe extern "C" fn graphics_stop_sampling(
104+
handle: *mut GraphicsHandle<'static>,
105+
) -> *mut IdeviceFfiError {
106+
if handle.is_null() {
107+
return ffi_err!(IdeviceError::FfiInvalidArg);
108+
}
109+
110+
let client = unsafe { &mut (*handle).0 };
111+
let res = run_sync(async move { client.stop_sampling().await });
112+
113+
match res {
114+
Ok(_) => null_mut(),
115+
Err(e) => ffi_err!(e),
116+
}
117+
}
118+
119+
/// Reads the next graphics data frame pushed by the device. Blocks until a frame arrives.
120+
///
121+
/// # Arguments
122+
/// * [`handle`] - The GraphicsClient handle
123+
/// * [`sample_out`] - On success, set to a heap-allocated IdeviceGraphicsSample
124+
///
125+
/// # Returns
126+
/// An IdeviceFfiError on error, null on success
127+
///
128+
/// # Safety
129+
/// All pointers must be valid and non-null. Free the sample with `graphics_sample_free`.
130+
#[unsafe(no_mangle)]
131+
pub unsafe extern "C" fn graphics_next_sample(
132+
handle: *mut GraphicsHandle<'static>,
133+
sample_out: *mut *mut IdeviceGraphicsSample,
134+
) -> *mut IdeviceFfiError {
135+
if handle.is_null() || sample_out.is_null() {
136+
return ffi_err!(IdeviceError::FfiInvalidArg);
137+
}
138+
139+
let client = unsafe { &mut (*handle).0 };
140+
let res = run_sync(async move { client.sample().await });
141+
142+
match res {
143+
Ok(sample) => {
144+
let c_sample = IdeviceGraphicsSample {
145+
timestamp: sample.timestamp,
146+
fps: sample.fps,
147+
alloc_system_memory: sample.alloc_system_memory,
148+
in_use_system_memory: sample.in_use_system_memory,
149+
in_use_system_memory_driver: sample.in_use_system_memory_driver,
150+
gpu_bundle_name: CString::new(sample.gpu_bundle_name)
151+
.unwrap_or_default()
152+
.into_raw(),
153+
recovery_count: sample.recovery_count,
154+
};
155+
unsafe { *sample_out = Box::into_raw(Box::new(c_sample)) };
156+
null_mut()
157+
}
158+
Err(e) => ffi_err!(e),
159+
}
160+
}

0 commit comments

Comments
 (0)