darwin-metrics
darwin-metrics
is a Rust library that provides native access to macOS system metrics through low-level system APIs. This crate offers efficient, safe, and async-capable interfaces for monitoring system resources on macOS.
Overview
The purpose of darwin-metrics
is to give Rust developers easy access to macOS system information without having to deal with the complexities of FFI, Objective-C bridging, or the intricacies of Apple's frameworks. It serves as a Rust-native wrapper around various macOS APIs like IOKit, Foundation, Metal, and more.
Features
- CPU Monitoring: Usage, frequency, temperature, and core information
- Memory Analysis: RAM usage, swap space, memory pressure, and page state tracking
- GPU Information: Model detection, utilization metrics, VRAM usage
- Process Monitoring: Resource usage stats, process enumeration, and hierarchies
- Storage Metrics: Disk space utilization and I/O performance
- Power Management: Battery status and power consumption data
- Thermal Monitoring: System-wide temperature sensors
Design Philosophy
The library follows several important design principles:
- Safety First: All unsafe FFI code is properly encapsulated, providing a safe Rust API to users
- Performance: Minimizes overhead by using efficient access patterns and sharing resources
- Modularity: Each system component is contained in its own module with clear APIs
- Error Handling: Comprehensive error handling with specific error types
- Async Support: Provides async interfaces where appropriate for non-blocking operations
Platform Compatibility
This library is specifically designed for macOS systems. It has been tested on:
- macOS Sonoma (14.x)
- macOS Sequoia (15.x)
Note: Because
darwin-metrics
uses platform-specific APIs, it will not work on non-macOS systems.
Getting Started
This guide will help you get started with using darwin-metrics
in your Rust projects.
Installation
Add darwin-metrics
to your Cargo.toml
:
[dependencies]
darwin-metrics = "0.1.5"
Requirements
- macOS El Capitan (10.11) or later
- Rust 1.85 or later
- Xcode Command Line Tools (for macOS system headers)
Basic Usage
Here's a simple example showing how to retrieve basic system metrics:
// Basic example of darwin-metrics usage
use darwin_metrics::{hardware, process};
use std::time::Duration;
fn main() -> darwin_metrics::Result<()> {
// Get CPU information
let cpu_info = hardware::cpu::Cpu::get_metrics()?;
println!("CPU Usage: {}%", cpu_info.usage);
// Get memory information
let memory = hardware::memory::Memory::get_info()?;
println!("Memory Used: {} bytes", memory.used);
println!("Memory Available: {} bytes", memory.available);
// Get process information
let processes = process::Process::get_all().unwrap();
println!("Running processes: {}", processes.len());
Ok(())
}
Using Feature Flags
darwin-metrics
is organized with feature flags to allow you to include only the modules you need:
[dependencies]
darwin-metrics = { version = "0.1.5", features = ["cpu", "memory"] }
Available features include:
cpu
- CPU monitoringmemory
- Memory statisticsgpu
- GPU monitoringprocess
- Process informationthermal
- Temperature sensorspower
- Power and battery information
Async Support
For non-blocking operations, darwin-metrics
provides async functionality:
// Async example with Tokio
use darwin_metrics::{hardware, process};
use std::time::Duration;
use futures::stream::StreamExt; // For the StreamExt trait
#[tokio::main]
async fn main() -> darwin_metrics::Result<()> {
// Monitor process metrics over time
let pid = std::process::id();
let mut stream = process::Process::monitor_metrics(pid, Duration::from_secs(1));
// Process the metrics stream
while let Some(process) = stream.next().await {
if let Ok(proc) = process {
println!("Process: {} - CPU: {}%, Memory: {}",
proc.name, proc.cpu_usage, proc.memory_usage);
}
}
Ok(())
}
Error Handling
darwin-metrics
provides a robust error handling system that helps you understand what went wrong:
// Error handling example
use darwin_metrics::{Error, Result};
// This is just a hypothetical function to demonstrate error handling
fn potentially_failing_function() -> Result<String> {
Ok(String::from("success"))
}
fn example() -> Result<()> {
let result = potentially_failing_function();
match result {
Ok(value) => println!("Success: {}", value),
Err(Error::NotAvailable(msg)) => println!("Resource not available: {}", msg),
Err(Error::System(msg)) => println!("System error: {}", msg),
Err(Error::Process(msg)) => println!("Process error: {}", msg),
Err(err) => println!("Other error: {}", err),
}
Ok(())
}
Next Steps
Explore the documentation for each module to learn more about available metrics and functionality:
CPU Monitoring
The CPU module in darwin-metrics provides a comprehensive API for monitoring CPU metrics on macOS systems. This module allows you to track CPU usage, temperature, frequency, and other important processor metrics in real-time.
Overview
The CPU monitoring functionality is centered around the CPU
struct, which provides access to a variety of processor metrics through a clean, ergonomic API. The module also includes the CpuMetrics
trait, which defines a standard interface for retrieving common CPU statistics.
Features
- Core Statistics: Physical and logical core counts
- Usage Monitoring: Overall and per-core CPU utilization tracking
- Temperature Sensing: CPU temperature readings (when available)
- Frequency Analysis: Current, minimum, and maximum CPU frequencies
- Model Information: CPU model identification
Quick Start
Here's a simple example to get you started with CPU monitoring:
// Basic CPU monitoring example
use darwin_metrics::hardware::cpu::CPU;
use darwin_metrics::error::Result;
fn main() -> Result<()> {
// Initialize the CPU monitor
let cpu = CPU::new()?;
// Display basic CPU information
println!("CPU Model: {}", cpu.model_name());
println!("Physical Cores: {}", cpu.physical_cores());
println!("Logical Cores: {}", cpu.logical_cores());
// Show current CPU metrics
println!("CPU Usage: {:.1}%", cpu.get_cpu_usage() * 100.0);
println!("CPU Frequency: {:.0} MHz", cpu.frequency_mhz());
if let Some(temp) = cpu.temperature() {
println!("CPU Temperature: {:.1}°C", temp);
}
// Display per-core usage
for (i, usage) in cpu.core_usage().iter().enumerate() {
println!("Core {} Usage: {:.1}%", i, usage * 100.0);
}
Ok(())
}
The CPU
Struct
The CPU
struct is the primary interface for CPU monitoring. It provides methods to retrieve various CPU metrics:
Initialization
// Create a new CPU instance with current metrics
let cpu = CPU::new()?;
Basic Properties
// Get the CPU model name
let model = cpu.model_name(); // e.g., "Apple M1 Pro"
// Get core counts
let physical = cpu.physical_cores(); // e.g., 8
let logical = cpu.logical_cores(); // e.g., 10
Performance Metrics
// Get the current CPU frequency in MHz
let freq = cpu.frequency_mhz(); // e.g., 3200.0
// Get overall CPU usage (0.0 to 1.0)
let usage = cpu.get_cpu_usage(); // e.g., 0.35 (35%)
// Get per-core usage as a slice
let core_usage = cpu.core_usage(); // e.g., [0.4, 0.2, 0.6, 0.1, ...]
// Get CPU temperature (if available)
if let Some(temp) = cpu.temperature() {
println!("CPU is running at {:.1}°C", temp);
}
Updating Metrics
To get the latest CPU metrics, you can update the instance:
// Refresh all CPU metrics
cpu.update()?;
Frequency Monitoring
For detailed CPU frequency information, darwin-metrics provides comprehensive access through both the CPU
struct directly and the standalone FrequencyMonitor
:
Using the CPU Struct (Recommended)
use darwin_metrics::hardware::cpu::CPU;
use darwin_metrics::error::Result;
fn monitor_cpu_frequency() -> Result<()> {
let cpu = CPU::new()?;
// Get current frequency
println!("Current frequency: {:.0} MHz", cpu.frequency_mhz());
// Get detailed frequency metrics (min/max/steps)
if let Some(min) = cpu.min_frequency_mhz() {
println!("Min frequency: {:.0} MHz", min);
}
if let Some(max) = cpu.max_frequency_mhz() {
println!("Max frequency: {:.0} MHz", max);
}
// Get all available frequency steps
if let Some(steps) = cpu.available_frequencies() {
println!("Available frequency steps: {:?} MHz", steps);
}
// Access the complete frequency metrics object
if let Some(metrics) = cpu.frequency_metrics() {
println!("Current: {:.0} MHz", metrics.current);
println!("Min: {:.0} MHz", metrics.min);
println!("Max: {:.0} MHz", metrics.max);
println!("Available steps: {:?} MHz", metrics.available);
}
Ok(())
}
Using the FrequencyMonitor Directly
use darwin_metrics::hardware::cpu::FrequencyMonitor;
use darwin_metrics::error::Result;
fn monitor_frequency() -> Result<()> {
let monitor = FrequencyMonitor::new();
let metrics = monitor.get_metrics()?;
println!("Current: {:.0} MHz", metrics.current);
println!("Min: {:.0} MHz", metrics.min);
println!("Max: {:.0} MHz", metrics.max);
println!("Available steps: {:?} MHz", metrics.available);
Ok(())
}
Platform-Specific Notes
macOS Implementation Details
On macOS, the CPU module uses a combination of:
- IOKit Framework: For accessing low-level hardware information
- AppleACPICPU Service: For core count and CPU model information
- System Management Controller (SMC): For temperature readings
- sysctl: For retrieving accurate CPU frequency information
hw.cpufrequency
for current frequencyhw.cpufrequency_min
for minimum frequencyhw.cpufrequency_max
for maximum frequency
Temperature Monitoring
Temperature readings are available on most modern Mac hardware, but may be unavailable on:
- Older Mac models
- Virtual machines
- Some Mac models with specific security restrictions
When temperature readings are not available, the temperature()
method returns None
.
Performance Considerations
- The
CPU
struct caches metrics untilupdate()
is called - For continuous monitoring, call
update()
periodically (e.g., every 1-2 seconds) - Avoid calling
update()
too frequently as it involves system calls
Error Handling
Most methods that interact with the system return a Result
type that should be handled appropriately:
match CPU::new() {
Ok(cpu) => {
// Use the CPU instance
},
Err(e) => {
eprintln!("Failed to initialize CPU monitoring: {}", e);
}
}
Advanced Usage
Implementing the CpuMetrics Trait
You can implement the CpuMetrics
trait for your own types to provide a consistent interface:
use darwin_metrics::hardware::cpu::CpuMetrics;
struct MyCpuMonitor {
usage: f64,
temperature: Option<f64>,
frequency: f64,
}
impl CpuMetrics for MyCpuMonitor {
fn get_cpu_usage(&self) -> f64 {
self.usage // Return stored value
}
fn get_cpu_temperature(&self) -> Option<f64> {
self.temperature // Return stored temperature if available
}
fn get_cpu_frequency(&self) -> f64 {
self.frequency // Return stored frequency
}
}
Integration Examples
Periodic Monitoring
use std::thread;
use std::time::Duration;
use darwin_metrics::hardware::cpu::CPU;
use darwin_metrics::error::Result;
fn monitor_cpu() -> Result<()> {
let mut cpu = CPU::new()?;
for _ in 0..10 {
// Update metrics
cpu.update()?;
// Display current usage
println!("CPU Usage: {:.1}%", cpu.get_cpu_usage() * 100.0);
// Wait before next update
thread::sleep(Duration::from_secs(1));
}
Ok(())
}
Usage with Async
use tokio::time::{interval, Duration};
use darwin_metrics::hardware::cpu::CPU;
use darwin_metrics::error::Result;
async fn monitor_cpu_async() -> Result<()> {
let mut cpu = CPU::new()?;
let mut interval = interval(Duration::from_secs(1));
for _ in 0..10 {
interval.tick().await;
cpu.update()?;
println!("CPU: {:.1}%", cpu.get_cpu_usage() * 100.0);
}
Ok(())
}
Memory Analysis
The memory module provides detailed information about system memory usage, memory pressure, and swap activity. It offers both synchronous and asynchronous APIs for monitoring memory metrics with a focus on reliability and performance.
Features
- System Memory Metrics: Total, available, and used memory with wired memory tracking
- Page State Tracking: Active, inactive, wired, free, and compressed memory page states
- Memory Pressure Monitoring: Real-time memory pressure monitoring with configurable thresholds
- Pressure Level Callbacks: Register callbacks to be notified when memory pressure levels change
- Swap Usage Monitoring: Track swap usage, activity rates, and pressure
- Asynchronous Support: Full async API for non-blocking memory metrics collection
- Memory History: Built-in tracking of memory usage history
- Resilient Implementation: Graceful fallbacks for environments where certain metrics aren't available
Usage Examples
Basic Memory Information
use darwin_metrics::hardware::memory::Memory;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new Memory instance
let memory = Memory::new()?;
// Get basic memory metrics
println!("Total Memory: {:.2} GB", memory.total as f64 / 1_073_741_824.0);
println!("Used Memory: {:.2} GB", memory.used as f64 / 1_073_741_824.0);
println!("Available Memory: {:.2} GB", memory.available as f64 / 1_073_741_824.0);
println!("Memory Usage: {:.1}%", memory.usage_percentage());
// Check memory pressure
println!("Memory Pressure: {:.1}%", memory.pressure_percentage());
println!("Memory Pressure Level: {:?}", memory.pressure_level());
Ok(())
}
Monitoring Memory Changes
use darwin_metrics::hardware::memory::Memory;
use std::thread;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new Memory instance
let mut memory = Memory::new()?;
// Monitor memory for a period of time
for _ in 0..5 {
// Update memory metrics
memory.update()?;
println!("Memory Usage: {:.1}%", memory.usage_percentage());
println!("Memory Pressure: {:.1}%", memory.pressure_percentage());
// Wait before the next update
thread::sleep(Duration::from_secs(2));
}
Ok(())
}
Memory Pressure Callbacks
use darwin_metrics::hardware::memory::{Memory, PressureLevel};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new Memory instance
let mut memory = Memory::new()?;
// Register a callback for memory pressure changes
memory.on_pressure_change(|level| {
match level {
PressureLevel::Normal => println!("Memory pressure is NORMAL"),
PressureLevel::Warning => println!("Memory pressure is HIGH - consider closing applications"),
PressureLevel::Critical => println!("Memory pressure is CRITICAL - system may be unstable"),
}
});
// Set custom pressure thresholds (warning at 50%, critical at 80%)
memory.set_pressure_thresholds(0.5, 0.8)?;
// Update and trigger checks
memory.update()?;
Ok(())
}
Asynchronous Memory Monitoring
use darwin_metrics::hardware::memory::Memory;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new Memory instance
let mut memory = Memory::new()?;
// Start monitoring memory pressure asynchronously
let monitor = memory.start_monitoring(500).await?;
println!("Started memory pressure monitoring...");
// Update asynchronously
for _ in 0..5 {
memory.update_async().await?;
println!("Memory Usage: {:.1}%", memory.usage_percentage());
tokio::time::sleep(Duration::from_secs(2)).await;
}
// Stop monitoring when done
monitor.stop();
Ok(())
}
Page States and Memory Classifications
The Memory
struct provides detailed information about different memory page states:
- Active: Memory currently in active use
- Inactive: Memory that hasn't been accessed recently
- Wired: Memory that can't be paged out (kernel, etc.)
- Free: Memory immediately available for allocation
- Compressed: Memory that has been compressed to save physical RAM
These metrics help you understand not just how much memory is in use, but how it's being used and managed by the system.
Swap Usage and Memory Pressure
In macOS, swap usage and memory pressure are important metrics for understanding system performance:
- Swap Usage: Tracks total, used, and free swap space
- Swap Activity: Monitors swap-in and swap-out rates
- Swap Pressure: Shows percentage of swap utilization
- Memory Pressure: Overall system memory pressure indicator
High memory pressure often precedes increased swap activity and can indicate potential performance issues.
API Reference
For complete API details, see the Rust API documentation.
GPU Metrics
The GPU
module in darwin-metrics
provides comprehensive monitoring of GPU resources on macOS systems, including integrated GPUs on Apple Silicon and discrete GPUs on Intel-based Macs.
Features
The GPU module offers the following metrics:
- GPU Utilization: Current GPU usage as a percentage (0-100%)
- Memory Usage: Used, free, and total GPU memory
- Temperature: Current GPU temperature in degrees Celsius (where available)
- Device Information: GPU model name and device information
- Fallback Mechanisms: Works across different Mac models with graceful degradation
Implementation Details
The GPU metrics are obtained through a combination of macOS frameworks:
- IOKit: Primary source of GPU statistics including utilization, memory usage, and thermal information
- Metal: Used as a fallback for GPU name retrieval
- SMC (System Management Controller): Used for accessing temperature data
The module handles Apple Silicon systems (with unified memory architecture) differently than Intel-based Macs with discrete GPUs.
Usage Examples
Basic GPU Information
use darwin_metrics::hardware::gpu::GPU;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize GPU monitoring
let gpu = GPU::new()?;
// Get basic GPU information
let name = gpu.name()?;
println!("GPU: {}", name);
// Get current utilization
let utilization = gpu.utilization()?;
println!("GPU Utilization: {:.1}%", utilization);
// Get memory information
let memory = gpu.memory_info()?;
println!("Memory: {}/{} MB used",
memory.used / 1024 / 1024,
memory.total / 1024 / 1024
);
// Get temperature (if available)
match gpu.temperature() {
Ok(temp) => println!("Temperature: {:.1}°C", temp),
Err(_) => println!("Temperature: Not available"),
}
Ok(())
}
Complete GPU Metrics
For a more comprehensive approach, you can get all metrics at once:
use darwin_metrics::hardware::gpu::GPU;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let gpu = GPU::new()?;
// Get all metrics in one call
let metrics = gpu.metrics()?;
println!("GPU: {}", metrics.name);
println!("Utilization: {:.1}%", metrics.utilization);
if let Some(temp) = metrics.temperature {
println!("Temperature: {:.1}°C", temp);
}
println!("Memory: {}/{} MB used ({:.1}%)",
metrics.memory.used / 1024 / 1024,
metrics.memory.total / 1024 / 1024,
(metrics.memory.used as f64 / metrics.memory.total as f64) * 100.0
);
Ok(())
}
Handling Different Mac Models
The GPU module handles different Mac models with varying hardware support:
- Apple Silicon (M1/M2/M3): Correctly identifies unified memory architecture
- Intel Macs with discrete GPUs: Reports accurate GPU model and memory information
- Older Mac models: Provides fallbacks for missing metrics
Error Handling
The module implements robust error handling for varied environments:
- Graceful degradation when specific metrics are unavailable
- Comprehensive error types with context for debugging
- Safe memory management with autorelease pools to prevent leaks and crashes
Memory Management
The GPU module uses the following techniques to ensure memory safety:
- Autorelease Pools: All IOKit and Objective-C calls are wrapped in autorelease pools to properly manage temporary objects
- Reference Counting: Proper retain/release patterns for Core Foundation objects
- Safe FFI: Careful validation of foreign function interface calls
- Resource Cleanup: Explicit cleanup of IOKit resources after use
These techniques prevent memory leaks and address previous issues with segmentation faults that occurred during testing and teardown phases. The implementation follows Apple's guidelines for memory management in mixed Rust/Objective-C code.
Complete Examples
The repository includes several GPU monitoring examples that demonstrate different approaches:
-
examples/gpu_static.rs
: Provides basic GPU information with minimal IOKit calls- One-time retrieval of GPU model, utilization, and memory
- Suitable for simple system information tools
-
examples/gpu_monitor_simple.rs
: A simulation-based GPU monitor- Uses a simple approach that doesn't rely heavily on IOKit
- Good for testing and development
-
examples/gpu_monitor_safe.rs
: A system load-based GPU metrics estimator- Uses proper autoreleasepool management to prevent memory leaks
- Demonstrates safe Objective-C interoperability
-
examples/gpu_monitor.rs
: A full-featured GPU monitor- Real-time GPU monitoring with automatic updates
- Visual representation of utilization and memory usage
- Robust error handling
- Proper memory management with autoreleasepool
These examples show progressively more complex implementations, from basic static information to real-time monitoring with proper memory management.
Disk Monitoring
The disk
module in darwin-metrics
provides comprehensive monitoring of storage volumes and disk I/O performance on macOS systems.
Features
The disk monitoring module offers the following capabilities:
- Volume Information: Details about mounted filesystems including capacity, usage, and mount points
- Disk I/O Performance: Read/write operations per second, throughput, and latency metrics
- Disk Type Detection: Identification of SSD, HDD, Fusion drives, and other storage types
- Per-Volume Metrics: Track individual volumes, partitions, and mount points
- Path-Based Lookups: Find which volume contains a specific file or directory
Usage Examples
Basic Volume Information
use darwin_metrics::disk::Disk;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get information about all mounted volumes
let volumes = Disk::get_all()?;
for volume in volumes {
println!("Volume: {}", volume.name);
println!(" Mount point: {}", volume.mount_point);
println!(" Filesystem: {}", volume.fs_type);
println!(" Capacity: {}", volume.total_display());
println!(" Used: {} ({}%)",
volume.used_display(),
volume.usage_percentage() as u32);
println!(" Available: {}", volume.available_display());
if volume.is_nearly_full() {
println!(" WARNING: Volume is nearly full!");
}
println!();
}
// Get information about the root filesystem
let root = Disk::get_info()?;
println!("Root filesystem: {}", root.summary());
// Get information about a specific path
let home = Disk::get_for_path("/Users")?;
println!("Home directory volume: {}", home.summary());
Ok(())
}
Disk Performance Monitoring
use darwin_metrics::disk::DiskMonitor;
use std::thread::sleep;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize the disk monitor
let mut monitor = DiskMonitor::new();
// Sample disk performance every second
for _ in 0..10 {
match monitor.get_performance() {
Ok(performance) => {
for (device, perf) in performance {
println!("Device: {}", device);
println!(" Read: {:.1} ops/s, {:.2} MB/s",
perf.reads_per_second,
perf.bytes_read_per_second as f64 / (1024.0 * 1024.0));
println!(" Write: {:.1} ops/s, {:.2} MB/s",
perf.writes_per_second,
perf.bytes_written_per_second as f64 / (1024.0 * 1024.0));
println!(" Latency: {:.2} ms read, {:.2} ms write",
perf.read_latency_ms,
perf.write_latency_ms);
println!(" Utilization: {:.1}%", perf.utilization);
println!();
}
},
Err(e) => println!("Error: {}", e),
}
// Update internal statistics
monitor.update()?;
// Wait for next sample
sleep(Duration::from_secs(1));
}
Ok(())
}
Implementation Details
The disk module uses several macOS APIs to gather comprehensive storage metrics:
- statfs: For basic volume information like capacity and usage
- IOKit: For disk performance metrics and device type detection
- DiskArbitration framework: For additional volume metadata
- FSEvents: For monitoring filesystem changes (future enhancement)
Performance Considerations
Disk I/O monitoring is designed to be lightweight, with a minimal performance impact on the system:
- Incremental updates track deltas between samples rather than absolute values
- Smart caching of filesystem metadata to reduce syscalls
- Configurable sampling rate to balance detail with overhead
Advanced Features
Disk Type Detection
The module can detect various storage types:
use darwin_metrics::disk::{Disk, DiskType};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let volumes = Disk::get_all()?;
for volume in volumes {
let type_description = match volume.disk_type {
DiskType::SSD => "Solid State Drive",
DiskType::HDD => "Hard Disk Drive",
DiskType::Fusion => "Fusion Drive",
DiskType::External => "External Drive",
DiskType::Network => "Network Volume",
DiskType::RAM => "RAM Disk",
DiskType::Virtual => "Virtual Disk",
DiskType::Unknown => "Unknown Type",
};
println!("{}: {}", volume.name, type_description);
}
Ok(())
}
Complete Example
For a full-featured example of disk monitoring, see the examples/disk_monitor.rs
file in the repository, which demonstrates:
- Real-time volume information display
- I/O performance tracking
- Visual representation of disk usage and I/O rates
- Handling of error conditions
Network Monitoring
The Network module in darwin-metrics
provides comprehensive monitoring for network interfaces and traffic statistics on macOS systems. It uses a combination of macOS native APIs and system frameworks to collect real-time information about network interfaces, their status, and data transfer metrics.
Features
- Interface Discovery: Automatically detect and monitor all network interfaces on macOS
- Interface Classification: Identify interface types (Ethernet, WiFi, Loopback, Virtual)
- Traffic Statistics: Track bytes and packets sent/received in real-time
- Error Monitoring: Track packet errors, collisions, and drops
- State Tracking: Monitor interface up/down status and flags
- Interface Information: Get MAC addresses, IP addresses, and interface capabilities
- Speed Calculation: Calculate real-time upload and download speeds
- Connection Monitoring: Track active network connections and their status
macOS Implementation Details
The Network module is specifically designed for macOS systems and uses:
- SystemConfiguration framework: For network interface enumeration and configuration
- Network framework: For modern network monitoring capabilities
- getifaddrs(): For IP/MAC address collection
- IOKit API: To determine interface capabilities and state
Usage Example
Here's a basic example of using the Network module to monitor interface traffic:
use darwin_metrics::network::{NetworkMonitor, NetworkMetrics};
use std::thread::sleep;
use std::time::Duration;
async fn main() -> darwin_metrics::Result<()> {
// Create a new network monitor
let mut monitor = NetworkMonitor::new().await?;
// Initial stats
println!("Initial network statistics:");
print_network_stats(&monitor);
// Sleep for a while to allow traffic to occur
sleep(Duration::from_secs(5));
// Update stats
monitor.refresh().await?;
// Print updated stats with speeds
println!("\nUpdated network statistics:");
print_network_stats(&monitor);
Ok(())
}
fn print_network_stats(monitor: &NetworkMonitor) {
for interface in monitor.interfaces() {
println!("Interface: {} ({})", interface.name(), interface.interface_type());
println!(" Status: {}", if interface.is_active() { "Active" } else { "Inactive" });
// Display MAC address if available
if let Some(mac) = interface.mac_address() {
println!(" MAC address: {}", mac);
}
// Traffic statistics
println!(" Download: {} bytes ({:.2} KB/s)",
interface.bytes_received(),
interface.download_speed() / 1024.0);
println!(" Upload: {} bytes ({:.2} KB/s)",
interface.bytes_sent(),
interface.upload_speed() / 1024.0);
println!(" Packets: {} received, {} sent",
interface.packets_received(),
interface.packets_sent());
// Error statistics
if interface.receive_errors() > 0 || interface.send_errors() > 0 {
println!(" Errors: {} receive, {} send",
interface.receive_errors(),
interface.send_errors());
println!(" Collisions: {}", interface.collisions());
}
// Print IP addresses if available
if let Some(addresses) = interface.addresses() {
println!(" IP Addresses:");
for addr in addresses {
println!(" {}", addr);
}
}
// Interface properties
println!(" Properties:");
println!(" Loopback: {}", interface.is_loopback());
println!(" Wireless: {}", interface.is_wireless());
println!(" Multicast: {}", interface.supports_multicast());
println!(" Broadcast: {}", interface.supports_broadcast());
println!("");
}
// Print total network usage
println!("Total network usage:");
println!(" Download speed: {:.2} KB/s", monitor.total_download_speed() / 1024.0);
println!(" Upload speed: {:.2} KB/s", monitor.total_upload_speed() / 1024.0);
}
API Overview
NetworkMonitor
The NetworkMonitor
is the main entry point for network monitoring:
use darwin_metrics::network::{NetworkMonitor, NetworkMetrics};
async fn example() -> darwin_metrics::Result<()> {
// Create a new NetworkMonitor
let mut monitor = NetworkMonitor::new().await?;
// Update network statistics
monitor.refresh().await?;
// Get all interfaces
let interfaces = monitor.interfaces();
// Get interface count
println!("Found {} network interfaces", interfaces.len());
// Get a specific interface by name
if let Some(wifi) = monitor.get_interface("en0") {
println!("WiFi download: {:.2} KB/s", wifi.download_speed() / 1024.0);
}
// Get total network speeds across all interfaces
println!("Total download: {:.2} MB/s", monitor.total_download_speed() / (1024.0 * 1024.0));
println!("Total upload: {:.2} MB/s", monitor.total_upload_speed() / (1024.0 * 1024.0));
// Get active connections
let connections = monitor.connections().await?;
println!("Active connections: {}", connections.len());
Ok(())
}
Interface Types
The InterfaceType
enum identifies different types of network interfaces:
// From darwin_metrics::network::interface
pub enum InterfaceType {
Ethernet, // Wired ethernet interfaces
WiFi, // Wireless interfaces
Loopback, // Loopback interface (lo0)
Virtual, // Virtual interfaces (utun, bridge)
Cellular, // Cellular network interfaces
Other, // Other/unknown interface types
}
Interface
The Interface
struct represents a single network interface with all its properties and metrics:
use darwin_metrics::network::{NetworkMonitor, NetworkMetrics};
use std::net::IpAddr;
async fn example() -> darwin_metrics::Result<()> {
let monitor = NetworkMonitor::new().await?;
// For each interface in the system
for interface in monitor.interfaces() {
// Basic information
println!("Name: {}", interface.name());
println!("Type: {}", interface.interface_type());
println!("Active: {}", interface.is_active());
// MAC address (if available)
if let Some(mac) = interface.mac_address() {
println!("MAC: {}", mac);
}
// IP addresses (if available)
if let Some(addrs) = interface.addresses() {
for addr in addrs {
println!("IP: {}", addr);
}
}
// Traffic statistics
println!("Bytes received: {}", interface.bytes_received());
println!("Bytes sent: {}", interface.bytes_sent());
println!("Packets received: {}", interface.packets_received());
println!("Packets sent: {}", interface.packets_sent());
// Error statistics
println!("Receive errors: {}", interface.receive_errors());
println!("Send errors: {}", interface.send_errors());
println!("Collisions: {}", interface.collisions());
// Speed calculations
println!("Download speed: {:.2} KB/s", interface.download_speed() / 1024.0);
println!("Upload speed: {:.2} KB/s", interface.upload_speed() / 1024.0);
println!("Packet receive rate: {:.2} pps", interface.packet_receive_rate());
println!("Packet send rate: {:.2} pps", interface.packet_send_rate());
// Interface properties
println!("Is loopback: {}", interface.is_loopback());
println!("Is wireless: {}", interface.is_wireless());
println!("Supports broadcast: {}", interface.supports_broadcast());
println!("Supports multicast: {}", interface.supports_multicast());
println!("Is point-to-point: {}", interface.is_point_to_point());
// Signal strength (for wireless interfaces)
if interface.is_wireless() {
if let Some(signal) = interface.signal_strength() {
println!("Signal strength: {} dBm", signal);
}
}
}
Ok(())
}
NetworkMetrics Trait
The NetworkMetrics
trait defines standard methods implemented by network-related types:
// From darwin_metrics::network module
pub trait NetworkMetrics {
// Data transfer metrics
fn bytes_received(&self) -> u64; // Total bytes received
fn bytes_sent(&self) -> u64; // Total bytes sent
fn packets_received(&self) -> u64; // Total packets received
fn packets_sent(&self) -> u64; // Total packets sent
// Error metrics
fn receive_errors(&self) -> u64; // Count of receive errors
fn send_errors(&self) -> u64; // Count of send errors
fn collisions(&self) -> u64; // Count of collisions
// Rate metrics
fn download_speed(&self) -> f64; // Current download speed in bytes/s
fn upload_speed(&self) -> f64; // Current upload speed in bytes/s
fn packet_receive_rate(&self) -> f64; // Packets per second received
fn packet_send_rate(&self) -> f64; // Packets per second sent
// Status
fn is_active(&self) -> bool; // Whether the interface is active
}
Connection Monitoring
The NetworkMonitor also provides functionality to track active network connections:
use darwin_metrics::network::{NetworkMonitor, Connection, ConnectionType};
async fn connection_monitoring() -> darwin_metrics::Result<()> {
let monitor = NetworkMonitor::new().await?;
// Get all active connections
let connections = monitor.connections().await?;
for conn in connections {
println!("Connection: {}:{} -> {}:{}",
conn.local_address(),
conn.local_port(),
conn.remote_address().unwrap_or_else(|| "N/A".to_string()),
conn.remote_port());
println!(" Type: {}", match conn.connection_type() {
ConnectionType::Tcp => "TCP",
ConnectionType::Udp => "UDP",
ConnectionType::Other => "Other"
});
println!(" State: {}", conn.state());
if let Some(process) = conn.process_name() {
println!(" Process: {} (PID: {})", process, conn.pid().unwrap_or(0));
}
}
Ok(())
}
Error Handling
Network operations can return the following error types:
Error::Network
: Errors related to network operationsError::NotAvailable
: When specific network metrics aren't availableError::System
: Underlying system errorsError::Permission
: When insufficient permissions exist to access network data
The implementation includes graceful fallbacks when some metrics aren't available, avoiding crashes or panics when running on different macOS environments.
Performance Considerations
- Update Frequency: For real-time monitoring, call
refresh()
at regular intervals (1-5 seconds) - Speed Calculations: Require at least two measurements over time
- Resource Usage: The implementation is designed to be lightweight with minimal system impact
- Thread Safety: The API is designed to be used safely with tokio's async runtime
- Caching Strategy: Some data is cached to reduce system calls and improve performance
Common Usage Patterns
-
Simple Interface Listing:
use darwin_metrics::network::{NetworkMonitor, NetworkMetrics}; async fn example_interface_listing() -> darwin_metrics::Result<()> { let monitor = NetworkMonitor::new().await?; for interface in monitor.interfaces() { println!("{}: {}", interface.name(), interface.interface_type()); } Ok(()) }
-
Bandwidth Monitoring:
use darwin_metrics::network::{NetworkMonitor, NetworkMetrics}; use std::time::Duration; use tokio::time::sleep; async fn example_bandwidth_monitoring() -> darwin_metrics::Result<()> { let mut monitor = NetworkMonitor::new().await?; // This would run forever in a real application for _ in 0..3 { monitor.refresh().await?; println!("Download: {:.2} MB/s", monitor.total_download_speed() / (1024.0 * 1024.0)); sleep(Duration::from_secs(1)).await; } Ok(()) }
-
Interface State Monitoring:
use darwin_metrics::network::{NetworkMonitor, NetworkMetrics}; async fn example_state_monitoring() -> darwin_metrics::Result<()> { let mut monitor = NetworkMonitor::new().await?; monitor.refresh().await?; if let Some(wifi) = monitor.get_interface("en0") { println!("WiFi is {}", if wifi.is_active() { "connected" } else { "disconnected" }); if wifi.is_active() && wifi.is_wireless() { if let Some(signal) = wifi.signal_strength() { println!("Signal strength: {} dBm", signal); } } } Ok(()) }
-
Traffic Usage Statistics:
use darwin_metrics::network::{NetworkMonitor, NetworkMetrics}; use tokio::time::sleep; use std::time::Duration; async fn example_traffic_usage() -> darwin_metrics::Result<()> { let mut monitor = NetworkMonitor::new().await?; let initial = monitor.interfaces().iter().map(|i| i.bytes_received()).sum::<u64>(); sleep(Duration::from_secs(5)).await; // Using 5 seconds for testing, would be longer in real usage monitor.refresh().await?; let final_bytes = monitor.interfaces().iter().map(|i| i.bytes_received()).sum::<u64>(); println!("Used {} MB in the last period", (final_bytes.saturating_sub(initial)) as f64 / (1024.0 * 1024.0)); Ok(()) }
-
Connection Monitoring:
use darwin_metrics::network::{NetworkMonitor, ConnectionType}; async fn example_connection_monitoring() -> darwin_metrics::Result<()> { let monitor = NetworkMonitor::new().await?; // Count connections by type let connections = monitor.connections().await?; let tcp_count = connections.iter() .filter(|c| c.connection_type() == ConnectionType::Tcp) .count(); let udp_count = connections.iter() .filter(|c| c.connection_type() == ConnectionType::Udp) .count(); println!("Active connections: {} TCP, {} UDP", tcp_count, udp_count); // Find connections for a specific process let browser_connections = connections.iter() .filter(|c| c.process_name().map_or(false, |name| name.contains("firefox") || name.contains("chrome"))) .count(); println!("Browser connections: {}", browser_connections); Ok(()) }
Process Monitoring
The process module provides comprehensive monitoring of processes running on your macOS system. It offers ways to enumerate running processes, track their resource usage, obtain detailed information about specific processes, and even visualize process hierarchies.
Basic Usage
// Basic process monitoring example
use darwin_metrics::process::Process;
use std::time::Duration;
// Get all running processes
let processes = Process::get_all().unwrap();
println!("Total processes: {}", processes.len());
// Get information about a specific process
let pid = std::process::id(); // Our own process ID
let process = Process::get_by_pid(pid).unwrap();
println!("Process: {} (PID: {})", process.name, process.pid);
println!("CPU Usage: {}%", process.cpu_usage);
println!("Memory Usage: {} bytes", process.memory_usage);
println!("Thread Count: {}", process.thread_count);
Key Features
Process Information
The Process
struct provides access to critical metrics:
- Basic Info: Process ID (PID), name, parent PID
- Resource Usage: CPU usage percentage, memory consumption
- Performance Metrics: Thread count, uptime, I/O statistics
- Process State: Running, suspended, etc.
Process Enumeration
There are multiple ways to retrieve process information:
// Process enumeration examples
use darwin_metrics::process::Process;
// Get all processes
let all_processes = Process::get_all().unwrap();
// Get process by specific PID
let process = Process::get_by_pid(1234).unwrap();
// Get child processes of a specific process
let children = Process::get_child_processes(1234).unwrap();
// Get parent process
let parent_pid = Process::get_parent_pid(1234).unwrap();
Process Tree
You can visualize the entire process hierarchy:
// Process tree visualization
use darwin_metrics::process::Process;
// Get process tree (process and depth pairs)
let process_tree = Process::get_process_tree().unwrap();
// Print process tree
for (process, depth) in process_tree {
let indent = " ".repeat(depth);
println!("{}{} ({})", indent, process.name, process.pid);
}
Real-time Monitoring
For continuous monitoring of process metrics:
// Real-time process monitoring with async
use darwin_metrics::process::Process;
use futures::stream::StreamExt;
use std::time::Duration;
#[tokio::main]
async fn main() {
let pid = std::process::id();
let interval = Duration::from_secs(1);
// Create a stream of process metrics
let mut stream = Process::monitor_metrics(pid, interval);
// Process the metrics as they arrive
while let Some(result) = stream.next().await {
if let Ok(process) = result {
println!("CPU: {}%, Memory: {}", process.cpu_usage, process.memory_usage);
}
}
}
Implementation Details
The process module uses a hybrid approach for efficiency:
- Bulk Retrieval: Uses
sysctl
for efficient retrieval of all processes at once - Detailed Information: Uses
libproc
for gathering detailed metrics about specific processes - Fallback Mechanism: If one approach fails, the module automatically falls back to alternatives
This design ensures both performance and reliability across different macOS versions and permission scenarios.
I/O Statistics
For each process, you can retrieve detailed I/O statistics:
// Process I/O statistics example
use darwin_metrics::process::Process;
// Get current process ID
let pid = std::process::id();
// Get process with I/O stats
let process = Process::get_by_pid(pid).unwrap();
// Access I/O information
println!("Read bytes: {}", process.io_stats.read_bytes);
println!("Write bytes: {}", process.io_stats.write_bytes);
println!("Read operations: {}", process.io_stats.read_count);
println!("Write operations: {}", process.io_stats.write_count);
System Processes
The module provides utilities to identify system processes:
// System process identification
use darwin_metrics::process::Process;
// Get current process ID
let pid = std::process::id();
// Check if this is a system process
let process = Process::get_by_pid(pid).unwrap();
if process.is_system_process() {
println!("{} is a system process", process.name);
}
Performance Considerations
- The first call to
get_all()
might be slower as it initializes internal caches - Subsequent calls will be faster due to optimized data structures
- Using
monitor_metrics()
is more efficient than repeatedly callingget_by_pid()
- The module cleans up its internal history tracking to prevent memory leaks
Thermal Monitoring
The Thermal module provides comprehensive temperature monitoring capabilities for macOS systems, allowing you to track CPU, GPU, and other temperature sensors, monitor fan speeds, and detect thermal throttling.
Features
- Multiple Temperature Sensors: Access CPU, GPU, heatsink, ambient, and battery temperature readings
- Fan Information: Monitor fan speeds, min/max values, and utilization percentages
- Thermal Throttling Detection: Determine if the system is experiencing thermal throttling
- Power Monitoring: Track CPU power consumption (when available)
- Asynchronous API: Both synchronous and async interfaces are provided
- Customizable Polling: Configure polling intervals and thresholds
Basic Usage
use darwin_metrics::hardware::Temperature;
use std::thread;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new Temperature instance
let mut temperature = Temperature::new();
// Get CPU temperature
let cpu_temp = temperature.cpu_temperature()?;
println!("CPU Temperature: {:.1}°C", cpu_temp);
// Monitor temperatures over time
for _ in 0..5 {
// Get comprehensive thermal metrics
let metrics = temperature.get_thermal_metrics()?;
println!("CPU: {:.1}°C", metrics.cpu_temperature.unwrap_or(0.0));
if let Some(gpu_temp) = metrics.gpu_temperature {
println!("GPU: {:.1}°C", gpu_temp);
}
// Check if the system is throttling
if metrics.is_throttling {
println!("ALERT: System is thermal throttling!");
}
// Display fan information
for (i, fan) in metrics.fans.iter().enumerate() {
println!(
"Fan {}: {} RPM ({:.1}%)",
i, fan.speed_rpm, fan.percentage
);
}
println!("---");
thread::sleep(Duration::from_secs(2));
}
Ok(())
}
Custom Configuration
You can customize the temperature monitoring behavior:
use darwin_metrics::hardware::{Temperature, TemperatureConfig};
fn main() {
// Create a custom configuration
let config = TemperatureConfig {
poll_interval_ms: 5000, // Poll every 5 seconds
throttling_threshold: 90.0, // Higher throttling threshold
auto_refresh: true, // Automatically refresh data
};
// Initialize temperature module with custom config
let temperature = Temperature::with_config(config);
// ... use temperature instance
}
Sensor Locations
The SensorLocation
enum represents different temperature sensor locations:
pub enum SensorLocation {
Cpu, // CPU temperature sensor
Gpu, // GPU temperature sensor
Memory, // System memory temperature sensor
Storage, // Storage/SSD temperature sensor
Battery, // Battery temperature sensor
Heatsink, // Heatsink temperature sensor
Ambient, // Ambient (inside case) temperature sensor
Other(String), // Other temperature sensor with a custom name
}
Fan Information
The Fan
struct provides detailed fan information:
pub struct Fan {
pub name: String, // Fan identifier (e.g., "CPU Fan", "System Fan")
pub speed_rpm: u32, // Current fan speed in RPM
pub min_speed: u32, // Minimum fan speed in RPM
pub max_speed: u32, // Maximum fan speed in RPM
pub percentage: f64, // Current fan utilization as a percentage (0-100%)
}
Asynchronous API
For applications using async/await, the module provides async versions of all methods:
use darwin_metrics::hardware::Temperature;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut temperature = Temperature::new();
// Get CPU temperature asynchronously
let cpu_temp = temperature.cpu_temperature_async().await?;
println!("CPU Temperature: {:.1}°C", cpu_temp);
// Get comprehensive thermal metrics asynchronously
let metrics = temperature.get_thermal_metrics_async().await?;
// Check if the system is throttling asynchronously
let is_throttling = temperature.is_throttling_async().await?;
if is_throttling {
println!("System is thermal throttling!");
}
Ok(())
}
Finding Available Sensors
Systems may have different temperature sensors available. You can discover the available sensors:
use darwin_metrics::hardware::Temperature;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut temperature = Temperature::new();
// List all available temperature sensors
let sensors = temperature.list_sensors()?;
println!("Available temperature sensors:");
for (name, location) in sensors {
println!("- {} ({:?})", name, location);
}
Ok(())
}
Thermal Metrics
The ThermalMetrics
struct provides a comprehensive snapshot of the system's thermal state:
pub struct ThermalMetrics {
pub cpu_temperature: Option<f64>, // CPU temperature in degrees Celsius
pub gpu_temperature: Option<f64>, // GPU temperature in degrees Celsius
pub heatsink_temperature: Option<f64>, // Heatsink temperature in degrees Celsius
pub ambient_temperature: Option<f64>, // Ambient temperature in degrees Celsius
pub battery_temperature: Option<f64>, // Battery temperature in degrees Celsius
pub is_throttling: bool, // Whether the system is thermal throttling
pub cpu_power: Option<f64>, // CPU power consumption in watts
pub fans: Vec<Fan>, // Information about all fans
}
Implementation Details
The thermal module uses macOS's System Management Controller (SMC) to access temperature sensors and fan information. The internal implementation:
- Communicates with the SMC via IOKit
- Reads temperature sensor data from various system components
- Retrieves fan speeds and calculates utilization percentages
- Monitors CPU power consumption when available
- Detects thermal throttling via SMC indicators or temperature-based heuristics
Performance Considerations
- Temperature readings are cached based on the configured polling interval
- Auto-refresh can be disabled for performance-critical applications
- Async methods offload blocking I/O operations to a separate thread pool
- Fan information is updated alongside temperature data for efficiency
Common Issues
- Missing Sensors: Not all Macs have the same temperature sensors; always check if values are present
- Throttling Detection: The primary method uses built-in SMC indicators, with temperature thresholds as a fallback
- Battery Temperature: Only available on MacBooks with a battery
- Fan Information: Systems with passive cooling may not have fan information available
Power Management
System Information
Async Support
Performance Considerations
FFI Bindings
The darwin-metrics
library provides a centralized approach to FFI (Foreign Function Interface) bindings for macOS system APIs. This page explains how these bindings are organized and used throughout the codebase.
Overview
All low-level FFI bindings are centralized in the src/utils/bindings.rs
file. This architectural decision provides several benefits:
- Maintainability: Changes to FFI interfaces only need to be made in one place
- Consistency: Prevents duplicate and potentially conflicting definitions
- Safety: Centralizes unsafe code, making auditing easier
- Reusability: Allows sharing of bindings across different modules
- Convention Adaptation: Adapts C-style naming to Rust conventions when needed
Included Bindings
The bindings module includes interfaces to various macOS frameworks:
System Framework
// System framework bindings
use std::os::raw::{c_int, c_uint, c_void};
#[link(name = "System", kind = "framework")]
extern "C" {
pub fn sysctl(
name: *const c_int,
namelen: c_uint,
oldp: *mut c_void,
oldlenp: *mut usize,
newp: *const c_void,
newlen: usize,
) -> c_int;
}
IOKit Framework
// IOKit framework bindings
use std::os::raw::c_char;
// A type alias for easier readability
type ffi_c_void = std::os::raw::c_void;
#[link(name = "IOKit", kind = "framework")]
extern "C" {
// IOService functions
pub fn IOServiceGetMatchingService(masterPort: u32, matchingDict: *const ffi_c_void) -> u32;
pub fn IOServiceMatching(serviceName: *const c_char) -> *mut ffi_c_void;
pub fn IOServiceOpen(service: u32, owningTask: u32, type_: u32, handle: *mut u32) -> i32;
// ... and more
}
Mach Host Functions
// Mach host functions
// Type definitions for Mach types
type MachPortT = u32;
type HostInfoT = *mut std::os::raw::c_void;
extern "C" {
pub static vm_kernel_page_size: u32;
pub fn host_statistics64(
host_priv: MachPortT,
flavor: i32,
host_info_out: HostInfoT,
host_info_outCnt: *mut u32,
) -> i32;
pub fn mach_host_self() -> MachPortT;
}
Constants and Types
The module also provides centralized definitions for:
- Type aliases for platform-specific types
- Constants used in system API calls
- Data structures for FFI data exchange
Helper Functions
To make these low-level bindings more usable, helper functions are provided:
// Helper functions for working with low-level macOS APIs
use std::os::raw::c_char;
// Process info structure definition (simplified)
#[repr(C)]
pub struct kinfo_proc {
// Process basic info
pub kp_proc: proc_info,
// Process extra info
pub kp_eproc: eproc_info,
}
#[repr(C)]
pub struct proc_info {
// Process ID, state, etc.
pub p_pid: i32,
// Other fields...
}
#[repr(C)]
pub struct eproc_info {
// Process name as a fixed-size array of bytes
pub p_comm: [u8; 16],
// Other fields...
}
/// Convert a char array to an SMC key integer
pub fn smc_key_from_chars(key: [c_char; 4]) -> u32 {
let mut result: u32 = 0;
for &k in &key {
result = (result << 8) | (k as u8 as u32);
}
result
}
/// Extract the process name from a kinfo_proc structure
pub fn extract_proc_name(proc_info: &kinfo_proc) -> String {
let raw_name = &proc_info.kp_eproc.p_comm;
let end = raw_name.iter().position(|&c| c == 0).unwrap_or(raw_name.len());
let name_slice = &raw_name[0..end];
String::from_utf8_lossy(name_slice).to_string()
}
Usage Example
Here's how modules use these centralized bindings:
// Example of how to use the centralized bindings in your code
use std::os::raw::{c_int, c_uint, c_void};
// Custom error handling for this example
#[derive(Debug)]
pub enum Error {
System(String),
// Other error variants...
}
// Result type alias
pub type Result<T> = std::result::Result<T, Error>;
// Import bindings from the central module
use crate::utils::bindings::{
sysctl, vm_statistics64, xsw_usage, vm_kernel_page_size,
host_statistics64, mach_host_self,
KERN_SUCCESS, HOST_VM_INFO64, HOST_VM_INFO64_COUNT,
HostInfoT,
sysctl_constants::{CTL_HW, HW_MEMSIZE, CTL_VM, VM_SWAPUSAGE}
};
// Now you can use these bindings safely
fn get_total_memory() -> Result<u64> {
let mut size = 0u64;
let mut size_len = std::mem::size_of::<u64>();
let mib = [CTL_HW, HW_MEMSIZE];
let result = unsafe {
sysctl(
mib.as_ptr(),
mib.len() as u32,
&mut size as *mut u64 as *mut _,
&mut size_len,
std::ptr::null(),
0,
)
};
if result == 0 {
Ok(size)
} else {
Err(Error::System("Failed to get total memory".to_string()))
}
}
Safety Considerations
While the bindings are centralized, using them still requires care:
- Unsafe Blocks: Always use
unsafe
blocks when calling FFI functions - Error Handling: Check return values from system calls
- Memory Management: Be careful with memory allocated by system functions
- Type Conversions: Ensure proper conversion between Rust and C types
Maintaining Bindings
When adding new FFI bindings:
- Add them to
src/utils/bindings.rs
with proper documentation - Group related bindings together with clear section markers
- Provide helper functions when appropriate
- Don't expose unsafe interfaces directly in your API
- Use Rust's snake_case naming convention for struct fields, even when binding C APIs (added in v0.1.4)
- For example,
pLimitData
in C should be namedp_limit_data
in Rust - This helps avoid clippy warnings and maintain consistent style
- For example,
By following this centralized approach, darwin-metrics
maintains a cleaner, safer, and more maintainable codebase.
Contributing to darwin-metrics
Thank you for your interest in contributing to darwin-metrics
! This guide will help you get started with the development process.
Getting Started
Prerequisites
- Rust 1.85 or later
- macOS El Capitan (10.11) or later
- Xcode Command Line Tools
- Git
Setup
-
Fork the repository on GitHub
-
Clone your fork locally:
git clone https://github.com/your-username/darwin-metrics.git cd darwin-metrics
-
Add the original repository as an upstream remote:
git remote add upstream https://github.com/sm-moshi/darwin-metrics.git
Development Workflow
Building the Project
# Build the project
cargo build
# Build with all features
cargo build --all-features
Running Tests
# Run all tests
cargo test --all-features
# Run a specific test
cargo test <test_name> -- --nocapture
# Run faster tests using nextest
cargo nextest run
Code Coverage
# Generate coverage report
cargo llvm-cov
Code Quality
Before submitting a pull request, run these checks:
# Format code
cargo +beta fmt
# Run linter
cargo +beta clippy --workspace --tests --all-targets --all-features
Adding New Features
Creating a New Module
- Create a new directory in
src/
for your module (if applicable) - Create a
mod.rs
file within that directory - Add the module to
src/lib.rs
with a public export
Example of a new module:
// src/mynewmodule/mod.rs
// First import the error types from the main crate
use darwin_metrics::error::{Error, Result};
pub struct MyNewFeature {
// Implementation details
name: String,
value: f64,
}
impl MyNewFeature {
pub fn new() -> Result<Self> {
// Initialize your feature
Ok(Self {
name: "My Feature".to_string(),
value: 0.0,
})
}
pub fn get_metrics(&self) -> Result<String> {
// Implement your metrics collection logic
Ok(format!("{}: {}", self.name, self.value))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_my_feature() {
// Test implementation
let feature = MyNewFeature::new().unwrap();
assert!(feature.get_metrics().is_ok());
}
}
FFI Bindings
If your module requires FFI bindings:
- Add your bindings to
src/utils/bindings.rs
- Group related bindings together
- Add proper documentation
- Provide helper functions for complex operations
Pull Request Process
-
Create a new branch for your feature or fix:
git checkout -b feature/your-feature-name
-
Make your changes and commit them with a clear message:
git commit -m "feat: add new feature X"
-
Push your branch to your fork:
git push origin feature/your-feature-name
-
Create a pull request on GitHub
-
Ensure your PR includes:
- A clear description of the changes
- Any relevant issue numbers
- Documentation updates
- Tests for new functionality
Commit Style
We follow a simplified version of the Conventional Commits specification:
feat
: A new featurefix
: A bug fixdocs
: Documentation changesrefactor
: Code refactoringtest
: Adding or fixing testsperf
: Performance improvementschore
: Other changes
Example: feat: add CPU frequency monitoring
Code Style
We adhere to standard Rust style guidelines:
- Use
snake_case
for variables and functions - Use
CamelCase
for types and structs - Use
SCREAMING_CASE
for constants - Use the
?
operator for error propagation - Document public APIs with Rustdoc comments
- Write clear, concise comments for complex logic
License
By contributing to this project, you agree that your contributions will be licensed under the project's MIT license.
Questions?
If you have any questions or need help with your contribution, please open an issue on GitHub.
Internal Architecture
This document explains the internal architecture and organization of the darwin-metrics
codebase.
Project Structure
The darwin-metrics library follows a domain-driven design approach, organizing code around system resources:
darwin-metrics/
├── Cargo.lock
├── Cargo.toml # Crate configuration
├── LICENSE
├── NOTICE
├── README.md # Project overview
├── build.rs # Native code build script
├── changelog-configuration.json # Release configuration
├── clippy.toml # Linting configuration
├── coverage/ # Code coverage reports
├── docs/ # Documentation
│ ├── CHANGELOG.md # Release history
│ ├── CHECKLIST.md # Release checklist
│ ├── ROADMAP.md # Development roadmap
│ ├── RUST_API_CHECKLIST.md # API design guidelines
│ ├── TODO.md # Development tasks
│ ├── book.toml # mdBook configuration
│ ├── book/ # Generated documentation
│ ├── custom.css # Documentation styling
│ └── src/ # Documentation source
│ ├── SUMMARY.md # Documentation index
│ ├── advanced/ # Advanced topics
│ ├── development/ # Developer guides
│ ├── getting-started.md # Quickstart guide
│ ├── introduction.md # Project introduction
│ └── modules/ # Module documentation
├── examples/ # Example applications
│ ├── disk_monitor.rs # Disk monitoring example
│ ├── gpu_monitor_safe.rs # Safe GPU monitoring
│ ├── gpu_monitor_simplified.rs # Simplified GPU example
│ ├── gpu_static.rs # Static GPU info example
│ ├── memory_monitor.rs # Memory monitoring
│ ├── memory_monitor_async.rs # Async memory monitoring
│ ├── network_async.rs # Async network monitoring
│ └── network_info.rs # Network info example
├── src/ # Main source code
│ ├── battery/ # Battery monitoring
│ │ └── mod.rs # Battery module implementation
│ ├── disk/ # Disk monitoring
│ │ └── mod.rs # Disk module implementation
│ ├── docs_rs_stubs.rs # Support for docs.rs
│ ├── error.rs # Error handling
│ ├── hardware/ # Hardware-related modules
│ │ ├── cpu/ # CPU metrics
│ │ │ ├── cpu_impl.rs # CPU implementation
│ │ │ ├── frequency.rs # CPU frequency tracking
│ │ │ └── mod.rs # CPU module definition
│ │ ├── gpu/ # GPU metrics
│ │ │ └── mod.rs # GPU implementation
│ │ ├── iokit/ # IOKit interface
│ │ │ ├── mock.rs # Mock implementation for testing
│ │ │ ├── mod.rs # Main implementation
│ │ │ └── tests.rs # Tests for IOKit
│ │ ├── memory/ # Memory metrics
│ │ │ └── mod.rs # Memory implementation
│ │ ├── mod.rs # Hardware module exports
│ │ └── temperature/ # Temperature sensors
│ │ └── mod.rs # Temperature implementation
│ ├── lib.rs # Library entry point
│ ├── network/ # Network monitoring
│ │ ├── interface.rs # Network interfaces
│ │ ├── mod.rs # Network module exports
│ │ └── traffic.rs # Network traffic
│ ├── power/ # Power management
│ │ └── mod.rs # Power implementation
│ ├── process/ # Process monitoring
│ │ └── mod.rs # Process implementation
│ ├── resource/ # Resource monitoring
│ │ └── mod.rs # Resource implementation
│ ├── system/ # System information
│ │ └── mod.rs # System implementation
│ └── utils/ # Utility functions
│ ├── bindings.rs # FFI bindings
│ ├── mod.rs # Utilities exports
│ ├── property_utils.rs # Property access utilities
│ ├── property_utils_tests.rs # Tests for property utils
│ └── test_utils.rs # Testing utilities
└── tests/ # Integration tests
└── version-sync.rs # Version consistency tests
Core Design Principles
1. Module Independence
Each module is designed to function independently while relying on shared utilities. This allows:
- Using only the features you need
- Minimizing dependency chain issues
- Isolated testing without the entire library
2. Layered Architecture
The codebase follows a layered approach:
+-----------------------------------------+
| Public API (lib.rs) |
+-----------------------------------------+
| Domain-specific Modules |
| (cpu, memory, process, etc.) |
+-----------------------------------------+
| Shared Utilities |
| (error handling, FFI helpers) |
+-----------------------------------------+
| FFI Layer |
| (bindings.rs, unsafe code) |
+-----------------------------------------+
3. FFI Strategy
Foreign Function Interface (FFI) calls to macOS APIs are centralized in src/utils/bindings.rs
. This design:
- Contains unsafe code in a single location
- Promotes code reuse across modules
- Makes the codebase easier to audit
- Simplifies maintenance of platform-specific code
Error Handling
The library uses a consistent error handling approach:
- A centralized
Error
enum insrc/error.rs
- Module-specific error variants
- The
Result<T, Error>
type alias for function returns - Use of
thiserror
for error derivation
Example:
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("System error: {0}")]
System(String),
#[error("Process error: {0}")]
Process(String),
#[error("Resource not available: {0}")]
NotAvailable(String),
// ... other error variants
}
pub type Result<T> = std::result::Result<T, Error>;
Testing Strategy
The codebase uses multiple testing approaches:
- Unit Tests: Focused on isolated functionality
- Integration Tests: Testing module interactions
- Mock Objects: For simulating hardware components
- Conditional Testing: Platform-specific test cases
Test utilities in src/utils/test_utils.rs
provide common testing functionality.
Platform Compatibility
While the library targets macOS specifically:
- Platform checks ensure proper behavior
- API stability is maintained across macOS versions
- Version-specific code is isolated when needed
- Fallback mechanisms handle API differences
Roadmap
This document outlines the planned development roadmap for the darwin-metrics
library. It provides a high-level overview of our goals and priorities for future releases.
Current Status
The library is currently transitioning from Phase 1 to Phase 2 of development, with core functionality implemented and optimizations ongoing.
Project Structure
darwin-metrics/
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── NOTICE
├── README.md
├── build.rs # Build script for native code
├── changelog-configuration.json
├── clippy.toml
├── coverage/ # Code coverage reports
├── docs/ # Documentation
│ ├── CHANGELOG.md
│ ├── CHECKLIST.md
│ ├── ROADMAP.md
│ ├── RUST_API_CHECKLIST.md
│ ├── TODO.md
│ ├── book.toml
│ ├── book/ # Generated mdBook output
│ ├── custom.css
│ └── src/ # mdBook source
│ ├── SUMMARY.md
│ ├── advanced/
│ ├── development/
│ ├── getting-started.md
│ ├── introduction.md
│ └── modules/
├── examples/ # Example applications
│ ├── disk_monitor.rs
│ ├── gpu_monitor_safe.rs
│ ├── gpu_monitor_simplified.rs
│ ├── gpu_static.rs
│ ├── memory_monitor.rs
│ ├── memory_monitor_async.rs
│ ├── network_async.rs
│ └── network_info.rs
├── src/ # Main source code
│ ├── battery/ # Battery monitoring
│ ├── disk/ # Disk monitoring
│ ├── docs_rs_stubs.rs # Support for docs.rs
│ ├── error.rs # Error handling
│ ├── hardware/ # Hardware-related modules
│ │ ├── cpu/ # CPU metrics
│ │ │ ├── cpu_impl.rs
│ │ │ ├── frequency.rs
│ │ │ └── mod.rs
│ │ ├── gpu/ # GPU metrics
│ │ ├── iokit/ # IOKit interface
│ │ │ ├── mock.rs # Mock implementation for testing
│ │ │ ├── mod.rs # Main implementation
│ │ │ └── tests.rs # Tests for IOKit
│ │ ├── memory/ # Memory metrics
│ │ ├── mod.rs
│ │ └── temperature/ # Temperature sensors
│ ├── lib.rs # Library entry point
│ ├── network/ # Network monitoring
│ │ ├── interface.rs # Network interfaces
│ │ ├── mod.rs
│ │ └── traffic.rs # Network traffic
│ ├── power/ # Power management
│ ├── process/ # Process monitoring
│ ├── resource/ # Resource monitoring
│ ├── system/ # System information
│ └── utils/ # Utility functions
│ ├── bindings.rs # FFI bindings
│ ├── mod.rs
│ ├── property_utils.rs # Property access utilities
│ ├── property_utils_tests.rs
│ └── test_utils.rs # Testing utilities
└── tests/ # Integration tests
└── version-sync.rs # Version consistency tests
Development Phases
Phase 1: Codebase Refactoring & Cleanup (0.1.x) - Completed
- Latest release: 0.1.4 (March 10, 2025)
Goal: Improve structure, maintainability, and performance before implementing new features.
Key Tasks:
- ✅ Process Monitoring: Implement comprehensive process monitoring capabilities
- ✅ CPU Metrics: Refactor and enhance CPU monitoring functionality
- ✅ GPU Metrics: Implement and refine GPU metrics collection
- ✅ Network Monitoring: Implement bandwidth tracking and network state monitoring
- ✅ Temperature Monitoring: Enhance thermal sensor data collection
- ✅ Disk Monitoring: Implement volume detection and I/O monitoring
- ✅ General Code Cleanup: Refactor and improve code quality
- ✅ Centralized FFI bindings in src/utils/bindings.rs
- ✅ Improved error handling and propagation
- ✅ Enhanced code organization and documentation
Phase 2: Enhanced System Metrics (0.2.0) - In Progress
Goal: Expand monitoring capabilities with additional system metrics.
Key Tasks:
- 🔄 Enhance CPU and GPU monitoring with improved async processing
- ✅ Implement detailed CPU and GPU frequency tracking
- 🔄 Add advanced process monitoring features
- ✅ Implement comprehensive disk and storage monitoring
- ✅ Enhance network monitoring capabilities with async support
- 🔄 Complete battery and power management functionality
- 🔄 Expand test coverage and benchmarking
Phase 3: Optimization & Advanced Features (0.3.0) - Planned
Goal: Optimize for performance and introduce advanced tracking.
Key Tasks:
- Optimize CPU and memory metrics for lower overhead
- Implement fan control features for supported devices
- Improve power management insights
- Reduce memory footprint and improve CPU efficiency
- Implement event-driven monitoring
- Enhance testing with performance benchmarks
Phase 4: Final Optimizations & Production Release (1.0.0) - Planned
Goal: Prepare for stable, production-ready release.
Key Tasks:
- Complete API documentation and examples
- Ensure comprehensive test coverage
- Conduct final performance tests and optimizations
- Review API consistency
- Prepare for crates.io release
Feature Priorities
These are our current feature priorities, ranked from highest to lowest:
- System Stability & Performance: Ensuring the library has minimal impact on the host system
- Core Metrics Support: Comprehensive coverage of CPU, memory, and process metrics
- GPU Metrics: Support for detailed GPU usage and monitoring
- Network Metrics: Monitoring of network interfaces and traffic
- Advanced Features: Power management, fan control, and detailed temperature monitoring
Release Schedule
Our current timeline is:
- 0.1.x: Q1-Q2 2024 (Alpha releases with core functionality)
- ✅ 0.1.0: Initial release with basic monitoring
- ✅ 0.1.1: Improved GPU and memory monitoring
- ✅ 0.1.2: Enhanced process tracking and disc metrics
- ✅ 0.1.3: Improved documentation and CI workflow
- 0.2.0: Q3 2024 (Beta release with enhanced features)
- 0.3.0: Q4 2024 (Release candidate with optimizations)
- 1.0.0: Q1 2025 (Stable release with complete documentation)
Contributing
We welcome contributions that align with our roadmap. If you're interested in helping, please check out our Contributing Guide for details on how to get involved.
This roadmap is subject to change based on community feedback and project priorities. For the most up-to-date information, please check the repository's issue tracker.