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:

  1. Safety First: All unsafe FFI code is properly encapsulated, providing a safe Rust API to users
  2. Performance: Minimizes overhead by using efficient access patterns and sharing resources
  3. Modularity: Each system component is contained in its own module with clear APIs
  4. Error Handling: Comprehensive error handling with specific error types
  5. 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 monitoring
  • memory - Memory statistics
  • gpu - GPU monitoring
  • process - Process information
  • thermal - Temperature sensors
  • power - 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:

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 frequency
    • hw.cpufrequency_min for minimum frequency
    • hw.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 until update() 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:

  1. IOKit: Primary source of GPU statistics including utilization, memory usage, and thermal information
  2. Metal: Used as a fallback for GPU name retrieval
  3. 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:

  1. Autorelease Pools: All IOKit and Objective-C calls are wrapped in autorelease pools to properly manage temporary objects
  2. Reference Counting: Proper retain/release patterns for Core Foundation objects
  3. Safe FFI: Careful validation of foreign function interface calls
  4. 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:

  1. 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
  2. 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
  3. 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
  4. 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:

  1. statfs: For basic volume information like capacity and usage
  2. IOKit: For disk performance metrics and device type detection
  3. DiskArbitration framework: For additional volume metadata
  4. 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 operations
  • Error::NotAvailable: When specific network metrics aren't available
  • Error::System: Underlying system errors
  • Error::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

  1. 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(())
    }
  2. 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(())
    }
  3. 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(())
    }
  4. 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(())
    }
  5. 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:

  1. Bulk Retrieval: Uses sysctl for efficient retrieval of all processes at once
  2. Detailed Information: Uses libproc for gathering detailed metrics about specific processes
  3. 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 calling get_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:

  1. Communicates with the SMC via IOKit
  2. Reads temperature sensor data from various system components
  3. Retrieves fan speeds and calculates utilization percentages
  4. Monitors CPU power consumption when available
  5. 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:

  1. Maintainability: Changes to FFI interfaces only need to be made in one place
  2. Consistency: Prevents duplicate and potentially conflicting definitions
  3. Safety: Centralizes unsafe code, making auditing easier
  4. Reusability: Allows sharing of bindings across different modules
  5. 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:

  1. Unsafe Blocks: Always use unsafe blocks when calling FFI functions
  2. Error Handling: Check return values from system calls
  3. Memory Management: Be careful with memory allocated by system functions
  4. Type Conversions: Ensure proper conversion between Rust and C types

Maintaining Bindings

When adding new FFI bindings:

  1. Add them to src/utils/bindings.rs with proper documentation
  2. Group related bindings together with clear section markers
  3. Provide helper functions when appropriate
  4. Don't expose unsafe interfaces directly in your API
  5. 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 named p_limit_data in Rust
    • This helps avoid clippy warnings and maintain consistent style

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

  1. Fork the repository on GitHub

  2. Clone your fork locally:

    git clone https://github.com/your-username/darwin-metrics.git
    cd darwin-metrics
    
  3. 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

  1. Create a new directory in src/ for your module (if applicable)
  2. Create a mod.rs file within that directory
  3. 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:

  1. Add your bindings to src/utils/bindings.rs
  2. Group related bindings together
  3. Add proper documentation
  4. Provide helper functions for complex operations

Pull Request Process

  1. Create a new branch for your feature or fix:

    git checkout -b feature/your-feature-name
    
  2. Make your changes and commit them with a clear message:

    git commit -m "feat: add new feature X"
    
  3. Push your branch to your fork:

    git push origin feature/your-feature-name
    
  4. Create a pull request on GitHub

  5. 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 feature
  • fix: A bug fix
  • docs: Documentation changes
  • refactor: Code refactoring
  • test: Adding or fixing tests
  • perf: Performance improvements
  • chore: 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:

  1. A centralized Error enum in src/error.rs
  2. Module-specific error variants
  3. The Result<T, Error> type alias for function returns
  4. 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:

  1. Unit Tests: Focused on isolated functionality
  2. Integration Tests: Testing module interactions
  3. Mock Objects: For simulating hardware components
  4. 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:

  1. System Stability & Performance: Ensuring the library has minimal impact on the host system
  2. Core Metrics Support: Comprehensive coverage of CPU, memory, and process metrics
  3. GPU Metrics: Support for detailed GPU usage and monitoring
  4. Network Metrics: Monitoring of network interfaces and traffic
  5. 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.