Skip to content

Commit 188f817

Browse files
authored
feat: add proper library API for external Rust projects (#109)
* feat: add proper library API for external Rust projects Introduce high-level AllSmi client struct with ergonomic API for querying GPU/NPU, CPU, memory, and chassis information. Add unified Error type using thiserror, prelude module for convenient imports, and comprehensive examples and tests for library usage. Closes #106 * fix: improve thread safety documentation and add #[must_use] attributes
1 parent 02fe310 commit 188f817

File tree

7 files changed

+1371
-3
lines changed

7 files changed

+1371
-3
lines changed

README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,107 @@ make release
380380
make test
381381
```
382382

383+
## Library API
384+
385+
`all-smi` can also be used as a Rust library for building custom monitoring tools or integrating hardware metrics into your applications.
386+
387+
### Add Dependency
388+
389+
Add to your `Cargo.toml`:
390+
391+
```toml
392+
[dependencies]
393+
all-smi = "0.15"
394+
```
395+
396+
### Basic Usage
397+
398+
```rust
399+
use all_smi::{AllSmi, Result};
400+
401+
fn main() -> Result<()> {
402+
// Initialize with auto-detection
403+
let smi = AllSmi::new()?;
404+
405+
// Get all GPU/NPU information
406+
for gpu in smi.get_gpu_info() {
407+
println!("{}: {}% utilization, {:.1}W",
408+
gpu.name, gpu.utilization, gpu.power_consumption);
409+
}
410+
411+
// Get CPU information
412+
for cpu in smi.get_cpu_info() {
413+
println!("{}: {:.1}% utilization", cpu.cpu_model, cpu.utilization);
414+
}
415+
416+
// Get memory information
417+
for mem in smi.get_memory_info() {
418+
println!("Memory: {:.1}% used", mem.utilization);
419+
}
420+
421+
Ok(())
422+
}
423+
```
424+
425+
### Using the Prelude
426+
427+
For convenience, import all common types:
428+
429+
```rust
430+
use all_smi::prelude::*;
431+
432+
fn main() -> Result<()> {
433+
let smi = AllSmi::new()?;
434+
435+
// Types like GpuInfo, CpuInfo, MemoryInfo are available
436+
let gpus: Vec<GpuInfo> = smi.get_gpu_info();
437+
println!("Found {} GPU(s)", gpus.len());
438+
439+
Ok(())
440+
}
441+
```
442+
443+
### Available Methods
444+
445+
| Method | Returns | Description |
446+
|--------|---------|-------------|
447+
| `get_gpu_info()` | `Vec<GpuInfo>` | GPU/NPU metrics (utilization, memory, temp, power) |
448+
| `get_cpu_info()` | `Vec<CpuInfo>` | CPU metrics (utilization, frequency, temp) |
449+
| `get_memory_info()` | `Vec<MemoryInfo>` | System memory metrics |
450+
| `get_process_info()` | `Vec<ProcessInfo>` | GPU process information |
451+
| `get_chassis_info()` | `Option<ChassisInfo>` | Node-level power and thermal info |
452+
453+
### Configuration
454+
455+
```rust
456+
use all_smi::{AllSmi, AllSmiConfig};
457+
458+
let config = AllSmiConfig::new()
459+
.sample_interval(500) // 500ms sample interval
460+
.verbose(true); // Enable verbose warnings
461+
462+
let smi = AllSmi::with_config(config)?;
463+
```
464+
465+
### Thread Safety
466+
467+
`AllSmi` is `Send + Sync` and can be safely shared across threads:
468+
469+
```rust
470+
use std::sync::Arc;
471+
use std::thread;
472+
473+
let smi = Arc::new(AllSmi::new()?);
474+
475+
let smi_clone = smi.clone();
476+
thread::spawn(move || {
477+
let gpus = smi_clone.get_gpu_info();
478+
// ...
479+
});
480+
```
481+
482+
For more examples, see `examples/library_usage.rs` in the repository.
483+
383484
## Development
384485

385486
For development documentation including building from source, testing with mock servers, architecture details, and technology stack information, see [DEVELOPERS.md](DEVELOPERS.md).

examples/library_usage.rs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Copyright 2025 Lablup Inc. and Jeongkyu Shin
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! Example demonstrating the all-smi library API.
16+
//!
17+
//! This example shows how to use the high-level AllSmi API to query
18+
//! GPU, CPU, memory, and process information.
19+
//!
20+
//! Run with: `cargo run --example library_usage`
21+
22+
use all_smi::prelude::*;
23+
24+
fn main() -> Result<()> {
25+
println!("=== all-smi Library Usage Example ===\n");
26+
27+
// Initialize AllSmi with default configuration
28+
let smi = AllSmi::new()?;
29+
30+
// ==========================================================================
31+
// GPU / NPU Information
32+
// ==========================================================================
33+
println!("--- GPU/NPU Information ---");
34+
let gpus = smi.get_gpu_info();
35+
36+
if gpus.is_empty() {
37+
println!("No GPUs/NPUs detected on this system.");
38+
} else {
39+
println!("Found {} GPU(s)/NPU(s):\n", gpus.len());
40+
41+
for (i, gpu) in gpus.iter().enumerate() {
42+
println!(" [{}] {}", i, gpu.name);
43+
println!(" Type: {}", gpu.device_type);
44+
println!(" Utilization: {:.1}%", gpu.utilization);
45+
println!(
46+
" Memory: {} MB / {} MB ({:.1}%)",
47+
gpu.used_memory / 1024 / 1024,
48+
gpu.total_memory / 1024 / 1024,
49+
if gpu.total_memory > 0 {
50+
(gpu.used_memory as f64 / gpu.total_memory as f64) * 100.0
51+
} else {
52+
0.0
53+
}
54+
);
55+
println!(" Temperature: {}C", gpu.temperature);
56+
println!(" Power: {:.1}W", gpu.power_consumption);
57+
println!(" Frequency: {} MHz", gpu.frequency);
58+
59+
if let Some(cores) = gpu.gpu_core_count {
60+
println!(" GPU Cores: {}", cores);
61+
}
62+
63+
println!();
64+
}
65+
}
66+
67+
// ==========================================================================
68+
// GPU/NPU Process Information
69+
// ==========================================================================
70+
println!("--- GPU/NPU Processes ---");
71+
let processes = smi.get_process_info();
72+
73+
if processes.is_empty() {
74+
println!("No GPU/NPU processes running.");
75+
} else {
76+
println!("Found {} GPU process(es):\n", processes.len());
77+
78+
for proc in processes.iter().take(5) {
79+
println!(
80+
" PID {}: {} ({} MB GPU memory)",
81+
proc.pid,
82+
proc.process_name,
83+
proc.used_memory / 1024 / 1024
84+
);
85+
}
86+
87+
if processes.len() > 5 {
88+
println!(" ... and {} more", processes.len() - 5);
89+
}
90+
}
91+
println!();
92+
93+
// ==========================================================================
94+
// CPU Information
95+
// ==========================================================================
96+
println!("--- CPU Information ---");
97+
let cpus = smi.get_cpu_info();
98+
99+
if cpus.is_empty() {
100+
println!("CPU information not available.");
101+
} else {
102+
for cpu in &cpus {
103+
println!(" Model: {}", cpu.cpu_model);
104+
println!(" Architecture: {}", cpu.architecture);
105+
println!(
106+
" Cores: {} (Threads: {})",
107+
cpu.total_cores, cpu.total_threads
108+
);
109+
println!(" Sockets: {}", cpu.socket_count);
110+
println!(" Utilization: {:.1}%", cpu.utilization);
111+
println!(
112+
" Frequency: {} MHz (Max: {} MHz)",
113+
cpu.base_frequency_mhz, cpu.max_frequency_mhz
114+
);
115+
116+
if let Some(temp) = cpu.temperature {
117+
println!(" Temperature: {}C", temp);
118+
}
119+
120+
if let Some(power) = cpu.power_consumption {
121+
println!(" Power: {:.1}W", power);
122+
}
123+
124+
// Apple Silicon specific info
125+
if let Some(ref apple_info) = cpu.apple_silicon_info {
126+
println!(" Apple Silicon Details:");
127+
println!(
128+
" P-cores: {} ({:.1}% utilization)",
129+
apple_info.p_core_count, apple_info.p_core_utilization
130+
);
131+
println!(
132+
" E-cores: {} ({:.1}% utilization)",
133+
apple_info.e_core_count, apple_info.e_core_utilization
134+
);
135+
println!(" GPU cores: {}", apple_info.gpu_core_count);
136+
137+
if let Some(p_freq) = apple_info.p_cluster_frequency_mhz {
138+
println!(" P-cluster frequency: {} MHz", p_freq);
139+
}
140+
if let Some(e_freq) = apple_info.e_cluster_frequency_mhz {
141+
println!(" E-cluster frequency: {} MHz", e_freq);
142+
}
143+
}
144+
}
145+
}
146+
println!();
147+
148+
// ==========================================================================
149+
// Memory Information
150+
// ==========================================================================
151+
println!("--- Memory Information ---");
152+
let memory = smi.get_memory_info();
153+
154+
if memory.is_empty() {
155+
println!("Memory information not available.");
156+
} else {
157+
for mem in &memory {
158+
let total_gb = mem.total_bytes as f64 / 1024.0 / 1024.0 / 1024.0;
159+
let used_gb = mem.used_bytes as f64 / 1024.0 / 1024.0 / 1024.0;
160+
let available_gb = mem.available_bytes as f64 / 1024.0 / 1024.0 / 1024.0;
161+
162+
println!(" Total: {:.1} GB", total_gb);
163+
println!(" Used: {:.1} GB ({:.1}%)", used_gb, mem.utilization);
164+
println!(" Available: {:.1} GB", available_gb);
165+
166+
if mem.swap_total_bytes > 0 {
167+
let swap_total_gb = mem.swap_total_bytes as f64 / 1024.0 / 1024.0 / 1024.0;
168+
let swap_used_gb = mem.swap_used_bytes as f64 / 1024.0 / 1024.0 / 1024.0;
169+
println!(" Swap: {:.1} GB / {:.1} GB", swap_used_gb, swap_total_gb);
170+
}
171+
172+
// Linux-specific metrics
173+
if mem.buffers_bytes > 0 || mem.cached_bytes > 0 {
174+
let buffers_mb = mem.buffers_bytes as f64 / 1024.0 / 1024.0;
175+
let cached_mb = mem.cached_bytes as f64 / 1024.0 / 1024.0;
176+
println!(
177+
" Buffers: {:.1} MB, Cached: {:.1} MB",
178+
buffers_mb, cached_mb
179+
);
180+
}
181+
}
182+
}
183+
println!();
184+
185+
// ==========================================================================
186+
// Chassis Information
187+
// ==========================================================================
188+
println!("--- Chassis Information ---");
189+
if let Some(chassis) = smi.get_chassis_info() {
190+
if let Some(power) = chassis.total_power_watts {
191+
println!(" Total System Power: {:.1}W", power);
192+
}
193+
194+
if let Some(ref pressure) = chassis.thermal_pressure {
195+
println!(" Thermal Pressure: {}", pressure);
196+
}
197+
198+
if let Some(inlet) = chassis.inlet_temperature {
199+
println!(" Inlet Temperature: {:.1}C", inlet);
200+
}
201+
202+
if let Some(outlet) = chassis.outlet_temperature {
203+
println!(" Outlet Temperature: {:.1}C", outlet);
204+
}
205+
206+
if !chassis.fan_speeds.is_empty() {
207+
println!(" Fans:");
208+
for fan in &chassis.fan_speeds {
209+
println!(
210+
" {}: {} RPM / {} RPM",
211+
fan.name, fan.speed_rpm, fan.max_rpm
212+
);
213+
}
214+
}
215+
216+
if !chassis.psu_status.is_empty() {
217+
println!(" PSUs:");
218+
for psu in &chassis.psu_status {
219+
println!(" {}: {:?}", psu.name, psu.status);
220+
}
221+
}
222+
} else {
223+
println!(" Chassis information not available on this platform.");
224+
}
225+
println!();
226+
227+
// ==========================================================================
228+
// Summary
229+
// ==========================================================================
230+
println!("--- Summary ---");
231+
println!(" GPU readers: {}", smi.gpu_reader_count());
232+
println!(" Has GPUs: {}", smi.has_gpus());
233+
println!(" Has CPU monitoring: {}", smi.has_cpu_monitoring());
234+
println!(" Has memory monitoring: {}", smi.has_memory_monitoring());
235+
236+
Ok(())
237+
}

0 commit comments

Comments
 (0)