initial commit
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ib131388c1056b6708b730a35011811026a6a6964
This commit is contained in:
commit
033e253259
33 changed files with 3126 additions and 0 deletions
228
pscand-cli/src/main.rs
Normal file
228
pscand-cli/src/main.rs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
use clap::Parser;
|
||||
use libloading::Library;
|
||||
use pscand_core::logging::{LogEntry, RingBufferLogger};
|
||||
use pscand_core::scanner::Scanner;
|
||||
use pscand_core::Config as CoreConfig;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::time::interval;
|
||||
|
||||
type ScannerCreator = unsafe extern "C" fn() -> Box<dyn Scanner>;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "pscand",
|
||||
version = "0.1.0",
|
||||
about = "Pluggable System Condition Monitoring Daemon"
|
||||
)]
|
||||
enum Args {
|
||||
Run(RunArgs),
|
||||
List,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct RunArgs {
|
||||
#[arg(short, long, default_value = "/etc/pscand/pscand.conf")]
|
||||
config: PathBuf,
|
||||
|
||||
#[arg(short, long)]
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
struct LoadedScanner {
|
||||
name: String,
|
||||
scanner: Arc<RwLock<Box<dyn Scanner>>>,
|
||||
#[allow(dead_code)]
|
||||
library: Library,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
match args {
|
||||
Args::Run(run_args) => {
|
||||
run_daemon(run_args).await?;
|
||||
}
|
||||
Args::List => {
|
||||
list_scanners().await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if args.debug {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
|
||||
} else {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
}
|
||||
|
||||
log::info!("Starting pscand daemon");
|
||||
|
||||
let config = if args.config.exists() {
|
||||
CoreConfig::load(&args.config)?
|
||||
} else {
|
||||
log::warn!("Config file not found, using defaults");
|
||||
CoreConfig::default()
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&config.log_dir)?;
|
||||
|
||||
let log_file = config.log_dir.join("pscand.log");
|
||||
let logger = Arc::new(RingBufferLogger::new(
|
||||
config.ring_buffer_size,
|
||||
Some(log_file),
|
||||
config.journal_enabled,
|
||||
config.file_enabled,
|
||||
));
|
||||
|
||||
std::panic::set_hook(Box::new({
|
||||
let logger = Arc::clone(&logger);
|
||||
let log_dir = config.log_dir.clone();
|
||||
move |panic_info| {
|
||||
let entries = logger.get_recent(60);
|
||||
let crash_log = log_dir.join("crash.log");
|
||||
if let Ok(mut file) = std::fs::File::create(&crash_log) {
|
||||
use std::io::Write;
|
||||
let _ = writeln!(file, "=== Crash at {} ===", chrono::Utc::now());
|
||||
let _ = writeln!(file, "Panic: {}", panic_info);
|
||||
let _ = writeln!(file, "\n=== Last {} log entries ===", entries.len());
|
||||
for entry in entries {
|
||||
let _ = writeln!(file, "{}", entry.to_json());
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
let scanners = load_scanners(&config).await?;
|
||||
|
||||
if scanners.is_empty() {
|
||||
log::warn!("No scanners loaded!");
|
||||
} else {
|
||||
log::info!("Loaded {} scanners", scanners.len());
|
||||
}
|
||||
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for loaded in scanners {
|
||||
let logger = Arc::clone(&logger);
|
||||
let name = loaded.name.clone();
|
||||
let scanner = loaded.scanner.clone();
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let mut ticker = interval(Duration::from_secs(1));
|
||||
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
|
||||
let scanner_guard = scanner.read().await;
|
||||
match scanner_guard.collect() {
|
||||
Ok(metrics) => {
|
||||
let entry = LogEntry::new(name.as_str(), metrics);
|
||||
logger.push(entry);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Scanner {} error: {}", name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
tokio::signal::ctrl_c().await?;
|
||||
log::info!("Shutting down pscand");
|
||||
|
||||
for handle in handles {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_scanners(
|
||||
config: &CoreConfig,
|
||||
) -> Result<Vec<LoadedScanner>, Box<dyn std::error::Error>> {
|
||||
let mut loaded = Vec::new();
|
||||
|
||||
for dir in &config.scanner_dirs {
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
log::info!("Loading scanners from {:?}", dir);
|
||||
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.extension().and_then(|s| s.to_str()) != Some("so") {
|
||||
continue;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
match Library::new(&path) {
|
||||
Ok(lib) => {
|
||||
let creator: libloading::Symbol<ScannerCreator> =
|
||||
match lib.get(b"pscand_scanner") {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::warn!("Scanner {:?} missing symbol: {}", path, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let scanner = creator();
|
||||
let name = scanner.name().to_string();
|
||||
|
||||
let scanner_enabled = config.is_scanner_enabled(&name);
|
||||
|
||||
if !scanner_enabled {
|
||||
log::info!("Scanner {} disabled in config", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut scanner = scanner;
|
||||
|
||||
if let Some(scanner_config) = config.scanner_config(&name) {
|
||||
let toml_map: toml::map::Map<String, toml::Value> =
|
||||
scanner_config.extra.clone().into_iter().collect();
|
||||
let toml_val = toml::Value::Table(toml_map);
|
||||
if let Err(e) = scanner.init(&toml_val) {
|
||||
log::error!("Failed to init scanner {}: {}", name, e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
loaded.push(LoadedScanner {
|
||||
name,
|
||||
scanner: Arc::new(RwLock::new(scanner)),
|
||||
library: lib,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load scanner {:?}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(loaded)
|
||||
}
|
||||
|
||||
async fn list_scanners() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Available built-in scanners:");
|
||||
println!(" - system: CPU, memory, disk, network, load average");
|
||||
println!(" - sensor: hwmon temperature, fan, voltage sensors");
|
||||
println!(" - power: battery and power supply status");
|
||||
println!(" - proc: process count and zombie detection");
|
||||
println!("\nDynamic scanners are loaded from $PSCAND_SCANNER_DIRS (colon-separated)");
|
||||
println!(" Default fallback: ~/.local/share/pscand/scanners/ or ~/.config/pscand/scanners/");
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue