Compare commits
6 commits
040d620917
...
ffae695240
| Author | SHA1 | Date | |
|---|---|---|---|
|
ffae695240 |
|||
|
a4a0b9135a |
|||
|
9bec96db1b |
|||
|
5850f342a1 |
|||
|
16672b2ff1 |
|||
|
7fdd64c379 |
31 changed files with 2538 additions and 2075 deletions
28
.rustfmt.toml
Normal file
28
.rustfmt.toml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
14
.taplo.toml
Normal file
14
.taplo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[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
72
Cargo.lock
generated
|
|
@ -82,6 +82,15 @@ 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"
|
||||||
|
|
@ -176,12 +185,41 @@ 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"
|
||||||
|
|
@ -248,6 +286,16 @@ 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"
|
||||||
|
|
@ -533,6 +581,7 @@ dependencies = [
|
||||||
"ringbuf",
|
"ringbuf",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -730,6 +779,17 @@ 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"
|
||||||
|
|
@ -880,6 +940,12 @@ 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"
|
||||||
|
|
@ -892,6 +958,12 @@ 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"
|
||||||
|
|
|
||||||
31
Cargo.toml
31
Cargo.toml
|
|
@ -1,5 +1,4 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
|
||||||
members = [
|
members = [
|
||||||
"crates/*",
|
"crates/*",
|
||||||
"scanners/scanner-system",
|
"scanners/scanner-system",
|
||||||
|
|
@ -7,33 +6,35 @@ members = [
|
||||||
"scanners/scanner-power",
|
"scanners/scanner-power",
|
||||||
"scanners/scanner-proc",
|
"scanners/scanner-proc",
|
||||||
]
|
]
|
||||||
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
authors = [ "NotAShelf <raf@notashelf.dev>" ]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
authors = ["NotAShelf <raf@notashelf.dev>"]
|
version = "0.1.0"
|
||||||
|
|
||||||
[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" }
|
||||||
|
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
chrono = { features = [ "serde" ], version = "0.4.43" }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
clap = { features = [ "derive" ], version = "4.5.59" }
|
||||||
serde_json = "1.0.149"
|
dirs = "6.0.0"
|
||||||
toml = "1.0.0"
|
|
||||||
libloading = "0.9.0"
|
|
||||||
chrono = { version = "0.4.43", features = ["serde"] }
|
|
||||||
sysinfo = "0.38.2"
|
|
||||||
log = "0.4.29"
|
|
||||||
env_logger = "0.11.9"
|
env_logger = "0.11.9"
|
||||||
thiserror = "2.0.18"
|
libloading = "0.9.0"
|
||||||
|
log = "0.4.29"
|
||||||
parking_lot = "0.12.5"
|
parking_lot = "0.12.5"
|
||||||
ringbuf = "0.4.8"
|
ringbuf = "0.4.8"
|
||||||
dirs = "6.0.0"
|
serde = { features = [ "derive" ], version = "1.0.228" }
|
||||||
clap = { version = "4.5.59", features = ["derive"] }
|
serde_json = "1.0.149"
|
||||||
|
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
|
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,8 @@ 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 }
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
|
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 = [
|
dirs = [ "~/.local/share/pscand/scanners" ]
|
||||||
"~/.local/share/pscand/scanners",
|
|
||||||
]
|
|
||||||
|
|
||||||
[scanners.system]
|
[scanners.system]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,20 @@ 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
|
||||||
tokio.workspace = true
|
ringbuf.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
toml.workspace = true
|
sha2.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
|
sysinfo.workspace = true
|
||||||
clap.workspace = true
|
thiserror.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
toml.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,58 @@
|
||||||
#![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::logging::{LogLevel, RingBufferLogger};
|
use pscand_core::{
|
||||||
use pscand_core::scanner::Scanner;
|
logging::{
|
||||||
use pscand_core::Config as CoreConfig;
|
LogLevel,
|
||||||
use std::path::PathBuf;
|
RingBufferLogger,
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
},
|
||||||
use std::sync::Arc;
|
scanner::Scanner,
|
||||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
Config as CoreConfig,
|
||||||
use tokio::sync::RwLock;
|
};
|
||||||
use tokio::time::interval;
|
use sha2::{
|
||||||
|
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",
|
||||||
|
|
@ -27,13 +66,44 @@ enum Args {
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
struct RunArgs {
|
struct RunArgs {
|
||||||
#[arg(short, long, default_value = "/etc/pscand/pscand.toml")]
|
#[arg(short, long, default_value = "~/.config/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>>>,
|
||||||
|
|
@ -115,9 +185,14 @@ impl DaemonState {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write_status(&self, logger: &RingBufferLogger) -> std::io::Result<()> {
|
async fn write_status(
|
||||||
|
&self,
|
||||||
|
logger: &RingBufferLogger,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
let uptime = SystemTime::now()
|
let uptime = SystemTime::now()
|
||||||
.duration_since(self.start_time.try_read().map(|t| *t).unwrap_or(UNIX_EPOCH))
|
.duration_since(
|
||||||
|
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);
|
||||||
|
|
@ -158,10 +233,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(())
|
||||||
|
|
@ -169,21 +244,44 @@ 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::Env::default().default_filter_or("debug")).init();
|
env_logger::Builder::from_env(
|
||||||
|
env_logger::Env::default().default_filter_or("debug"),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
} else {
|
} else {
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
env_logger::Builder::from_env(
|
||||||
|
env_logger::Env::default().default_filter_or("info"),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Starting pscand daemon");
|
log::info!("Starting pscand daemon");
|
||||||
|
|
||||||
let config = if args.config.exists() {
|
// Expand ~ in config path
|
||||||
CoreConfig::load(&args.config)?
|
let config_path = expand_path(&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, using defaults");
|
log::warn!("Config file not found at {:?}", config_path);
|
||||||
|
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)?;
|
std::fs::create_dir_all(&config.log_dir).map_err(|e| {
|
||||||
|
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(
|
||||||
|
|
@ -256,8 +354,23 @@ 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::warn!("No scanners loaded!");
|
log::error!("No scanners loaded!");
|
||||||
logger.log(LogLevel::Warn, "daemon", "no_scanners", "{}".to_string());
|
log::error!("Please ensure:");
|
||||||
|
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(
|
||||||
|
|
@ -308,7 +421,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,
|
||||||
|
|
@ -316,16 +429,22 @@ 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;
|
||||||
state.update_heartbeat().await.ok();
|
if let Err(e) = state.update_heartbeat().await {
|
||||||
|
log::warn!("Failed to update heartbeat during shutdown: {}", e);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let scan_start = Instant::now();
|
let scan_start = Instant::now();
|
||||||
let scanner_guard = scanner.read().await;
|
let collect_result = {
|
||||||
match scanner_guard.collect() {
|
let guard = scanner.read().await;
|
||||||
|
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(
|
||||||
|
|
@ -339,13 +458,24 @@ 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(LogLevel::Error, &name, "collection_error", e.to_string());
|
logger.log(
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -360,12 +490,15 @@ 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;
|
||||||
daemon_state_hb.update_heartbeat().await.ok();
|
if let Err(e) = daemon_state_hb.update_heartbeat().await {
|
||||||
|
log::warn!("Failed to update heartbeat: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let sigint = tokio::signal::ctrl_c();
|
let sigint = tokio::signal::ctrl_c();
|
||||||
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
|
let mut sigterm =
|
||||||
|
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = sigint => {
|
_ = sigint => {
|
||||||
|
|
@ -423,8 +556,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,6 +575,18 @@ 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) => {
|
||||||
|
|
@ -453,7 +602,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()) {
|
||||||
|
|
@ -464,7 +613,7 @@ async fn load_scanners(
|
||||||
path.display()
|
path.display()
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
let name = scanner.name().to_string();
|
let name = scanner.name().to_string();
|
||||||
|
|
||||||
|
|
@ -506,7 +655,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(
|
||||||
|
|
@ -515,10 +664,18 @@ 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)
|
||||||
|
|
@ -530,7 +687,12 @@ 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!("\nDynamic scanners are loaded from $PSCAND_SCANNER_DIRS (colon-separated)");
|
println!(
|
||||||
println!(" Default fallback: ~/.local/share/pscand/scanners/ or ~/.config/pscand/scanners/");
|
"\nDynamic scanners are loaded from $PSCAND_SCANNER_DIRS (colon-separated)"
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Default fallback: ~/.local/share/pscand/scanners/ or \
|
||||||
|
~/.config/pscand/scanners/"
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@ license.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio.workspace = true
|
chrono.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
|
||||||
toml.workspace = true
|
|
||||||
chrono.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
thiserror.workspace = true
|
|
||||||
parking_lot.workspace = true
|
|
||||||
dirs.workspace = true
|
|
||||||
sysinfo.workspace = true
|
sysinfo.workspace = true
|
||||||
ringbuf.workspace = true
|
thiserror.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
toml.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "pscand_core"
|
name = "pscand_core"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use std::{
|
||||||
use std::collections::HashMap;
|
collections::HashMap,
|
||||||
use std::path::{Path, PathBuf};
|
path::{
|
||||||
|
Path,
|
||||||
|
PathBuf,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{
|
||||||
|
Deserialize,
|
||||||
|
Serialize,
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
use std::fs;
|
collections::HashMap,
|
||||||
use std::path::PathBuf;
|
fs,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct PowerHelper;
|
pub struct PowerHelper;
|
||||||
|
|
||||||
|
|
@ -103,7 +105,8 @@ impl PowerHelper {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn power_supplies() -> std::io::Result<HashMap<String, HashMap<String, String>>> {
|
pub fn power_supplies(
|
||||||
|
) -> 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");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
use std::fs;
|
collections::HashMap,
|
||||||
|
fs,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ProcessHelper;
|
pub struct ProcessHelper;
|
||||||
|
|
||||||
|
|
@ -25,14 +27,16 @@ impl ProcessHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
let pid: u32 = match path.file_name() {
|
let pid: u32 = match path.file_name() {
|
||||||
Some(name) => match name.to_str() {
|
Some(name) => {
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -88,10 +92,12 @@ impl ProcessHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zombie_processes() -> std::io::Result<Vec<ProcessInfo>> {
|
pub fn zombie_processes() -> std::io::Result<Vec<ProcessInfo>> {
|
||||||
Ok(Self::list_processes()?
|
Ok(
|
||||||
|
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>> {
|
||||||
|
|
@ -109,14 +115,16 @@ 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(count: usize) -> std::io::Result<Vec<ProcessInfo>> {
|
pub fn top_memory_processes(
|
||||||
|
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);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
use std::fs;
|
collections::HashMap,
|
||||||
|
fs,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ResourceHelper;
|
pub struct ResourceHelper;
|
||||||
|
|
||||||
|
|
@ -72,7 +74,8 @@ 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();
|
||||||
|
|
||||||
|
|
@ -81,7 +84,8 @@ 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.insert("reads_completed".to_string(), parts[3].parse().unwrap_or(0));
|
stats
|
||||||
|
.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));
|
||||||
|
|
@ -89,8 +93,10 @@ impl ResourceHelper {
|
||||||
"writes_completed".to_string(),
|
"writes_completed".to_string(),
|
||||||
parts[7].parse().unwrap_or(0),
|
parts[7].parse().unwrap_or(0),
|
||||||
);
|
);
|
||||||
stats.insert("writes_merged".to_string(), parts[8].parse().unwrap_or(0));
|
stats
|
||||||
stats.insert("sectors_written".to_string(), parts[9].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("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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
use std::fs;
|
collections::HashMap,
|
||||||
use std::path::{Path, PathBuf};
|
fs,
|
||||||
|
path::{
|
||||||
|
Path,
|
||||||
|
PathBuf,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SensorHelper;
|
pub struct SensorHelper;
|
||||||
|
|
||||||
|
|
@ -21,7 +26,10 @@ impl SensorHelper {
|
||||||
Ok(hwmons)
|
Ok(hwmons)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_hwmon_sensor(hwmon_path: &Path, sensor: &str) -> std::io::Result<Option<f64>> {
|
pub fn read_hwmon_sensor(
|
||||||
|
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)?;
|
||||||
|
|
@ -31,7 +39,9 @@ impl SensorHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hwmon_info(hwmon_path: &Path) -> std::io::Result<HashMap<String, String>> {
|
pub fn hwmon_info(
|
||||||
|
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");
|
||||||
|
|
@ -53,7 +63,10 @@ impl SensorHelper {
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
info.insert(format!("temp_{}_celsius", id), format!("{}", t / 1000.0));
|
info.insert(
|
||||||
|
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") {
|
||||||
|
|
@ -73,7 +86,8 @@ impl SensorHelper {
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
info.insert(format!("voltage_{}_mv", id), format!("{}", v / 1000.0));
|
info
|
||||||
|
.insert(format!("voltage_{}_mv", id), format!("{}", v / 1000.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +96,8 @@ impl SensorHelper {
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_sensors() -> std::io::Result<HashMap<String, HashMap<String, String>>> {
|
pub fn all_sensors(
|
||||||
|
) -> 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()? {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::fs;
|
use std::{
|
||||||
use std::time::Duration;
|
fs,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SystemHelper;
|
pub struct SystemHelper;
|
||||||
|
|
||||||
|
|
@ -14,7 +16,8 @@ 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").map(|s| s.trim().to_string())
|
fs::read_to_string("/proc/sys/kernel/random/boot_id")
|
||||||
|
.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)> {
|
||||||
|
|
@ -36,10 +39,12 @@ impl SystemHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hostname() -> std::io::Result<String> {
|
pub fn hostname() -> std::io::Result<String> {
|
||||||
fs::read_to_string("/proc/sys/kernel/hostname").map(|s| s.trim().to_string())
|
fs::read_to_string("/proc/sys/kernel/hostname")
|
||||||
|
.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").map(|s| s.trim().to_string())
|
fs::read_to_string("/proc/sys/kernel/osrelease")
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,17 @@ pub mod logging;
|
||||||
pub mod scanner;
|
pub mod scanner;
|
||||||
|
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use logging::{DaemonLogEntry, LogLevel, RingBufferLogger};
|
pub use logging::{
|
||||||
|
DaemonLogEntry,
|
||||||
|
LogLevel,
|
||||||
|
RingBufferLogger,
|
||||||
|
};
|
||||||
pub use scanner::{
|
pub use scanner::{
|
||||||
get_scanner, register_scanner, MetricValue, Scanner, ScannerCreatorFfi, ScannerError,
|
get_scanner,
|
||||||
|
register_scanner,
|
||||||
|
MetricValue,
|
||||||
|
Scanner,
|
||||||
|
ScannerCreatorFfi,
|
||||||
|
ScannerError,
|
||||||
};
|
};
|
||||||
pub type Result<T> = std::result::Result<T, ScannerError>;
|
pub type Result<T> = std::result::Result<T, ScannerError>;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,42 @@
|
||||||
use chrono::{DateTime, Utc};
|
use std::{
|
||||||
|
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::{CachingCons, CachingProd},
|
wrap::caching::{
|
||||||
|
CachingCons,
|
||||||
|
CachingProd,
|
||||||
|
},
|
||||||
SharedRb,
|
SharedRb,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{
|
||||||
use std::collections::HashMap;
|
Deserialize,
|
||||||
use std::fs::{self, OpenOptions};
|
Serialize,
|
||||||
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;
|
||||||
|
|
||||||
|
|
@ -70,7 +93,11 @@ pub struct DaemonLogEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DaemonLogEntry {
|
impl DaemonLogEntry {
|
||||||
pub fn new(source: impl Into<String>, event: impl Into<String>, message: String) -> Self {
|
pub fn new(
|
||||||
|
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,
|
||||||
|
|
@ -91,7 +118,10 @@ impl DaemonLogEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogEntry {
|
impl LogEntry {
|
||||||
pub fn new(scanner: impl Into<String>, metrics: HashMap<String, MetricValue>) -> Self {
|
pub fn new(
|
||||||
|
scanner: impl Into<String>,
|
||||||
|
metrics: HashMap<String, MetricValue>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
timestamp: Utc::now(),
|
timestamp: Utc::now(),
|
||||||
scanner: scanner.into(),
|
scanner: scanner.into(),
|
||||||
|
|
@ -124,7 +154,8 @@ impl LogEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> String {
|
pub fn to_json(&self) -> String {
|
||||||
serde_json::to_string(self).unwrap_or_else(|e| format!("{{\"error\":\"{}\"}}", e))
|
serde_json::to_string(self)
|
||||||
|
.unwrap_or_else(|e| format!("{{\"error\":\"{}\"}}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_journal(&self) -> String {
|
pub fn to_journal(&self) -> String {
|
||||||
|
|
@ -211,7 +242,13 @@ impl RingBufferLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log(&self, level: LogLevel, source: &str, event: &str, message: String) {
|
pub fn log(
|
||||||
|
&self,
|
||||||
|
level: LogLevel,
|
||||||
|
source: &str,
|
||||||
|
event: &str,
|
||||||
|
message: String,
|
||||||
|
) {
|
||||||
let entry = DaemonLogEntry {
|
let entry = DaemonLogEntry {
|
||||||
timestamp: Utc::now(),
|
timestamp: Utc::now(),
|
||||||
level,
|
level,
|
||||||
|
|
@ -256,19 +293,23 @@ 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={}\n",
|
"PRIORITY={}\nSYSLOG_IDENTIFIER=pscand\nPSCAND_SCANNER={}\nMESSAGE={}\\
|
||||||
|
n",
|
||||||
priority, scanner, msg
|
priority, scanner, msg
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Ok(sock) = UnixDatagram::unbound() {
|
if let Ok(sock) = UnixDatagram::unbound() {
|
||||||
let _ = sock.send_to(journal_msg.as_bytes(), "/run/systemd/journal/socket");
|
let _ =
|
||||||
|
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) = OpenOptions::new().create(true).append(true).open(path) {
|
if let Ok(mut file) =
|
||||||
|
OpenOptions::new().create(true).append(true).open(path)
|
||||||
|
{
|
||||||
let _ = writeln!(file, "{}", entry.to_json());
|
let _ = writeln!(file, "{}", entry.to_json());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -292,19 +333,23 @@ 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={}\nPSCAND_EVENT={}\nMESSAGE={}\n",
|
"PRIORITY={}\nSYSLOG_IDENTIFIER=pscand\nPSCAND_SOURCE={}\\
|
||||||
|
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 _ = sock.send_to(journal_msg.as_bytes(), "/run/systemd/journal/socket");
|
let _ =
|
||||||
|
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) = OpenOptions::new().create(true).append(true).open(path) {
|
if let Ok(mut file) =
|
||||||
|
OpenOptions::new().create(true).append(true).open(path)
|
||||||
|
{
|
||||||
let _ = writeln!(file, "{}", entry.to_json());
|
let _ = writeln!(file, "{}", entry.to_json());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -384,10 +429,16 @@ impl RuntimeMonitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn record_collection(&self, scanner: &str, time_ms: u64, error: Option<&str>) {
|
pub fn record_collection(
|
||||||
|
&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.collection_time_sum
|
self
|
||||||
|
.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();
|
||||||
|
|
@ -463,16 +514,13 @@ impl RuntimeMonitor {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
|
||||||
scanner_stats.insert(
|
scanner_stats.insert(name.clone(), ScannerStats {
|
||||||
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 {
|
||||||
|
|
@ -543,8 +591,12 @@ impl CrashDetector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_interval(state_dir: PathBuf, heartbeat_interval_secs: u64) -> Self {
|
pub fn new_with_interval(
|
||||||
let heartbeat = Heartbeat::new(state_dir.join("heartbeat"), heartbeat_interval_secs);
|
state_dir: PathBuf,
|
||||||
|
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,
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,35 @@
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
use std::os::raw::c_void;
|
collections::HashMap,
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
os::raw::c_void,
|
||||||
use std::sync::LazyLock;
|
sync::{
|
||||||
use std::sync::Mutex;
|
atomic::{
|
||||||
use std::time::Duration;
|
AtomicUsize,
|
||||||
|
Ordering,
|
||||||
|
},
|
||||||
|
LazyLock,
|
||||||
|
Mutex,
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{
|
||||||
|
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 = Box<dyn Fn() -> ScannerCollectionResult + Send + Sync>;
|
pub type ScannerCollectFn =
|
||||||
pub type ScannerInitFnMut = Mutex<Box<dyn FnMut(&toml::Value) -> Result<()> + Send>>;
|
Box<dyn Fn() -> ScannerCollectionResult + Send + Sync>;
|
||||||
|
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 "C" fn() -> *mut c_void;
|
pub type ScannerCreatorFfi = unsafe extern fn() -> *mut c_void;
|
||||||
pub type ScannerInitFn = unsafe extern "C" fn() -> *mut c_void;
|
pub type ScannerInitFn = unsafe extern fn() -> *mut c_void;
|
||||||
pub type ScannerDropFn = unsafe extern "C" fn(*mut c_void);
|
pub type ScannerDropFn = unsafe extern fn(*mut c_void);
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ScannerError {
|
pub enum ScannerError {
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,4 @@ proc-macro = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "2", features = ["full"] }
|
syn = { features = [ "full" ], version = "2" }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse_macro_input, ItemFn};
|
use syn::{
|
||||||
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -143,17 +143,28 @@ pscand list
|
||||||
Configuration is stored in `/etc/pscand/pscand.toml`:
|
Configuration is stored in `/etc/pscand/pscand.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[daemon]
|
# Scanner plugin directories
|
||||||
log_level = "info"
|
scanner_dirs = ["~/.local/share/pscand/scanners"]
|
||||||
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 = 5000 # milliseconds
|
interval_secs = 5
|
||||||
|
|
||||||
[scanners.sensor]
|
[scanners.sensor]
|
||||||
enabled = true
|
enabled = true
|
||||||
interval = 10000
|
interval_secs = 10
|
||||||
|
|
||||||
|
[scanners.power]
|
||||||
|
enabled = true
|
||||||
|
interval_secs = 30
|
||||||
|
|
||||||
|
[scanners.proc]
|
||||||
|
enabled = true
|
||||||
|
interval_secs = 5
|
||||||
```
|
```
|
||||||
|
|
||||||
### Scanner Directories
|
### Scanner Directories
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,36 @@ pscand-core = { workspace = true }
|
||||||
## Implementing the `Scanner` trait
|
## Implementing the `Scanner` trait
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use pscand_core::scanner::Scanner;
|
use std::time::Duration;
|
||||||
|
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) -> &str {
|
fn name(&self) -> &'static str {
|
||||||
"custom"
|
"custom"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect(&self) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
fn interval(&self) -> Duration {
|
||||||
// Collect your metrics
|
Duration::from_secs(5)
|
||||||
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ mkShell {
|
||||||
cargo
|
cargo
|
||||||
|
|
||||||
# Tools
|
# Tools
|
||||||
rustfmt
|
(rustfmt.override {asNightly = true;})
|
||||||
clippy
|
clippy
|
||||||
cargo
|
cargo
|
||||||
taplo
|
taplo
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
use pscand_core::helpers::PowerHelper;
|
use std::{
|
||||||
use pscand_core::scanner::{MetricValue, Scanner};
|
collections::HashMap,
|
||||||
use std::collections::HashMap;
|
time::Duration,
|
||||||
use std::time::Duration;
|
};
|
||||||
|
|
||||||
|
use pscand_core::{
|
||||||
|
helpers::PowerHelper,
|
||||||
|
scanner::{
|
||||||
|
MetricValue,
|
||||||
|
Scanner,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
struct PowerScanner;
|
struct PowerScanner;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void {
|
pub extern 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
use pscand_core::helpers::ProcessHelper;
|
use std::{
|
||||||
use pscand_core::scanner::{MetricValue, Scanner};
|
collections::HashMap,
|
||||||
use std::collections::HashMap;
|
time::Duration,
|
||||||
use std::time::Duration;
|
};
|
||||||
|
|
||||||
|
use pscand_core::{
|
||||||
|
helpers::ProcessHelper,
|
||||||
|
scanner::{
|
||||||
|
MetricValue,
|
||||||
|
Scanner,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
struct ProcScanner;
|
struct ProcScanner;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void {
|
pub extern 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,21 @@
|
||||||
use pscand_core::helpers::SensorHelper;
|
use std::{
|
||||||
use pscand_core::scanner::{MetricValue, Scanner};
|
collections::HashMap,
|
||||||
use pscand_core::Result;
|
time::Duration,
|
||||||
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 "C" fn pscand_scanner() -> *mut std::os::raw::c_void {
|
pub extern 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +49,8 @@ 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.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
|
metrics
|
||||||
|
.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(
|
||||||
|
|
@ -53,17 +62,21 @@ 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.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
|
metrics
|
||||||
|
.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
|
||||||
fan_count += 1;
|
fan_count += 1;
|
||||||
if fan_count <= 2 {
|
if fan_count <= 2 {
|
||||||
metrics
|
metrics.insert(
|
||||||
.insert(format!("fan_{}", fan_count), MetricValue::from_f64(v));
|
format!("fan_{}", fan_count),
|
||||||
|
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.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
|
metrics
|
||||||
|
.insert(format!("{}_{}", hwmon, key), MetricValue::from_f64(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
use pscand_core::helpers::{ResourceHelper, SystemHelper};
|
use std::{
|
||||||
use pscand_core::scanner::{MetricValue, Scanner};
|
collections::HashMap,
|
||||||
use pscand_core::Result;
|
time::Duration,
|
||||||
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 {
|
||||||
|
|
@ -10,7 +21,7 @@ struct SystemScanner {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn pscand_scanner() -> *mut std::os::raw::c_void {
|
pub extern 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +56,8 @@ 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.insert("cpu_percent".to_string(), MetricValue::from_f64(*total));
|
metrics
|
||||||
|
.insert("cpu_percent".to_string(), MetricValue::from_f64(*total));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +74,9 @@ impl Scanner for SystemScanner {
|
||||||
MetricValue::Integer(*available as i64),
|
MetricValue::Integer(*available as i64),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let (Some(used), Some(total)) = (mem.get("MemAvailable"), mem.get("MemTotal")) {
|
if let (Some(used), Some(total)) =
|
||||||
|
(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(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue