Compare commits

..

No commits in common. "ffae695240b0f0e6ebed4c799fd8681f519faa6a" and "040d620917e4e8fceb3d89a10ea4925dd663a683" have entirely different histories.

31 changed files with 2075 additions and 2538 deletions

View file

@ -1,28 +0,0 @@
# float_literal_trailing_zero = "Always" # TODO: Warning for some reason?
condense_wildcard_suffixes = true
doc_comment_code_block_width = 80
edition = "2024" # Keep in sync with Cargo.toml.
enum_discrim_align_threshold = 60
force_explicit_abi = false
force_multiline_blocks = true
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true
group_imports = "StdExternalCrate"
hex_literal_case = "Upper"
imports_granularity = "Crate"
imports_layout = "Vertical"
inline_attribute_width = 60
match_block_trailing_comma = true
max_width = 80
newline_style = "Unix"
normalize_comments = true
normalize_doc_attributes = true
overflow_delimited_expr = true
struct_field_align_threshold = 60
tab_spaces = 2
unstable_features = true
use_field_init_shorthand = true
use_try_shorthand = true
wrap_comments = true

View file

@ -1,14 +0,0 @@
[formatting]
align_entries = true
column_width = 100
compact_arrays = false
reorder_inline_tables = true
reorder_keys = true
[[rule]]
include = [ "**/Cargo.toml" ]
keys = [ "package" ]
[rule.formatting]
reorder_keys = false

72
Cargo.lock generated
View file

@ -82,15 +82,6 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.0" version = "3.20.0"
@ -185,41 +176,12 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "6.0.0" version = "6.0.0"
@ -286,16 +248,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@ -581,7 +533,6 @@ dependencies = [
"ringbuf", "ringbuf",
"serde", "serde",
"serde_json", "serde_json",
"sha2",
"sysinfo", "sysinfo",
"thiserror", "thiserror",
"tokio", "tokio",
@ -779,17 +730,6 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -940,12 +880,6 @@ version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.24"
@ -958,12 +892,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.1+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"

View file

@ -1,4 +1,5 @@
[workspace] [workspace]
resolver = "3"
members = [ members = [
"crates/*", "crates/*",
"scanners/scanner-system", "scanners/scanner-system",
@ -6,35 +7,33 @@ members = [
"scanners/scanner-power", "scanners/scanner-power",
"scanners/scanner-proc", "scanners/scanner-proc",
] ]
resolver = "3"
[workspace.package] [workspace.package]
authors = [ "NotAShelf <raf@notashelf.dev>" ] version = "0.1.0"
edition = "2021" edition = "2021"
license = "MPL-2.0" license = "MPL-2.0"
version = "0.1.0" authors = ["NotAShelf <raf@notashelf.dev>"]
[workspace.dependencies] [workspace.dependencies]
pscand-core = { path = "./crates/pscand-core" } pscand-core = { path = "./crates/pscand-core" }
pscand-macros = { path = "./crates/pscand-macros" } pscand-macros = { path = "./crates/pscand-macros" }
chrono = { features = [ "serde" ], version = "0.4.43" } tokio = { version = "1.49.0", features = ["full"] }
clap = { features = [ "derive" ], version = "4.5.59" } serde = { version = "1.0.228", features = ["derive"] }
dirs = "6.0.0" serde_json = "1.0.149"
env_logger = "0.11.9" toml = "1.0.0"
libloading = "0.9.0" libloading = "0.9.0"
chrono = { version = "0.4.43", features = ["serde"] }
sysinfo = "0.38.2"
log = "0.4.29" log = "0.4.29"
env_logger = "0.11.9"
thiserror = "2.0.18"
parking_lot = "0.12.5" parking_lot = "0.12.5"
ringbuf = "0.4.8" ringbuf = "0.4.8"
serde = { features = [ "derive" ], version = "1.0.228" } dirs = "6.0.0"
serde_json = "1.0.149" clap = { version = "4.5.59", features = ["derive"] }
sha2 = "0.10.9"
sysinfo = "0.38.2"
thiserror = "2.0.18"
tokio = { features = [ "full" ], version = "1.49.0" }
toml = "1.0.0"
[profile.release] [profile.release]
codegen-units = 1
lto = true lto = true
opt-level = "z" opt-level = "z"
codegen-units = 1

View file

@ -42,8 +42,12 @@ interval_secs = 30 # Battery status changes slowly
enabled = true enabled = true
interval_secs = 5 interval_secs = 5
# Example: Disable a scanner
[scanners.system]
enabled = false
# Example: Custom scanner with extra parameters # Example: Custom scanner with extra parameters
# [scanners.custom] [scanners.custom]
# enabled = true enabled = true
# interval_secs = 60 interval_secs = 60
# extra = { custom_param = "value", threshold = 100 } extra = { custom_param = "value", threshold = 100 }

View file

@ -1,12 +1,14 @@
file_enabled = true
journal_enabled = true
log_dir = "/var/log/pscand" log_dir = "/var/log/pscand"
retention_days = 7 retention_days = 7
ring_buffer_size = 60 ring_buffer_size = 60
journal_enabled = true
file_enabled = true
[scanner_dirs] [scanner_dirs]
# Directories to load scanner plugins from # Directories to load scanner plugins from
dirs = [ "~/.local/share/pscand/scanners" ] dirs = [
"~/.local/share/pscand/scanners",
]
[scanners.system] [scanners.system]
enabled = true enabled = true

View file

@ -10,20 +10,19 @@ name = "pscand"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
chrono.workspace = true
clap.workspace = true
dirs.workspace = true
env_logger.workspace = true
libloading.workspace = true
log.workspace = true
parking_lot.workspace = true
pscand-core.workspace = true pscand-core.workspace = true
pscand-macros.workspace = true pscand-macros.workspace = true
ringbuf.workspace = true tokio.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
sha2.workspace = true
sysinfo.workspace = true
thiserror.workspace = true
tokio.workspace = true
toml.workspace = true toml.workspace = true
libloading.workspace = true
chrono.workspace = true
log.workspace = true
env_logger.workspace = true
thiserror.workspace = true
parking_lot.workspace = true
ringbuf.workspace = true
dirs.workspace = true
sysinfo.workspace = true
clap.workspace = true

View file

@ -1,58 +1,19 @@
#![allow(improper_ctypes_definitions)] #![allow(improper_ctypes_definitions)]
use std::{
fs,
io::Read,
path::{
Path,
PathBuf,
},
sync::{
atomic::{
AtomicBool,
Ordering,
},
Arc,
},
time::{
Duration,
Instant,
SystemTime,
UNIX_EPOCH,
},
};
use clap::Parser; use clap::Parser;
use libloading::Library; use libloading::Library;
use pscand_core::{ use pscand_core::logging::{LogLevel, RingBufferLogger};
logging::{ use pscand_core::scanner::Scanner;
LogLevel, use pscand_core::Config as CoreConfig;
RingBufferLogger, use std::path::PathBuf;
}, use std::sync::atomic::{AtomicBool, Ordering};
scanner::Scanner, use std::sync::Arc;
Config as CoreConfig, use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
}; use tokio::sync::RwLock;
use sha2::{ use tokio::time::interval;
Digest,
Sha256,
};
use tokio::{
sync::RwLock,
time::interval,
};
type ScannerCreator = pscand_core::ScannerCreatorFfi; type ScannerCreator = pscand_core::ScannerCreatorFfi;
fn expand_path(path: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
let path_str = path.to_str().ok_or("Invalid path encoding")?;
if path_str.starts_with("~/") {
if let Some(home) = dirs::home_dir() {
return Ok(home.join(&path_str[2..]));
}
}
Ok(path.to_path_buf())
}
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command( #[command(
name = "pscand", name = "pscand",
@ -66,44 +27,13 @@ enum Args {
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct RunArgs { struct RunArgs {
#[arg(short, long, default_value = "~/.config/pscand/pscand.toml")] #[arg(short, long, default_value = "/etc/pscand/pscand.toml")]
config: PathBuf, config: PathBuf,
#[arg(short, long)] #[arg(short, long)]
debug: bool, debug: bool,
} }
fn verify_library(path: &Path) -> Result<(), String> {
let mut file = fs::File::open(path).map_err(|e| {
format!(
"failed to open library {} for verification: {}",
path.display(),
e
)
})?;
let mut buffer = [0u8; 4096];
let bytes_read = file.read(&mut buffer).map_err(|e| {
format!(
"failed to read library {} for hash calculation: {}",
path.display(),
e
)
})?;
if bytes_read < 4 {
return Err(format!(
"library {} is too small to be valid",
path.display()
));
}
let mut hasher = Sha256::new();
hasher.update(&buffer[..bytes_read]);
let _hash = hasher.finalize();
Ok(())
}
struct LoadedScanner { struct LoadedScanner {
name: String, name: String,
scanner: Arc<RwLock<Box<dyn Scanner>>>, scanner: Arc<RwLock<Box<dyn Scanner>>>,
@ -185,14 +115,9 @@ impl DaemonState {
Ok(()) Ok(())
} }
async fn write_status( async fn write_status(&self, logger: &RingBufferLogger) -> std::io::Result<()> {
&self,
logger: &RingBufferLogger,
) -> std::io::Result<()> {
let uptime = SystemTime::now() let uptime = SystemTime::now()
.duration_since( .duration_since(self.start_time.try_read().map(|t| *t).unwrap_or(UNIX_EPOCH))
self.start_time.try_read().map(|t| *t).unwrap_or(UNIX_EPOCH),
)
.map(|d| d.as_secs()) .map(|d| d.as_secs())
.unwrap_or(0); .unwrap_or(0);
let collections = self.collection_count.try_read().map(|c| *c).unwrap_or(0); let collections = self.collection_count.try_read().map(|c| *c).unwrap_or(0);
@ -233,10 +158,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
match args { match args {
Args::Run(run_args) => { Args::Run(run_args) => {
run_daemon(run_args).await?; run_daemon(run_args).await?;
}, }
Args::List => { Args::List => {
list_scanners().await?; list_scanners().await?;
}, }
} }
Ok(()) Ok(())
@ -244,44 +169,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> { async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
if args.debug { if args.debug {
env_logger::Builder::from_env( env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
env_logger::Env::default().default_filter_or("debug"),
)
.init();
} else { } else {
env_logger::Builder::from_env( env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
env_logger::Env::default().default_filter_or("info"),
)
.init();
} }
log::info!("Starting pscand daemon"); log::info!("Starting pscand daemon");
// Expand ~ in config path let config = if args.config.exists() {
let config_path = expand_path(&args.config) CoreConfig::load(&args.config)?
.map_err(|e| format!("Failed to expand config path: {}", e))?;
let config = if config_path.exists() {
CoreConfig::load(&config_path)?
} else { } else {
log::warn!("Config file not found at {:?}", config_path); log::warn!("Config file not found, using defaults");
log::info!(
"Creating default config. Run with --config to specify a different path."
);
// Create default config directory if it doesn't exist
if let Some(parent) = config_path.parent() {
if let Err(e) = std::fs::create_dir_all(parent) {
log::error!("Failed to create config directory {:?}: {}", parent, e);
}
}
CoreConfig::default() CoreConfig::default()
}; };
std::fs::create_dir_all(&config.log_dir).map_err(|e| { std::fs::create_dir_all(&config.log_dir)?;
format!("Failed to create log directory {:?}: {}", config.log_dir, e)
})?;
let log_file = config.log_dir.join("pscand.log"); let log_file = config.log_dir.join("pscand.log");
let logger = Arc::new(RingBufferLogger::new( let logger = Arc::new(RingBufferLogger::new(
@ -354,23 +256,8 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
let scanners = load_scanners(&config, &logger).await?; let scanners = load_scanners(&config, &logger).await?;
if scanners.is_empty() { if scanners.is_empty() {
log::error!("No scanners loaded!"); log::warn!("No scanners loaded!");
log::error!("Please ensure:"); logger.log(LogLevel::Warn, "daemon", "no_scanners", "{}".to_string());
log::error!(
" 1. Scanner plugins are installed in one of the configured directories"
);
log::error!(
" 2. Scanner directories are correctly set in config file or \
PSCAND_SCANNER_DIRS env var"
);
log::error!(" 3. Scanners are not disabled in the configuration");
logger.log(
LogLevel::Error,
"daemon",
"no_scanners",
"No scanner plugins loaded".to_string(),
);
return Err("No scanners loaded. See error messages above.".into());
} else { } else {
log::info!("Loaded {} scanners", scanners.len()); log::info!("Loaded {} scanners", scanners.len());
logger.log( logger.log(
@ -421,7 +308,7 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
}) })
.to_string(), .to_string(),
); );
}, }
Err(e) => { Err(e) => {
logger.log( logger.log(
LogLevel::Error, LogLevel::Error,
@ -429,22 +316,16 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
"final_collection_error", "final_collection_error",
e.to_string(), e.to_string(),
); );
}, }
} }
state.record_collection().await; state.record_collection().await;
if let Err(e) = state.update_heartbeat().await { state.update_heartbeat().await.ok();
log::warn!("Failed to update heartbeat during shutdown: {}", e);
}
break; break;
} }
let scan_start = Instant::now(); let scan_start = Instant::now();
let collect_result = { let scanner_guard = scanner.read().await;
let guard = scanner.read().await; match scanner_guard.collect() {
guard.collect()
};
match collect_result {
Ok(metrics) => { Ok(metrics) => {
let elapsed = scan_start.elapsed().as_millis(); let elapsed = scan_start.elapsed().as_millis();
logger.log( logger.log(
@ -458,24 +339,13 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
.to_string(), .to_string(),
); );
state.record_collection().await; state.record_collection().await;
}, }
Err(e) => { Err(e) => {
logger.log( logger.log(LogLevel::Error, &name, "collection_error", e.to_string());
LogLevel::Error,
&name,
"collection_error",
e.to_string(),
);
state.record_error().await; state.record_error().await;
},
}
if let Err(e) = state.update_heartbeat().await {
log::warn!("Failed to update heartbeat: {}", e);
} }
} }
state.update_heartbeat().await.ok();
if let Err(e) = scanner.write().await.cleanup() {
logger.log(LogLevel::Warn, &name, "cleanup_error", e.to_string());
} }
}); });
@ -490,15 +360,12 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
let mut ticker = interval(Duration::from_secs(5)); let mut ticker = interval(Duration::from_secs(5));
loop { loop {
ticker.tick().await; ticker.tick().await;
if let Err(e) = daemon_state_hb.update_heartbeat().await { daemon_state_hb.update_heartbeat().await.ok();
log::warn!("Failed to update heartbeat: {}", e);
}
} }
}); });
let sigint = tokio::signal::ctrl_c(); let sigint = tokio::signal::ctrl_c();
let mut sigterm = let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
tokio::select! { tokio::select! {
_ = sigint => { _ = sigint => {
@ -556,12 +423,8 @@ async fn load_scanners(
) -> Result<Vec<LoadedScanner>, Box<dyn std::error::Error>> { ) -> Result<Vec<LoadedScanner>, Box<dyn std::error::Error>> {
let mut loaded = Vec::new(); let mut loaded = Vec::new();
let mut missing_dirs = Vec::new();
for dir in &config.scanner_dirs { for dir in &config.scanner_dirs {
if !dir.exists() { if !dir.exists() {
missing_dirs.push(dir.clone());
log::warn!("Scanner directory does not exist: {:?}", dir);
continue; continue;
} }
@ -575,18 +438,6 @@ async fn load_scanners(
continue; continue;
} }
// Verify library before loading
if let Err(e) = verify_library(&path) {
log::error!("Scanner {:?} failed verification: {}", path, e);
logger.log(
LogLevel::Error,
"loader",
"verification_failed",
format!("{}: {}", path.display(), e),
);
continue;
}
unsafe { unsafe {
match Library::new(&path) { match Library::new(&path) {
Ok(lib) => { Ok(lib) => {
@ -602,7 +453,7 @@ async fn load_scanners(
format!("{}: {}", path.display(), e), format!("{}: {}", path.display(), e),
); );
continue; continue;
}, }
}; };
let scanner = match pscand_core::get_scanner(creator()) { let scanner = match pscand_core::get_scanner(creator()) {
@ -613,7 +464,7 @@ async fn load_scanners(
path.display() path.display()
); );
continue; continue;
}, }
}; };
let name = scanner.name().to_string(); let name = scanner.name().to_string();
@ -655,7 +506,7 @@ async fn load_scanners(
interval, interval,
library: lib, library: lib,
}); });
}, }
Err(e) => { Err(e) => {
log::warn!("Failed to load scanner {:?}: {}", path, e); log::warn!("Failed to load scanner {:?}: {}", path, e);
logger.log( logger.log(
@ -664,18 +515,10 @@ async fn load_scanners(
"load_failed", "load_failed",
format!("{}: {}", path.display(), e), format!("{}: {}", path.display(), e),
); );
},
} }
} }
} }
} }
if !missing_dirs.is_empty() {
log::warn!("The following scanner directories do not exist:");
for dir in &missing_dirs {
log::warn!(" - {:?}", dir);
}
log::info!("Create these directories or update scanner_dirs in config");
} }
Ok(loaded) Ok(loaded)
@ -687,12 +530,7 @@ async fn list_scanners() -> Result<(), Box<dyn std::error::Error>> {
println!(" - sensor: hwmon temperature, fan, voltage sensors"); println!(" - sensor: hwmon temperature, fan, voltage sensors");
println!(" - power: battery and power supply status"); println!(" - power: battery and power supply status");
println!(" - proc: process count and zombie detection"); println!(" - proc: process count and zombie detection");
println!( println!("\nDynamic scanners are loaded from $PSCAND_SCANNER_DIRS (colon-separated)");
"\nDynamic scanners are loaded from $PSCAND_SCANNER_DIRS (colon-separated)" println!(" Default fallback: ~/.local/share/pscand/scanners/ or ~/.config/pscand/scanners/");
);
println!(
" Default fallback: ~/.local/share/pscand/scanners/ or \
~/.config/pscand/scanners/"
);
Ok(()) Ok(())
} }

View file

@ -6,17 +6,17 @@ license.workspace = true
authors.workspace = true authors.workspace = true
[dependencies] [dependencies]
chrono.workspace = true tokio.workspace = true
dirs.workspace = true
log.workspace = true
parking_lot.workspace = true
ringbuf.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
sysinfo.workspace = true
thiserror.workspace = true
tokio.workspace = true
toml.workspace = true toml.workspace = true
chrono.workspace = true
log.workspace = true
thiserror.workspace = true
parking_lot.workspace = true
dirs.workspace = true
sysinfo.workspace = true
ringbuf.workspace = true
[lib] [lib]
name = "pscand_core" name = "pscand_core"

View file

@ -1,15 +1,6 @@
use std::{ use serde::{Deserialize, Serialize};
collections::HashMap, use std::collections::HashMap;
path::{ use std::path::{Path, PathBuf};
Path,
PathBuf,
},
};
use serde::{
Deserialize,
Serialize,
};
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]

View file

@ -1,8 +1,6 @@
use std::{ use std::collections::HashMap;
collections::HashMap, use std::fs;
fs, use std::path::PathBuf;
path::PathBuf,
};
pub struct PowerHelper; pub struct PowerHelper;
@ -105,8 +103,7 @@ impl PowerHelper {
Ok(None) Ok(None)
} }
pub fn power_supplies( pub fn power_supplies() -> std::io::Result<HashMap<String, HashMap<String, String>>> {
) -> std::io::Result<HashMap<String, HashMap<String, String>>> {
let mut supplies = HashMap::new(); let mut supplies = HashMap::new();
let power_supply_path = PathBuf::from("/sys/class/power_supply"); let power_supply_path = PathBuf::from("/sys/class/power_supply");

View file

@ -1,7 +1,5 @@
use std::{ use std::collections::HashMap;
collections::HashMap, use std::fs;
fs,
};
pub struct ProcessHelper; pub struct ProcessHelper;
@ -27,16 +25,14 @@ impl ProcessHelper {
} }
let pid: u32 = match path.file_name() { let pid: u32 = match path.file_name() {
Some(name) => { Some(name) => match name.to_str() {
match name.to_str() {
Some(s) => s.parse().ok(), Some(s) => s.parse().ok(),
None => None, None => None,
} }
.ok_or(std::io::Error::new( .ok_or(std::io::Error::new(
std::io::ErrorKind::InvalidData, std::io::ErrorKind::InvalidData,
"invalid pid", "invalid pid",
))? ))?,
},
None => continue, None => continue,
}; };
@ -92,12 +88,10 @@ impl ProcessHelper {
} }
pub fn zombie_processes() -> std::io::Result<Vec<ProcessInfo>> { pub fn zombie_processes() -> std::io::Result<Vec<ProcessInfo>> {
Ok( Ok(Self::list_processes()?
Self::list_processes()?
.into_iter() .into_iter()
.filter(|p| p.state.starts_with('Z')) .filter(|p| p.state.starts_with('Z'))
.collect(), .collect())
)
} }
pub fn process_count() -> std::io::Result<HashMap<String, usize>> { pub fn process_count() -> std::io::Result<HashMap<String, usize>> {
@ -115,16 +109,14 @@ impl ProcessHelper {
'R' => *counts.get_mut("running").unwrap() += 1, 'R' => *counts.get_mut("running").unwrap() += 1,
'S' | 'D' => *counts.get_mut("sleeping").unwrap() += 1, 'S' | 'D' => *counts.get_mut("sleeping").unwrap() += 1,
'Z' => *counts.get_mut("zombie").unwrap() += 1, 'Z' => *counts.get_mut("zombie").unwrap() += 1,
_ => {}, _ => {}
} }
} }
Ok(counts) Ok(counts)
} }
pub fn top_memory_processes( pub fn top_memory_processes(count: usize) -> std::io::Result<Vec<ProcessInfo>> {
count: usize,
) -> std::io::Result<Vec<ProcessInfo>> {
let mut processes = Self::list_processes()?; let mut processes = Self::list_processes()?;
processes.sort_by(|a, b| b.memory_kb.cmp(&a.memory_kb)); processes.sort_by(|a, b| b.memory_kb.cmp(&a.memory_kb));
processes.truncate(count); processes.truncate(count);

View file

@ -1,7 +1,5 @@
use std::{ use std::collections::HashMap;
collections::HashMap, use std::fs;
fs,
};
pub struct ResourceHelper; pub struct ResourceHelper;
@ -74,8 +72,7 @@ impl ResourceHelper {
Ok(result) Ok(result)
} }
pub fn disk_stats() -> std::io::Result<HashMap<String, HashMap<String, u64>>> pub fn disk_stats() -> std::io::Result<HashMap<String, HashMap<String, u64>>> {
{
let content = fs::read_to_string("/proc/diskstats")?; let content = fs::read_to_string("/proc/diskstats")?;
let mut result = HashMap::new(); let mut result = HashMap::new();
@ -84,8 +81,7 @@ impl ResourceHelper {
if parts.len() >= 14 { if parts.len() >= 14 {
let device = parts[2].to_string(); let device = parts[2].to_string();
let mut stats = HashMap::new(); let mut stats = HashMap::new();
stats stats.insert("reads_completed".to_string(), parts[3].parse().unwrap_or(0));
.insert("reads_completed".to_string(), parts[3].parse().unwrap_or(0));
stats.insert("reads_merged".to_string(), parts[4].parse().unwrap_or(0)); stats.insert("reads_merged".to_string(), parts[4].parse().unwrap_or(0));
stats.insert("sectors_read".to_string(), parts[5].parse().unwrap_or(0)); stats.insert("sectors_read".to_string(), parts[5].parse().unwrap_or(0));
stats.insert("reads_ms".to_string(), parts[6].parse().unwrap_or(0)); stats.insert("reads_ms".to_string(), parts[6].parse().unwrap_or(0));
@ -93,10 +89,8 @@ impl ResourceHelper {
"writes_completed".to_string(), "writes_completed".to_string(),
parts[7].parse().unwrap_or(0), parts[7].parse().unwrap_or(0),
); );
stats stats.insert("writes_merged".to_string(), parts[8].parse().unwrap_or(0));
.insert("writes_merged".to_string(), parts[8].parse().unwrap_or(0)); stats.insert("sectors_written".to_string(), parts[9].parse().unwrap_or(0));
stats
.insert("sectors_written".to_string(), parts[9].parse().unwrap_or(0));
stats.insert("writes_ms".to_string(), parts[10].parse().unwrap_or(0)); stats.insert("writes_ms".to_string(), parts[10].parse().unwrap_or(0));
result.insert(device, stats); result.insert(device, stats);
} }

View file

@ -1,11 +1,6 @@
use std::{ use std::collections::HashMap;
collections::HashMap, use std::fs;
fs, use std::path::{Path, PathBuf};
path::{
Path,
PathBuf,
},
};
pub struct SensorHelper; pub struct SensorHelper;
@ -26,10 +21,7 @@ impl SensorHelper {
Ok(hwmons) Ok(hwmons)
} }
pub fn read_hwmon_sensor( pub fn read_hwmon_sensor(hwmon_path: &Path, sensor: &str) -> std::io::Result<Option<f64>> {
hwmon_path: &Path,
sensor: &str,
) -> std::io::Result<Option<f64>> {
let sensor_path = hwmon_path.join(sensor); let sensor_path = hwmon_path.join(sensor);
if sensor_path.exists() { if sensor_path.exists() {
let content = fs::read_to_string(sensor_path)?; let content = fs::read_to_string(sensor_path)?;
@ -39,9 +31,7 @@ impl SensorHelper {
} }
} }
pub fn hwmon_info( pub fn hwmon_info(hwmon_path: &Path) -> std::io::Result<HashMap<String, String>> {
hwmon_path: &Path,
) -> std::io::Result<HashMap<String, String>> {
let mut info = HashMap::new(); let mut info = HashMap::new();
let name_path = hwmon_path.join("name"); let name_path = hwmon_path.join("name");
@ -63,10 +53,7 @@ impl SensorHelper {
.ok() .ok()
.flatten() .flatten()
{ {
info.insert( info.insert(format!("temp_{}_celsius", id), format!("{}", t / 1000.0));
format!("temp_{}_celsius", id),
format!("{}", t / 1000.0),
);
} }
} }
if filename.starts_with("fan") && filename.ends_with("_input") { if filename.starts_with("fan") && filename.ends_with("_input") {
@ -86,8 +73,7 @@ impl SensorHelper {
.ok() .ok()
.flatten() .flatten()
{ {
info info.insert(format!("voltage_{}_mv", id), format!("{}", v / 1000.0));
.insert(format!("voltage_{}_mv", id), format!("{}", v / 1000.0));
} }
} }
} }
@ -96,8 +82,7 @@ impl SensorHelper {
Ok(info) Ok(info)
} }
pub fn all_sensors( pub fn all_sensors() -> std::io::Result<HashMap<String, HashMap<String, String>>> {
) -> std::io::Result<HashMap<String, HashMap<String, String>>> {
let mut all = HashMap::new(); let mut all = HashMap::new();
for hwmon in Self::discover_hwmon()? { for hwmon in Self::discover_hwmon()? {

View file

@ -1,7 +1,5 @@
use std::{ use std::fs;
fs, use std::time::Duration;
time::Duration,
};
pub struct SystemHelper; pub struct SystemHelper;
@ -16,8 +14,7 @@ impl SystemHelper {
} }
pub fn boot_id() -> std::io::Result<String> { pub fn boot_id() -> std::io::Result<String> {
fs::read_to_string("/proc/sys/kernel/random/boot_id") fs::read_to_string("/proc/sys/kernel/random/boot_id").map(|s| s.trim().to_string())
.map(|s| s.trim().to_string())
} }
pub fn load_average() -> std::io::Result<(f64, f64, f64)> { pub fn load_average() -> std::io::Result<(f64, f64, f64)> {
@ -39,12 +36,10 @@ impl SystemHelper {
} }
pub fn hostname() -> std::io::Result<String> { pub fn hostname() -> std::io::Result<String> {
fs::read_to_string("/proc/sys/kernel/hostname") fs::read_to_string("/proc/sys/kernel/hostname").map(|s| s.trim().to_string())
.map(|s| s.trim().to_string())
} }
pub fn kernel_version() -> std::io::Result<String> { pub fn kernel_version() -> std::io::Result<String> {
fs::read_to_string("/proc/sys/kernel/osrelease") fs::read_to_string("/proc/sys/kernel/osrelease").map(|s| s.trim().to_string())
.map(|s| s.trim().to_string())
} }
} }

View file

@ -4,17 +4,8 @@ pub mod logging;
pub mod scanner; pub mod scanner;
pub use config::Config; pub use config::Config;
pub use logging::{ pub use logging::{DaemonLogEntry, LogLevel, RingBufferLogger};
DaemonLogEntry,
LogLevel,
RingBufferLogger,
};
pub use scanner::{ pub use scanner::{
get_scanner, get_scanner, register_scanner, MetricValue, Scanner, ScannerCreatorFfi, ScannerError,
register_scanner,
MetricValue,
Scanner,
ScannerCreatorFfi,
ScannerError,
}; };
pub type Result<T> = std::result::Result<T, ScannerError>; pub type Result<T> = std::result::Result<T, ScannerError>;

View file

@ -1,42 +1,19 @@
use std::{ use chrono::{DateTime, Utc};
collections::HashMap,
fs::{
self,
OpenOptions,
},
io::Write,
path::{
Path,
PathBuf,
},
sync::{
atomic::{
AtomicU64,
Ordering,
},
Arc,
},
time::Instant,
};
use chrono::{
DateTime,
Utc,
};
use parking_lot::Mutex; use parking_lot::Mutex;
use ringbuf::{ use ringbuf::{
storage::Heap, storage::Heap,
traits::*, traits::*,
wrap::caching::{ wrap::caching::{CachingCons, CachingProd},
CachingCons,
CachingProd,
},
SharedRb, SharedRb,
}; };
use serde::{ use serde::{Deserialize, Serialize};
Deserialize, use std::collections::HashMap;
Serialize, use std::fs::{self, OpenOptions};
}; use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Instant;
use crate::scanner::MetricValue; use crate::scanner::MetricValue;
@ -93,11 +70,7 @@ pub struct DaemonLogEntry {
} }
impl DaemonLogEntry { impl DaemonLogEntry {
pub fn new( pub fn new(source: impl Into<String>, event: impl Into<String>, message: String) -> Self {
source: impl Into<String>,
event: impl Into<String>,
message: String,
) -> Self {
Self { Self {
timestamp: Utc::now(), timestamp: Utc::now(),
level: LogLevel::Info, level: LogLevel::Info,
@ -118,10 +91,7 @@ impl DaemonLogEntry {
} }
impl LogEntry { impl LogEntry {
pub fn new( pub fn new(scanner: impl Into<String>, metrics: HashMap<String, MetricValue>) -> Self {
scanner: impl Into<String>,
metrics: HashMap<String, MetricValue>,
) -> Self {
Self { Self {
timestamp: Utc::now(), timestamp: Utc::now(),
scanner: scanner.into(), scanner: scanner.into(),
@ -154,8 +124,7 @@ impl LogEntry {
} }
pub fn to_json(&self) -> String { pub fn to_json(&self) -> String {
serde_json::to_string(self) serde_json::to_string(self).unwrap_or_else(|e| format!("{{\"error\":\"{}\"}}", e))
.unwrap_or_else(|e| format!("{{\"error\":\"{}\"}}", e))
} }
pub fn to_journal(&self) -> String { pub fn to_journal(&self) -> String {
@ -242,13 +211,7 @@ impl RingBufferLogger {
} }
} }
pub fn log( pub fn log(&self, level: LogLevel, source: &str, event: &str, message: String) {
&self,
level: LogLevel,
source: &str,
event: &str,
message: String,
) {
let entry = DaemonLogEntry { let entry = DaemonLogEntry {
timestamp: Utc::now(), timestamp: Utc::now(),
level, level,
@ -293,23 +256,19 @@ impl RingBufferLogger {
use std::os::unix::net::UnixDatagram; use std::os::unix::net::UnixDatagram;
let journal_msg = format!( let journal_msg = format!(
"PRIORITY={}\nSYSLOG_IDENTIFIER=pscand\nPSCAND_SCANNER={}\nMESSAGE={}\\ "PRIORITY={}\nSYSLOG_IDENTIFIER=pscand\nPSCAND_SCANNER={}\nMESSAGE={}\n",
n",
priority, scanner, msg priority, scanner, msg
); );
if let Ok(sock) = UnixDatagram::unbound() { if let Ok(sock) = UnixDatagram::unbound() {
let _ = let _ = sock.send_to(journal_msg.as_bytes(), "/run/systemd/journal/socket");
sock.send_to(journal_msg.as_bytes(), "/run/systemd/journal/socket");
} }
}); });
} }
fn write_to_file(&self, entry: &LogEntry) { fn write_to_file(&self, entry: &LogEntry) {
if let Some(ref path) = self.file_path { if let Some(ref path) = self.file_path {
if let Ok(mut file) = if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
OpenOptions::new().create(true).append(true).open(path)
{
let _ = writeln!(file, "{}", entry.to_json()); let _ = writeln!(file, "{}", entry.to_json());
} }
} }
@ -333,23 +292,19 @@ impl RingBufferLogger {
use std::os::unix::net::UnixDatagram; use std::os::unix::net::UnixDatagram;
let journal_msg = format!( let journal_msg = format!(
"PRIORITY={}\nSYSLOG_IDENTIFIER=pscand\nPSCAND_SOURCE={}\\ "PRIORITY={}\nSYSLOG_IDENTIFIER=pscand\nPSCAND_SOURCE={}\nPSCAND_EVENT={}\nMESSAGE={}\n",
nPSCAND_EVENT={}\nMESSAGE={}\n",
priority, source, event, msg priority, source, event, msg
); );
if let Ok(sock) = UnixDatagram::unbound() { if let Ok(sock) = UnixDatagram::unbound() {
let _ = let _ = sock.send_to(journal_msg.as_bytes(), "/run/systemd/journal/socket");
sock.send_to(journal_msg.as_bytes(), "/run/systemd/journal/socket");
} }
}); });
} }
fn write_daemon_to_file(&self, entry: &DaemonLogEntry) { fn write_daemon_to_file(&self, entry: &DaemonLogEntry) {
if let Some(ref path) = self.file_path { if let Some(ref path) = self.file_path {
if let Ok(mut file) = if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
OpenOptions::new().create(true).append(true).open(path)
{
let _ = writeln!(file, "{}", entry.to_json()); let _ = writeln!(file, "{}", entry.to_json());
} }
} }
@ -429,16 +384,10 @@ impl RuntimeMonitor {
} }
} }
pub fn record_collection( pub fn record_collection(&self, scanner: &str, time_ms: u64, error: Option<&str>) {
&self,
scanner: &str,
time_ms: u64,
error: Option<&str>,
) {
self.total_collections.fetch_add(1, Ordering::Relaxed); self.total_collections.fetch_add(1, Ordering::Relaxed);
self.last_collection_time.store(time_ms, Ordering::Relaxed); self.last_collection_time.store(time_ms, Ordering::Relaxed);
self self.collection_time_sum
.collection_time_sum
.fetch_add(time_ms, Ordering::Relaxed); .fetch_add(time_ms, Ordering::Relaxed);
let mut collections = self.scanner_collections.lock(); let mut collections = self.scanner_collections.lock();
@ -514,13 +463,16 @@ impl RuntimeMonitor {
0.0 0.0
}; };
scanner_stats.insert(name.clone(), ScannerStats { scanner_stats.insert(
name.clone(),
ScannerStats {
collections: coll_count, collections: coll_count,
errors: err_count, errors: err_count,
last_error: last_err, last_error: last_err,
last_collection_time_ms: last_t, last_collection_time_ms: last_t,
avg_collection_time_ms: avg_t, avg_collection_time_ms: avg_t,
}); },
);
} }
RuntimeStats { RuntimeStats {
@ -591,12 +543,8 @@ impl CrashDetector {
} }
} }
pub fn new_with_interval( pub fn new_with_interval(state_dir: PathBuf, heartbeat_interval_secs: u64) -> Self {
state_dir: PathBuf, let heartbeat = Heartbeat::new(state_dir.join("heartbeat"), heartbeat_interval_secs);
heartbeat_interval_secs: u64,
) -> Self {
let heartbeat =
Heartbeat::new(state_dir.join("heartbeat"), heartbeat_interval_secs);
let state_file = state_dir.join("state"); let state_file = state_dir.join("state");
Self { Self {
heartbeat, heartbeat,

View file

@ -1,35 +1,23 @@
use std::{ use std::collections::HashMap;
collections::HashMap, use std::os::raw::c_void;
os::raw::c_void, use std::sync::atomic::{AtomicUsize, Ordering};
sync::{ use std::sync::LazyLock;
atomic::{ use std::sync::Mutex;
AtomicUsize, use std::time::Duration;
Ordering,
},
LazyLock,
Mutex,
},
time::Duration,
};
use serde::{ use serde::{Deserialize, Serialize};
Deserialize,
Serialize,
};
use thiserror::Error; use thiserror::Error;
pub type ScannerBox = Box<dyn Scanner>; pub type ScannerBox = Box<dyn Scanner>;
pub type ScannerResult = Result<ScannerBox>; pub type ScannerResult = Result<ScannerBox>;
pub type ScannerMetrics = HashMap<String, MetricValue>; pub type ScannerMetrics = HashMap<String, MetricValue>;
pub type ScannerCollectionResult = Result<ScannerMetrics>; pub type ScannerCollectionResult = Result<ScannerMetrics>;
pub type ScannerCollectFn = pub type ScannerCollectFn = Box<dyn Fn() -> ScannerCollectionResult + Send + Sync>;
Box<dyn Fn() -> ScannerCollectionResult + Send + Sync>; pub type ScannerInitFnMut = Mutex<Box<dyn FnMut(&toml::Value) -> Result<()> + Send>>;
pub type ScannerInitFnMut =
Mutex<Box<dyn FnMut(&toml::Value) -> Result<()> + Send>>;
pub type ScannerCleanupFnMut = Mutex<Box<dyn FnMut() -> Result<()> + Send>>; pub type ScannerCleanupFnMut = Mutex<Box<dyn FnMut() -> Result<()> + Send>>;
pub type ScannerCreatorFfi = unsafe extern fn() -> *mut c_void; pub type ScannerCreatorFfi = unsafe extern "C" fn() -> *mut c_void;
pub type ScannerInitFn = unsafe extern fn() -> *mut c_void; pub type ScannerInitFn = unsafe extern "C" fn() -> *mut c_void;
pub type ScannerDropFn = unsafe extern fn(*mut c_void); pub type ScannerDropFn = unsafe extern "C" fn(*mut c_void);
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ScannerError { pub enum ScannerError {

View file

@ -11,4 +11,4 @@ proc-macro = true
[dependencies] [dependencies]
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = { features = [ "full" ], version = "2" } syn = { version = "2", features = ["full"] }

View file

@ -1,9 +1,6 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ use syn::{parse_macro_input, ItemFn};
parse_macro_input,
ItemFn,
};
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn scanner(name: TokenStream, input: TokenStream) -> TokenStream { pub fn scanner(name: TokenStream, input: TokenStream) -> TokenStream {

View file

@ -143,28 +143,17 @@ pscand list
Configuration is stored in `/etc/pscand/pscand.toml`: Configuration is stored in `/etc/pscand/pscand.toml`:
```toml ```toml
# Scanner plugin directories [daemon]
scanner_dirs = ["~/.local/share/pscand/scanners"] log_level = "info"
scanner_dirs = ["/usr/lib/pscand/scanners"]
# Where to store log files
log_dir = "~/.local/share/pscand/logs"
# Per-scanner configuration (interval in seconds)
[scanners.system] [scanners.system]
enabled = true enabled = true
interval_secs = 5 interval = 5000 # milliseconds
[scanners.sensor] [scanners.sensor]
enabled = true enabled = true
interval_secs = 10 interval = 10000
[scanners.power]
enabled = true
interval_secs = 30
[scanners.proc]
enabled = true
interval_secs = 5
``` ```
### Scanner Directories ### Scanner Directories

View file

@ -28,36 +28,21 @@ pscand-core = { workspace = true }
## Implementing the `Scanner` trait ## Implementing the `Scanner` trait
```rust ```rust
use std::time::Duration; use pscand_core::scanner::Scanner;
use pscand_core::scanner::{Scanner, ScannerCollectionResult, MetricValue, Result};
use pscand_macros::scanner; use pscand_macros::scanner;
use std::collections::HashMap;
pub struct CustomScanner; pub struct CustomScanner;
impl Scanner for CustomScanner { impl Scanner for CustomScanner {
fn name(&self) -> &'static str { fn name(&self) -> &str {
"custom" "custom"
} }
fn interval(&self) -> Duration { fn collect(&self) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
Duration::from_secs(5) // Collect your metrics
} Ok(serde_json::json!({
"value": 42
fn init(&mut self, _config: &toml::Value) -> Result<()> { }))
// Initialize your scanner (e.g., open hardware interfaces)
Ok(())
}
fn collect(&self) -> ScannerCollectionResult {
let mut metrics = HashMap::new();
metrics.insert("value".to_string(), MetricValue::from_i64(42));
Ok(metrics)
}
fn cleanup(&mut self) -> Result<()> {
// Clean up resources
Ok(())
} }
} }

View file

@ -17,7 +17,7 @@ mkShell {
cargo cargo
# Tools # Tools
(rustfmt.override {asNightly = true;}) rustfmt
clippy clippy
cargo cargo
taplo taplo

View file

@ -10,6 +10,6 @@ pscand-core.workspace = true
toml.workspace = true toml.workspace = true
[lib] [lib]
crate-type = [ "cdylib" ]
name = "scanner_power" name = "scanner_power"
path = "src/lib.rs" path = "src/lib.rs"
crate-type = ["cdylib"]

View file

@ -1,20 +1,12 @@
use std::{ use pscand_core::helpers::PowerHelper;
collections::HashMap, use pscand_core::scanner::{MetricValue, Scanner};
time::Duration, use std::collections::HashMap;
}; use std::time::Duration;
use pscand_core::{
helpers::PowerHelper,
scanner::{
MetricValue,
Scanner,
},
};
struct PowerScanner; struct PowerScanner;
#[no_mangle] #[no_mangle]
pub extern fn pscand_scanner() -> *mut std::os::raw::c_void { pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void {
Box::into_raw(Box::new(PowerScanner)) as *mut std::os::raw::c_void Box::into_raw(Box::new(PowerScanner)) as *mut std::os::raw::c_void
} }

View file

@ -10,6 +10,6 @@ pscand-core.workspace = true
toml.workspace = true toml.workspace = true
[lib] [lib]
crate-type = [ "cdylib" ]
name = "scanner_proc" name = "scanner_proc"
path = "src/lib.rs" path = "src/lib.rs"
crate-type = ["cdylib"]

View file

@ -1,20 +1,12 @@
use std::{ use pscand_core::helpers::ProcessHelper;
collections::HashMap, use pscand_core::scanner::{MetricValue, Scanner};
time::Duration, use std::collections::HashMap;
}; use std::time::Duration;
use pscand_core::{
helpers::ProcessHelper,
scanner::{
MetricValue,
Scanner,
},
};
struct ProcScanner; struct ProcScanner;
#[no_mangle] #[no_mangle]
pub extern fn pscand_scanner() -> *mut std::os::raw::c_void { pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void {
Box::into_raw(Box::new(ProcScanner)) as *mut std::os::raw::c_void Box::into_raw(Box::new(ProcScanner)) as *mut std::os::raw::c_void
} }

View file

@ -10,6 +10,6 @@ pscand-core.workspace = true
toml.workspace = true toml.workspace = true
[lib] [lib]
crate-type = [ "cdylib" ]
name = "scanner_sensor" name = "scanner_sensor"
path = "src/lib.rs" path = "src/lib.rs"
crate-type = ["cdylib"]

View file

@ -1,21 +1,13 @@
use std::{ use pscand_core::helpers::SensorHelper;
collections::HashMap, use pscand_core::scanner::{MetricValue, Scanner};
time::Duration, use pscand_core::Result;
}; use std::collections::HashMap;
use std::time::Duration;
use pscand_core::{
helpers::SensorHelper,
scanner::{
MetricValue,
Scanner,
},
Result,
};
struct SensorScanner; struct SensorScanner;
#[no_mangle] #[no_mangle]
pub extern fn pscand_scanner() -> *mut std::os::raw::c_void { pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void {
Box::into_raw(Box::new(SensorScanner)) as *mut std::os::raw::c_void Box::into_raw(Box::new(SensorScanner)) as *mut std::os::raw::c_void
} }
@ -49,8 +41,7 @@ impl Scanner for SensorScanner {
for (key, value) in values { for (key, value) in values {
if key.starts_with("temp_") && key.ends_with("_celsius") { if key.starts_with("temp_") && key.ends_with("_celsius") {
if let Ok(v) = value.parse::<f64>() { if let Ok(v) = value.parse::<f64>() {
metrics metrics.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
temp_count += 1; temp_count += 1;
if temp_count <= 3 { if temp_count <= 3 {
metrics.insert( metrics.insert(
@ -62,21 +53,17 @@ impl Scanner for SensorScanner {
} }
if key.starts_with("fan_") && key.ends_with("_rpm") { if key.starts_with("fan_") && key.ends_with("_rpm") {
if let Ok(v) = value.parse::<f64>() { if let Ok(v) = value.parse::<f64>() {
metrics metrics.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
fan_count += 1; fan_count += 1;
if fan_count <= 2 { if fan_count <= 2 {
metrics.insert( metrics
format!("fan_{}", fan_count), .insert(format!("fan_{}", fan_count), MetricValue::from_f64(v));
MetricValue::from_f64(v),
);
} }
} }
} }
if key.starts_with("voltage_") { if key.starts_with("voltage_") {
if let Ok(v) = value.parse::<f64>() { if let Ok(v) = value.parse::<f64>() {
metrics metrics.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
} }
} }
} }

View file

@ -10,6 +10,6 @@ pscand-core.workspace = true
toml.workspace = true toml.workspace = true
[lib] [lib]
crate-type = [ "cdylib" ]
name = "scanner_system" name = "scanner_system"
path = "src/lib.rs" path = "src/lib.rs"
crate-type = ["cdylib"]

View file

@ -1,19 +1,8 @@
use std::{ use pscand_core::helpers::{ResourceHelper, SystemHelper};
collections::HashMap, use pscand_core::scanner::{MetricValue, Scanner};
time::Duration, use pscand_core::Result;
}; use std::collections::HashMap;
use std::time::Duration;
use pscand_core::{
helpers::{
ResourceHelper,
SystemHelper,
},
scanner::{
MetricValue,
Scanner,
},
Result,
};
#[derive(Default)] #[derive(Default)]
struct SystemScanner { struct SystemScanner {
@ -21,7 +10,7 @@ struct SystemScanner {
} }
#[no_mangle] #[no_mangle]
pub extern fn pscand_scanner() -> *mut std::os::raw::c_void { pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void {
Box::into_raw(Box::new(SystemScanner::default())) as *mut std::os::raw::c_void Box::into_raw(Box::new(SystemScanner::default())) as *mut std::os::raw::c_void
} }
@ -56,8 +45,7 @@ impl Scanner for SystemScanner {
if let Ok(cpu) = ResourceHelper::cpu_usage() { if let Ok(cpu) = ResourceHelper::cpu_usage() {
if let Some(total) = cpu.get("total_usage_percent") { if let Some(total) = cpu.get("total_usage_percent") {
metrics metrics.insert("cpu_percent".to_string(), MetricValue::from_f64(*total));
.insert("cpu_percent".to_string(), MetricValue::from_f64(*total));
} }
} }
@ -74,9 +62,7 @@ impl Scanner for SystemScanner {
MetricValue::Integer(*available as i64), MetricValue::Integer(*available as i64),
); );
} }
if let (Some(used), Some(total)) = if let (Some(used), Some(total)) = (mem.get("MemAvailable"), mem.get("MemTotal")) {
(mem.get("MemAvailable"), mem.get("MemTotal"))
{
let used_mem = total.saturating_sub(*used); let used_mem = total.saturating_sub(*used);
metrics.insert( metrics.insert(
"mem_used_bytes".to_string(), "mem_used_bytes".to_string(),