blob: d46dcebb5deeecea0ae4cae142a7effce5824294 [file]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! This crate provides functionality for tracking CPU cycles and power consumption
//! per UID on Android, utilizing eBPF programs and Intel RAPL interface.
use anyhow::{anyhow, bail, Result};
use libbpf_rs::{num_possible_cpus, MapCore, MapFlags, MapHandle};
use std::collections::HashMap;
use std::ffi::CString;
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::sync::Mutex;
use log::{error, info, warn};
const LOG_TAG: &str = "cpucycleperuid_rust";
const PKG_POWER_PATH: &str = "/sys/class/powercap/intel-rapl:0/energy_uj";
const MAX_PKG_POWER_PATH: &str = "/sys/class/powercap/intel-rapl:0/max_energy_range_uj";
const BPF_PROG_PATH: &str = "/sys/fs/bpf/cpucycleperuid/prog_cyclePerUid_tp_sched_switch";
const LAST_RECORDED_CYCLE_MAP_PATH: &str =
"/sys/fs/bpf/cpucycleperuid/map_cyclePerUid_last_recorded_cycle_map";
const LAST_RUNNING_PID_MAP_PATH: &str =
"/sys/fs/bpf/cpucycleperuid/map_cyclePerUid_last_running_pid_map";
const UID_CPU_CYCLE_MAP_PATH: &str = "/sys/fs/bpf/cpucycleperuid/map_cyclePerUid_uid_cpu_cycle_map";
const TSC_EVENTS_PATH: &str = "/sys/fs/bpf/cpucycleperuid/map_cyclePerUid_tsc_events";
const DESYNC_COUNTER_MAP_PATH: &str = "/sys/fs/bpf/cpucycleperuid/map_cyclePerUid_desync_counter";
const EVENT_TYPE: &str = "sched";
const EVENT_NAME: &str = "sched_switch";
static TRACKER: Mutex<Option<CpuCycleTracker>> = Mutex::new(None);
/// Tracks CPU cycles and power consumption per UID.
pub(crate) struct CpuCycleTracker {
tracking: bool,
last_recorded_cycle_map: MapHandle,
last_running_pid_map: MapHandle,
uid_cpu_cycle_map: MapHandle,
_tsc_events_map: MapHandle,
desync_counter_map: MapHandle,
pkg_power_file: File,
_perf_event_fds: Vec<OwnedFd>, // For TSC events
last_package_energy: u64,
max_package_energy: u64,
last_uid_cpu_cycles: HashMap<u32, u64>,
tracepoint_link_fd: Option<OwnedFd>,
}
impl CpuCycleTracker {
/// Creates a new CpuCycleTracker instance.
pub(crate) fn new() -> Result<Self> {
if !is_rapl_available() {
error!(
"{}: init_globals: RAPL interface not found at {}, aborting BPF setup.",
LOG_TAG, PKG_POWER_PATH
);
bail!("RAPL interface not available");
}
let last_recorded_cycle_map = MapHandle::from_pinned_path(LAST_RECORDED_CYCLE_MAP_PATH)?;
let last_running_pid_map = MapHandle::from_pinned_path(LAST_RUNNING_PID_MAP_PATH)?;
let uid_cpu_cycle_map = MapHandle::from_pinned_path(UID_CPU_CYCLE_MAP_PATH)?;
let desync_counter_map = MapHandle::from_pinned_path(DESYNC_COUNTER_MAP_PATH)?;
let (perf_event_fds, tsc_events_map) = setup_tsc_events()?;
let max_package_energy = read_sysfs_file(MAX_PKG_POWER_PATH)?.trim().parse::<i64>()? as u64;
if max_package_energy == 0 {
error!("{}: Failed to read max package energy from {}", LOG_TAG, MAX_PKG_POWER_PATH);
bail!("Failed to read max package energy");
}
info!("{}: Max package energy: {}", LOG_TAG, max_package_energy);
let pkg_power_file = OpenOptions::new().read(true).open(PKG_POWER_PATH)?;
Ok(Self {
tracking: false,
last_recorded_cycle_map,
last_running_pid_map,
uid_cpu_cycle_map,
_tsc_events_map: tsc_events_map,
desync_counter_map,
pkg_power_file,
_perf_event_fds: perf_event_fds,
last_package_energy: 0,
max_package_energy,
last_uid_cpu_cycles: HashMap::new(),
tracepoint_link_fd: None,
})
}
/// Starts the BPF tracking by initializing globals and attaching the tracepoint program.
pub(crate) fn start_tracking(&mut self) -> Result<bool> {
info!("{}: Starting BPF tracking", LOG_TAG);
if self.tracking {
return Ok(true);
}
self.init_bpf_maps()?;
match attach_tracepoint_program() {
Ok(link_fd) => {
self.tracepoint_link_fd = link_fd;
}
Err(e) => {
let errno = errno::errno().0;
error!("{}: Failed to attach tracepoint: {} (errno: {})", LOG_TAG, e, errno);
return Err(e); // Propagate the error
}
}
self.tracking = true;
Ok(true)
}
/// Stops the BPF tracking by detaching the BPF program and cleaning up resources.
pub(crate) fn stop_tracking(&mut self) {
if !self.tracking {
return;
}
self.tracking = false;
if let Some(fd) = self.tracepoint_link_fd.take() {
// SAFETY: bpf_link_detach is safe to call with a valid link FD.
// We guarantee it's valid because it's an OwnedFd we got from bpf_link_create.
let ret = unsafe { libbpf_sys::bpf_link_detach(fd.as_raw_fd()) };
if ret < 0 {
let err = errno::errno().0;
error!("{}: Failed to detach link FD {:?}: {} (errno: {})", LOG_TAG, fd, ret, err);
}
// fd is dropped here and automatically closed, expressing clear ownership.
}
info!("{} BPF Tracking Stopped", LOG_TAG);
}
/// Reads the current total package energy consumption from the RAPL interface.
pub(crate) fn read_package_power(&mut self) -> Result<i64> {
read_number_from_file_handle(&mut self.pkg_power_file)
}
/// Reads the last recorded TSC value from the BPF map.
pub(crate) fn read_last_recorded_cycle(&self) -> Result<i64> {
let num_cpus = num_possible_cpus()
.map_err(|e| anyhow!("Failed to get number of possible CPUs: {}", e))?;
let key = (0u32).to_ne_bytes(); // Key for the percpu array
let per_cpu_byte_values = self
.last_recorded_cycle_map
.lookup_percpu(&key, MapFlags::ANY)?
.ok_or(anyhow!("Map entry not found"))?;
if per_cpu_byte_values.len() != num_cpus {
error!(
"{}: lookup_percpu returned {} entries, expected {}",
LOG_TAG,
per_cpu_byte_values.len(),
num_cpus
);
// Continue anyway, but this is unexpected
}
let mut max_cycle = 0u64;
for (cpu_id, cpu_val_bytes) in per_cpu_byte_values.iter().enumerate() {
if cpu_val_bytes.len() == std::mem::size_of::<u64>() {
let val = u64::from_ne_bytes(cpu_val_bytes.as_slice().try_into().unwrap());
if val > max_cycle {
max_cycle = val;
}
} else {
error!(
"{}: Unexpected value size for CPU {}: {}",
LOG_TAG,
cpu_id,
cpu_val_bytes.len()
);
}
}
Ok(max_cycle as i64)
}
/// Reads the accumulated desync count from the BPF map.
pub(crate) fn read_desync_count(&self) -> Result<u64> {
let num_cpus = num_possible_cpus()
.map_err(|e| anyhow!("Failed to get number of possible CPUs: {}", e))?;
let key = (0u32).to_ne_bytes(); // Key for the percpu array
let per_cpu_byte_values = self
.desync_counter_map
.lookup_percpu(&key, MapFlags::ANY)?
.ok_or(anyhow!("Map entry not found"))?;
if per_cpu_byte_values.len() != num_cpus {
error!(
"{}: lookup_percpu returned {} entries, expected {}",
LOG_TAG,
per_cpu_byte_values.len(),
num_cpus
);
// Continue anyway, but this is unexpected
}
let mut total_count = 0u64;
for (cpu_id, cpu_val_bytes) in per_cpu_byte_values.iter().enumerate() {
if cpu_val_bytes.len() == std::mem::size_of::<u64>() {
total_count += u64::from_ne_bytes(cpu_val_bytes.as_slice().try_into().unwrap());
} else {
error!(
"{}: Unexpected value size for CPU {}: {}",
LOG_TAG,
cpu_id,
cpu_val_bytes.len()
);
}
}
Ok(total_count)
}
/// Reads the accumulated CPU cycles for each UID from the BPF map.
pub(crate) fn read_uid_cpu_cycles(&self) -> Result<HashMap<u32, u64>> {
let mut ret = HashMap::new();
let map = &self.uid_cpu_cycle_map;
for key_bytes in map.keys() {
let uid = u32::from_ne_bytes(key_bytes.clone().try_into().unwrap());
if let Some(per_cpu_values) = map.lookup_percpu(&key_bytes, MapFlags::ANY)? {
let mut total_cycles = 0u64;
for cpu_val_bytes in per_cpu_values {
if cpu_val_bytes.len() == std::mem::size_of::<u64>() {
total_cycles +=
u64::from_ne_bytes(cpu_val_bytes.as_slice().try_into().unwrap());
} else {
error!(
"{}: Unexpected value size for PERCPU_HASH entry for UID {}",
LOG_TAG, uid
);
}
}
ret.insert(uid, total_cycles);
} else {
error!("{}: Failed to look up key for UID {}", LOG_TAG, uid);
}
}
Ok(ret)
}
/// Calculates the energy delta consumed by each UID since the last call.
pub(crate) fn read_uid_power_delta(&mut self) -> Result<HashMap<u32, u64>> {
let mut power_delta = HashMap::new();
// 1. Read Current Package Energy
let current_energy = self.read_package_power()? as u64;
// 2. Read Current PID CPU Times
let current_uid_cpu_cycles = self.read_uid_cpu_cycles()?;
// 3. Calculate Deltas
let energy_delta = if self.last_package_energy > 0 {
if current_energy >= self.last_package_energy {
current_energy - self.last_package_energy
} else {
// RAPL wrap around case
(self.max_package_energy - self.last_package_energy) + current_energy
}
} else {
0
};
let mut total_cpu_cycle_delta = 0;
let mut uid_cpu_cycle_deltas = HashMap::new();
for (uid, current_cycle) in &current_uid_cpu_cycles {
let last_cycle = self.last_uid_cpu_cycles.get(uid).unwrap_or(&0);
if current_cycle > last_cycle {
let delta = current_cycle - last_cycle;
uid_cpu_cycle_deltas.insert(*uid, delta);
total_cpu_cycle_delta += delta;
} else if current_cycle < last_cycle {
error!(
"{}: UID {}: current_cycle < last_cycle ({} < {}), skipping delta",
LOG_TAG, uid, current_cycle, last_cycle
);
}
}
// 4. Attribute Energy
if energy_delta > 0 && total_cpu_cycle_delta > 0 {
for (uid, cpu_delta) in &uid_cpu_cycle_deltas {
let uid_power =
(*cpu_delta as f64 / total_cpu_cycle_delta as f64 * energy_delta as f64) as u64;
power_delta.insert(*uid, uid_power);
}
}
// 5. Update Previous Values
self.last_uid_cpu_cycles = current_uid_cpu_cycles;
self.last_package_energy = current_energy;
Ok(power_delta)
}
// Initializes the BPF maps, clearing any existing entries.
fn init_bpf_maps(&self) -> Result<()> {
let num_cpus = num_possible_cpus()
.map_err(|e| anyhow!("Failed to get number of possible CPUs: {}", e))?;
if num_cpus == 0 {
bail!("Failed to get number of possible CPUs");
}
let zero32 = 0u32.to_ne_bytes();
let zeroed_val: Vec<u8> = 0u64.to_ne_bytes().to_vec();
let values: Vec<Vec<u8>> = vec![zeroed_val; num_cpus];
self.last_recorded_cycle_map.update_percpu(&zero32, &values, MapFlags::ANY)?;
let zeroed_val_pid: Vec<u8> = 0i32.to_ne_bytes().to_vec(); // pid_t is i32
let values_pid: Vec<Vec<u8>> = vec![zeroed_val_pid; num_cpus];
self.last_running_pid_map.update_percpu(&zero32, &values_pid, MapFlags::ANY)?;
self.desync_counter_map.update_percpu(&zero32, &values, MapFlags::ANY)?;
for key_bytes in self.uid_cpu_cycle_map.keys() {
self.uid_cpu_cycle_map.delete(&key_bytes)?;
}
info!("{}: BPF maps initialized", LOG_TAG);
Ok(())
}
}
impl Drop for CpuCycleTracker {
fn drop(&mut self) {
self.stop_tracking();
}
}
// Wrapper around the perf_event_open syscall.
fn perf_event_open(
hw_event: *mut libbpf_sys::perf_event_attr,
pid: libc::pid_t,
cpu: libc::c_int,
group_fd: libc::c_int,
flags: libc::c_ulong,
) -> Result<OwnedFd, i32> {
// SAFETY: `libc::syscall` is safe to call with valid arguments for `SYS_perf_event_open`.
// The kernel handles the system call, and the arguments are correctly prepared.
let fd: RawFd =
unsafe { libc::syscall(libc::SYS_perf_event_open, hw_event, pid, cpu, group_fd, flags) }
.try_into()
.map_err(|_| errno::errno().0)?;
if fd < 0 {
let err = errno::errno().0;
error!("{}: perf_event_open failed for cpu {}: {}", LOG_TAG, cpu, err);
Err(err)
} else {
// SAFETY: The file descriptor `fd` is a valid, newly created file descriptor
// from the `perf_event_open` syscall, and we are taking ownership of it.
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
}
// Reads a single i64 value from an open file.
fn read_number_from_file_handle(file: &mut File) -> Result<i64> {
// Rewind to the beginning of the file
file.seek(SeekFrom::Start(0))?;
let mut buf = [0u8; 64];
let bytes_read = file.read(&mut buf)?;
let data = std::str::from_utf8(&buf[..bytes_read])?.trim();
Ok(data.parse()?)
}
fn read_sysfs_file(file_path: &str) -> Result<String> {
let mut file = File::open(file_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
if contents.is_empty() {
error!("{}: Empty file: {}", LOG_TAG, file_path);
return Err(anyhow!("Empty file: {}", file_path));
}
Ok(contents)
}
/// Checks if the Intel RAPL interface for package power is available.
pub(crate) fn is_rapl_available() -> bool {
let exists = std::path::Path::new(PKG_POWER_PATH).exists();
exists
}
// Attaches the BPF program to the specified tracepoint.
fn attach_tracepoint_program() -> Result<Option<OwnedFd>> {
// Get FD for the pinned program
let prog_path_cstr = CString::new(BPF_PROG_PATH).unwrap();
let opts = libbpf_sys::bpf_obj_get_opts {
sz: std::mem::size_of::<libbpf_sys::bpf_obj_get_opts>() as u64,
file_flags: libbpf_sys::BPF_F_RDONLY,
..Default::default()
};
// SAFETY: bpf_obj_get_opts is safe to call with a valid CString path and initialized options.
// The kernel will return a valid FD or a negative error code.
let prog_raw_fd = unsafe { libbpf_sys::bpf_obj_get_opts(prog_path_cstr.as_ptr(), &opts) };
if prog_raw_fd < 0 {
error!(
"{}: Failed to get program FD from {}: {} (errno: {})",
LOG_TAG,
BPF_PROG_PATH,
prog_raw_fd,
errno::errno().0
);
bail!(
"Failed to get program FD from {}: {} (errno: {})",
BPF_PROG_PATH,
prog_raw_fd,
errno::errno().0
);
}
// SAFETY: prog_raw_fd is a valid file descriptor obtained from bpf_obj_get_opts if >= 0.
let prog_fd = unsafe { OwnedFd::from_raw_fd(prog_raw_fd) };
// For tp_btf (BPF_PROG_TYPE_TRACING + BPF_TRACE_RAW_TP), we attach using bpf_link_create.
// The target_fd is 0 because the target BTF ID is embedded in the program.
// attach_type must be BPF_TRACE_RAW_TP.
// SAFETY: bpf_link_create is called with a valid program FD and correct attach parameters.
// null_mut() is a safe value for the opts parameter as it indicates no additional options.
let link_fd = unsafe {
libbpf_sys::bpf_link_create(
prog_fd.as_raw_fd(),
0,
libbpf_sys::BPF_TRACE_RAW_TP,
std::ptr::null_mut(),
)
};
if link_fd < 0 {
let err = errno::errno().0;
if err == libc::EEXIST {
warn!(
"{}: Tracepoint {}/{} already has a BPF program attached (EEXIST). Continuing.",
LOG_TAG, EVENT_TYPE, EVENT_NAME
);
return Ok(None);
} else if err == libc::EBUSY {
warn!("{}: Tracepoint {}/{} is busy (EBUSY).", LOG_TAG, EVENT_TYPE, EVENT_NAME);
}
error!("{}: Failed to attach tp_btf {}: {} (errno: {})", LOG_TAG, EVENT_NAME, link_fd, err);
bail!("Failed to attach tp_btf {}: {} (errno: {})", EVENT_NAME, link_fd, err);
}
info!("{}: Successfully attached tp_btf {} with link FD {}", LOG_TAG, EVENT_NAME, link_fd);
// SAFETY: link_fd is a valid file descriptor obtained from bpf_link_create if >= 0.
Ok(Some(unsafe { OwnedFd::from_raw_fd(link_fd) }))
}
// Sets up perf events for reading the Time Stamp Counter (TSC) on each CPU.
fn setup_tsc_events() -> Result<(Vec<OwnedFd>, MapHandle)> {
let mut pe_attr = libbpf_sys::perf_event_attr {
type_: libbpf_sys::PERF_TYPE_HARDWARE,
size: std::mem::size_of::<libbpf_sys::perf_event_attr>() as u32,
config: libbpf_sys::PERF_COUNT_HW_CPU_CYCLES as u64,
..Default::default()
};
pe_attr.set_disabled(0);
let num_cpus =
num_possible_cpus().map_err(|e| anyhow!("Failed to get number of possible CPUs: {}", e))?;
if num_cpus == 0 {
bail!("Failed to get number of possible CPUs");
}
let mut perf_event_fds = Vec::with_capacity(num_cpus);
for cpu_id in 0..num_cpus {
let fd = perf_event_open(&mut pe_attr, -1, cpu_id as libc::c_int, -1, 0)
.map_err(|e| anyhow!("Failed to open perf event for CPU {}: {}", cpu_id, e))?;
perf_event_fds.push(fd);
}
let map = MapHandle::from_pinned_path(TSC_EVENTS_PATH)?;
for (cpu_id, fd) in perf_event_fds.iter().enumerate() {
let map_key = (cpu_id as u32).to_ne_bytes();
let fd_bytes = fd.as_raw_fd().to_ne_bytes();
map.update(&map_key, &fd_bytes, MapFlags::ANY)?;
}
info!("{}: Successfully set up TSC perf events and populated BPF map.", LOG_TAG);
Ok((perf_event_fds, map))
}
/// Checks if the required BPF program for cycle per UID tracking is loaded.
pub(crate) fn is_bpf_program_present() -> bool {
if let Ok(guard) = TRACKER.lock() {
if let Some(tracker) = guard.as_ref() {
if tracker.tracking {
return true;
}
}
}
let exists = std::path::Path::new(BPF_PROG_PATH).exists();
exists
}
/// C-compatible wrapper for `is_rapl_available`.
#[no_mangle]
pub extern "C" fn rust_is_rapl_available() -> bool {
is_rapl_available()
}
/// C-compatible wrapper for `is_bpf_program_present`.
#[no_mangle]
pub extern "C" fn rust_is_bpf_program_present() -> bool {
is_bpf_program_present()
}
/// C-compatible wrapper for `start_tracking`.
#[no_mangle]
pub extern "C" fn rust_start_tracking() -> bool {
let mut guard = match TRACKER.lock() {
Ok(g) => g,
Err(e) => {
error!("{}: Failed to lock tracker: {}", LOG_TAG, e);
return false;
}
};
if guard.is_none() {
match CpuCycleTracker::new() {
Ok(tracker) => *guard = Some(tracker),
Err(e) => {
error!("{}: Failed to initialize CpuCycleTracker: {}", LOG_TAG, e);
return false;
}
}
}
if let Some(tracker) = guard.as_mut() {
match tracker.start_tracking() {
Ok(ret) => ret,
Err(e) => {
error!("{}: Failed to start tracking: {}", LOG_TAG, e);
false
}
}
} else {
false
}
}
/// C-compatible wrapper for `stop_tracking`.
#[no_mangle]
pub extern "C" fn rust_stop_tracking() {
let mut guard = match TRACKER.lock() {
Ok(g) => g,
Err(e) => {
error!("{}: Failed to lock tracker: {}", LOG_TAG, e);
return;
}
};
if let Some(tracker) = guard.as_mut() {
tracker.stop_tracking();
}
}
/// C-compatible wrapper for `is_tracking`.
#[no_mangle]
pub extern "C" fn rust_is_tracking() -> bool {
let guard = match TRACKER.lock() {
Ok(g) => g,
Err(e) => {
error!("{}: Failed to lock tracker: {}", LOG_TAG, e);
return false;
}
};
if let Some(tracker) = guard.as_ref() {
tracker.tracking
} else {
false
}
}
/// C-compatible wrapper for `read_package_power`.
#[no_mangle]
pub extern "C" fn rust_read_package_power() -> i64 {
let mut guard = match TRACKER.lock() {
Ok(g) => g,
Err(e) => {
error!("{}: Failed to lock tracker: {}", LOG_TAG, e);
return -1;
}
};
if guard.is_none() {
match CpuCycleTracker::new() {
Ok(tracker) => *guard = Some(tracker),
Err(e) => {
error!("{}: Failed to initialize CpuCycleTracker: {}", LOG_TAG, e);
return -1;
}
}
}
if let Some(tracker) = guard.as_mut() {
tracker.read_package_power().unwrap_or(-1)
} else {
-1
}
}
/// C-compatible wrapper for `read_last_recorded_cycle`.
#[no_mangle]
pub extern "C" fn rust_read_last_recorded_cycle() -> i64 {
let mut guard = match TRACKER.lock() {
Ok(g) => g,
Err(e) => {
error!("{}: Failed to lock tracker: {}", LOG_TAG, e);
return -1;
}
};
if guard.is_none() {
match CpuCycleTracker::new() {
Ok(tracker) => *guard = Some(tracker),
Err(e) => {
error!("{}: Failed to initialize CpuCycleTracker: {}", LOG_TAG, e);
return -1;
}
}
}
if let Some(tracker) = guard.as_ref() {
tracker.read_last_recorded_cycle().unwrap_or(-1)
} else {
-1
}
}
/// C-compatible wrapper for `read_desync_count`.
#[no_mangle]
pub extern "C" fn rust_read_desync_count() -> i64 {
let mut guard = match TRACKER.lock() {
Ok(g) => g,
Err(e) => {
error!("{}: Failed to lock tracker: {}", LOG_TAG, e);
return -1;
}
};
if guard.is_none() {
match CpuCycleTracker::new() {
Ok(tracker) => *guard = Some(tracker),
Err(e) => {
error!("{}: Failed to initialize CpuCycleTracker: {}", LOG_TAG, e);
return -1;
}
}
}
if let Some(tracker) = guard.as_ref() {
tracker.read_desync_count().unwrap_or(0) as i64
} else {
-1
}
}
const MAX_TRACKED_UIDS: usize = cycleperuid_defs::MAX_TRACKED_UIDS as usize;
const FFI_ARRAY_SIZE: usize = MAX_TRACKED_UIDS * 2;
/// C-compatible wrapper for `read_uid_cpu_cycles`.
///
/// # Safety
///
/// The caller must ensure that the `buffer` pointer is valid and points to a
/// mutable memory region of at least `buffer_len` * sizeof(u64) bytes.
/// The `buffer_len` must be at least FFI_ARRAY_SIZE.
///
/// The buffer does not need to be initialized before the call. The function will write
/// values into it and return the number of elements successfully written. Rust code
/// can assume the returned number of elements are initialized.
///
/// The buffer pointer is not retained beyond the lifetime of this function.
#[no_mangle]
pub unsafe extern "C" fn rust_read_uid_cpu_cycles(buffer: *mut u64, buffer_len: usize) -> usize {
if buffer.is_null() || buffer_len < FFI_ARRAY_SIZE {
error!(
"{}: Buffer is null or too small. Provided: {}, Required: {}",
LOG_TAG, buffer_len, FFI_ARRAY_SIZE
);
return 0;
}
let mut guard = match TRACKER.lock() {
Ok(g) => g,
Err(e) => {
error!("{}: Failed to lock tracker: {}", LOG_TAG, e);
return 0;
}
};
if guard.is_none() {
match CpuCycleTracker::new() {
Ok(tracker) => *guard = Some(tracker),
Err(e) => {
error!("{}: Failed to initialize CpuCycleTracker: {}", LOG_TAG, e);
return 0;
}
}
}
let cycles = match guard.as_ref().unwrap().read_uid_cpu_cycles() {
Ok(cumulative_cycles) => cumulative_cycles,
Err(e) => {
error!("{}: Error in read_uid_cpu_cycles: {}", LOG_TAG, e);
return 0;
}
};
// SAFETY: The caller guarantees that the `buffer` pointer is valid and
// points to a mutable memory region of at least `buffer_len` * sizeof(u64) bytes.
let buffer_slice = unsafe { std::slice::from_raw_parts_mut(buffer, buffer_len) };
let mut count = 0;
for (uid, cycle) in cycles {
if count < MAX_TRACKED_UIDS {
let base_index = count * 2;
buffer_slice[base_index] = uid as u64;
buffer_slice[base_index + 1] = cycle;
count += 1;
} else {
error!("{}: Exceeded MAX_TRACKED_UIDS, stopping buffer fill", LOG_TAG);
break;
}
}
count * 2
}
/// C-compatible wrapper for `read_uid_power_delta`.
///
/// # Safety
///
/// The caller must ensure that the `buffer` pointer is valid and points to a
/// mutable memory region of at least `buffer_len` * sizeof(u64) bytes.
/// The `buffer_len` must be at least FFI_ARRAY_SIZE.
///
/// The buffer does not need to be initialized before the call. The function will write
/// values into it and return the number of elements successfully written. Rust code
/// can assume the returned number of elements are initialized.
///
/// The buffer pointer is not retained beyond the lifetime of this function.
#[no_mangle]
pub unsafe extern "C" fn rust_read_uid_power_delta(buffer: *mut u64, buffer_len: usize) -> usize {
if buffer.is_null() || buffer_len < FFI_ARRAY_SIZE {
error!(
"{}: Buffer is null or too small. Provided: {}, Required: {}",
LOG_TAG, buffer_len, FFI_ARRAY_SIZE
);
return 0;
}
let mut guard = match TRACKER.lock() {
Ok(g) => g,
Err(e) => {
error!("{}: Failed to lock tracker: {}", LOG_TAG, e);
return 0;
}
};
if guard.is_none() {
match CpuCycleTracker::new() {
Ok(tracker) => *guard = Some(tracker),
Err(e) => {
error!("{}: Failed to initialize CpuCycleTracker: {}", LOG_TAG, e);
return 0;
}
}
}
let power_deltas = match guard.as_mut().unwrap().read_uid_power_delta() {
Ok(deltas) => deltas,
Err(e) => {
error!("{}: Error in read_uid_power_delta: {}", LOG_TAG, e);
return 0;
}
};
if power_deltas.len() > MAX_TRACKED_UIDS {
error!("{}: More UIDs than expected: {}", LOG_TAG, power_deltas.len());
}
// SAFETY: The caller guarantees that the `buffer` pointer is valid.
let buffer_slice = unsafe { std::slice::from_raw_parts_mut(buffer, buffer_len) };
let mut count = 0;
for (uid, power) in power_deltas {
if count < MAX_TRACKED_UIDS {
buffer_slice[count * 2] = uid as u64;
buffer_slice[count * 2 + 1] = power;
count += 1;
} else {
error!("{}: Exceeded MAX_TRACKED_UIDS, stopping buffer fill", LOG_TAG);
break;
}
}
count * 2
}
#[cfg(test)]
mod tests;