Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
413 changes: 79 additions & 334 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

2,138 changes: 1,254 additions & 884 deletions Cargo.lock

Large diffs are not rendered by default.

69 changes: 41 additions & 28 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
edition = "2021"
name = "stat_client"
version = "1.8.1"
version = "1.8.2"

rust-version = "1.76"

Expand All @@ -17,36 +17,41 @@ repository = "https://github.com/zdz/ServerStatus-Rust"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[build-dependencies]
chrono = "0.4"
chrono = "0.4.43"
tonic-prost-build = "0.14.3"


[dependencies]
anyhow = "1"
bytes = {version = "1", features = ["serde"]}
chrono = "0.4"
clap = {version = "4.5", features = ["derive", "unicode", "env"]}
fastrand = "2.0.1"
hyper = {version = "1.2", features = ["full"]}
lazy_static = "1.4"
log = "0.4"
md5 = "0.7.0"
once_cell = "1"
pretty_env_logger = "0.5"
prettytable-rs = "^0.10"
prost = "0.12"
regex = "1.10"
reqwest = {version = "0.11", features = ["json", "rustls-tls", "brotli", "gzip", "deflate", "stream", "socks"], default-features = false}
rustls = { version = "0.23.5", default-features = false }
rustls-pemfile = { version = "2" }
serde = {version = "1.0", default-features = false, features = ["derive", "alloc"]}
serde_json = {version = "1.0", default-features = false, features = ["alloc"]}
anyhow = "1.0.100"
bytes = {version = "1.11.0", features = ["serde"]}
chrono = "0.4.43"
clap = {version = "4.5.55", features = ["derive", "unicode", "env"]}
fastrand = "2.3.0"
hyper = {version = "1.8.1", features = ["full"]}
lazy_static = "1.5.0"
log = "0.4.29"
md5 = "0.8.0"
once_cell = "1.21.3"
pretty_env_logger = "0.5.0"
prettytable-rs = "^0.10.0"
prost = "0.14.3"
regex = "1.12.2"
reqwest = {version = "0.13.1", features = ["json", "rustls", "brotli", "gzip", "deflate", "stream", "socks"], default-features = false}
rustls = { version = "0.23.36", default-features = false }
rustls-pemfile = { version = "2.2.0" }
serde = {version = "1.0.228", default-features = false, features = ["derive", "alloc"]}
serde_json = {version = "1.0.149", default-features = false, features = ["alloc"]}
stat_common = {path = "../common", version = "1.1.4"}
sysinfo = "0.30.7"
tokio = {version = "1", features = ["full"]}
tokio-rustls = { version = "0.26" }
tonic = {version = "0.11", features = ["tls", "tls-webpki-roots", "gzip"]}
tower = { version = "0.4" }
webpki-roots = "0.26"
url = "2.5.0"
sysinfo = "0.38.0"
tokio = {version = "1.49.0", features = ["full"]}
tokio-rustls = { version = "0.26.4" }
tonic = {version = "0.14.3", features = ["gzip","codegen", "transport","tls-ring"]}
tower = { version = "0.5.3" }
webpki-roots = "1.0.5"
url = "2.5.8"
tonic-build = "0.14.3"
rustls-native-certs="0.8.3"
tonic-prost="0.14.3"

[features]
default = ["sysinfo"]
Expand All @@ -72,3 +77,11 @@ assets = [
{ source = "LICENSE", dest = "/usr/share/doc/ServerStatus/LICENSE", doc = true, mode = "0644" },
{ source = "../systemd/stat_client.service", dest = "/opt/ServerStatus/stat_client.service", config = true, mode = "0644" },
]


[profile.release]
opt-level = "z" # 优化文件大小 (s 是优化速度和体积,z 是极致体积)
lto = true # 开启链接时优化 (跨模块删除无用代码)
codegen-units = 1 # 限制并行编译单元,增加优化空间
panic = "abort" # 发生 panic 时直接退出,不包含回溯栈信息 (减小体积明显)
strip = true # 自动从生成的二进制文件中剥离符号信息
48 changes: 25 additions & 23 deletions client/src/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
use std::str::FromStr;
use std::thread;
use std::time::Duration;
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};

use tonic::{metadata::MetadataValue, Request};
use tower::timeout::Timeout;
use url::Url;

use stat_common::server_status::server_status_client::ServerStatusClient;
use stat_common::server_status::StatRequest;

use tonic::transport::Channel;
use tonic::transport::{ClientTlsConfig,Identity,Certificate};
use crate::sample_all;
use crate::Args;


pub async fn report(args: &Args, stat_base: &mut StatRequest) -> anyhow::Result<()> {
let auth_user: String;
let ssr_auth: &[u8];
Expand All @@ -27,38 +29,39 @@ pub async fn report(args: &Args, stat_base: &mut StatRequest) -> anyhow::Result<

let addr = args.addr.replace("grpcs://", "https://");
let channel: Channel;
// mTLS
if args.mtls {
let u = Url::parse(addr.as_str())?;
// === mTLS 模式 ===
let u = Url::parse(&addr)?;
let domain_name = u.host_str().ok_or_else(|| anyhow::anyhow!("invalid URL: missing host"))?;

let tls_dir = std::path::PathBuf::from_str(&args.tls_dir)?;
let ca = std::fs::read_to_string(tls_dir.join("ca.pem"))?;
let client_cert = std::fs::read_to_string(tls_dir.join("client.pem"))?;
let client_key = std::fs::read_to_string(tls_dir.join("client.key"))?;
let client_identity = Identity::from_pem(client_cert, client_key);
let ca = Certificate::from_pem(ca);
let ca_pem = std::fs::read(tls_dir.join("ca.pem"))?;
let client_cert_pem = std::fs::read(tls_dir.join("client.pem"))?;
let client_key_pem = std::fs::read(tls_dir.join("client.key"))?;
let client_identity = Identity::from_pem(client_cert_pem, client_key_pem);
let ca = Certificate::from_pem(ca_pem);

let tls = ClientTlsConfig::new()
.domain_name(u.host_str().expect("invalid domain"))
let tls_config = ClientTlsConfig::new()
.domain_name(domain_name)
.ca_certificate(ca)
.identity(client_identity);
channel = Channel::from_shared(addr)?.tls_config(tls)?.connect().await?;
} else {

channel = Channel::from_shared(addr)?
.tls_config(tls_config)?
.connect().await?;
} else if addr.starts_with("https://") {
// TLS
if addr.starts_with("https://") {
let tls = ClientTlsConfig::new();
channel = Channel::from_shared(addr)?.tls_config(tls)?.connect().await?;
} else {
channel = Channel::from_shared(addr)?.connect().await?;
}
}
let tls_config = ClientTlsConfig::new();
channel = Channel::from_shared(addr)?.tls_config(tls_config)?.connect().await?;
} else {
channel = Channel::from_shared(addr)?.connect().await?;
};

let timeout_channel = Timeout::new(channel, Duration::from_millis(3000));
let grpc_client = ServerStatusClient::with_interceptor(timeout_channel, move |mut req: Request<()>| {
req.metadata_mut().insert("authorization", token.clone());
req.metadata_mut()
.insert("ssr-auth", MetadataValue::try_from(ssr_auth).unwrap());

Ok(req)
});

Expand All @@ -68,7 +71,6 @@ pub async fn report(args: &Args, stat_base: &mut StatRequest) -> anyhow::Result<

tokio::spawn(async move {
let request = tonic::Request::new(stat_rt);

match client.report(request).await {
Ok(resp) => {
info!("grpc report resp => {:?}", resp);
Expand All @@ -81,4 +83,4 @@ pub async fn report(args: &Args, stat_base: &mut StatRequest) -> anyhow::Result<

thread::sleep(Duration::from_secs(args.report_interval));
}
}
}
79 changes: 48 additions & 31 deletions client/src/sys_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::thread;
use std::time::Duration;
use sysinfo::CpuRefreshKind;
use sysinfo::{Components, Disks, MemoryRefreshKind, Networks, RefreshKind, System};

use std::env::consts::ARCH;
use crate::status;
use crate::vnstat;
use crate::Args;
Expand Down Expand Up @@ -45,13 +45,13 @@ lazy_static! {
pub static ref G_CPU_PERCENT: Arc<Mutex<f64>> = Arc::new(Default::default());
}
pub fn start_cpu_percent_collect_t() {
let mut sys = System::new_with_specifics(RefreshKind::new().with_cpu(CpuRefreshKind::new().with_cpu_usage()));
let mut sys = System::new_with_specifics(RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing().with_cpu_usage()));
thread::spawn(move || loop {
sys.refresh_cpu();
sys.refresh_cpu_all();

let global_cpu = sys.global_cpu_info();
let global_cpu = sys.global_cpu_usage();
if let Ok(mut cpu_percent) = G_CPU_PERCENT.lock() {
*cpu_percent = global_cpu.cpu_usage().round() as f64;
*cpu_percent = global_cpu.round() as f64;
}

thread::sleep(Duration::from_millis(SAMPLE_PERIOD));
Expand Down Expand Up @@ -86,7 +86,7 @@ pub fn start_net_speed_collect_t(args: &Args) {
t.net_tx = net_tx;
}

networks.refresh_list();
networks.refresh(true);
thread::sleep(Duration::from_millis(SAMPLE_PERIOD));
});
}
Expand All @@ -105,7 +105,7 @@ pub fn sample(args: &Args, stat: &mut StatRequest) {
unit = 1000;
}

let mut sys = System::new_with_specifics(RefreshKind::new().with_memory(MemoryRefreshKind::everything()));
let mut sys = System::new_with_specifics(RefreshKind::nothing().with_memory(MemoryRefreshKind::everything()));

// uptime
stat.uptime = System::uptime();
Expand All @@ -130,40 +130,57 @@ pub fn sample(args: &Args, stat: &mut StatRequest) {

// hdd KB -> KiB
let (mut hdd_total, mut hdd_avail) = (0_u64, 0_u64);
let (mut hdd_total_bytes, mut hdd_avail_bytes) = (0_u64, 0_u64);

#[cfg(not(target_os = "windows"))]
let mut uniq_disk_set = HashSet::new();

let disks = Disks::new_with_refreshed_list();
let mut seen_phys_disks: HashSet<String> = HashSet::new();

stat.disks.clear();

for disk in &disks {
let di = DiskInfo {
name: disk.name().to_str().unwrap().to_string(),
mount_point: disk.mount_point().to_str().unwrap().to_string(),
file_system: disk.file_system().to_str().unwrap().to_string(),
total: disk.total_space(),
used: disk.total_space() - disk.available_space(),
free: disk.available_space(),
};

let fs = di.file_system.to_lowercase();
if G_EXPECT_FS.iter().any(|&k| fs.contains(k)) {
let mount_point = disk.mount_point().to_string_lossy().to_string();
let fs_type = disk.file_system().to_string_lossy().to_lowercase();
let total_space = disk.total_space();

if G_EXPECT_FS.iter().any(|&k| fs_type.contains(k)) {
// 生成唯一标识
let disk_id = format!("{}-{}", total_space, fs_type);
let mut is_new_phys_disk = true;

#[cfg(not(target_os = "windows"))]
{
if uniq_disk_set.contains(disk.name()) {
continue;
// Linux 下检查是否是重复的物理分区
if seen_phys_disks.contains(&disk_id) {
is_new_phys_disk = false;
} else {
seen_phys_disks.insert(disk_id);
}
uniq_disk_set.insert(disk.name());
}

// 如果是新的物理磁盘(Windows 默认都是新的,Linux 经过上面去重判断)
if is_new_phys_disk {
hdd_total_bytes += total_space;
hdd_avail_bytes += disk.available_space();
}

hdd_total += disk.total_space();
hdd_avail += disk.available_space();
// 无论是否是重复物理磁盘,挂载点信息都要存入列表供前端展示
stat.disks.push(DiskInfo {
name: disk.name().to_string_lossy().to_string(),
mount_point,
file_system: fs_type,
total: total_space,
used: total_space - disk.available_space(),
free: disk.available_space(),
});
}

stat.disks.push(di);
}

stat.hdd_total = hdd_total / unit.pow(2);
stat.hdd_used = (hdd_total - hdd_avail) / unit.pow(2);
stat.hdd_total = hdd_total_bytes / 1024 / 1024;
stat.hdd_used = (hdd_total_bytes - hdd_avail_bytes) / 1024 / 1024;

// stat.hdd_total = hdd_total / unit.pow(2);
// stat.hdd_used = (hdd_total - hdd_avail) / unit.pow(2);

// t/u/p/d
let (t, u, p, d) = if args.disable_tupd {
Expand Down Expand Up @@ -233,7 +250,7 @@ pub fn collect_sys_info(args: &Args) -> SysInfo {
let mut info_pb = SysInfo::default();

let mut sys = System::new();
sys.refresh_cpu();
sys.refresh_cpu_all();

info_pb.name = args.user.to_owned();
info_pb.version = env!("CARGO_PKG_VERSION").to_string();
Expand Down Expand Up @@ -345,7 +362,7 @@ pub fn print_sysinfo() {
system_t.add_row(row!["Long OS version", System::long_os_version().unwrap_or_default()]);
system_t.add_row(row!["Distribution ID", System::distribution_id()]);
system_t.add_row(row!["Host name", System::host_name().unwrap_or_default()]);
system_t.add_row(row!["CPU arch", System::cpu_arch().unwrap_or_default()]);
system_t.add_row(row!["CPU arch", ARCH]);

let mut cpu_t = Table::new();
cpu_t.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
Expand Down
22 changes: 12 additions & 10 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
edition = "2021"
name = "stat_common"
version = "1.1.4"
version = "1.1.5"

authors = ["doge <doge.py@gmail.com>"]
categories = ["monitoring-tools"]
Expand All @@ -15,16 +15,18 @@ repository = "https://github.com/zdz/ServerStatus-Rust"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bytes = {version = "1", features = ["serde"]}
prost = "0.12"
serde = {version = "1.0", default-features = false, features = ["derive", "alloc"]}
tonic = {version = "0.11", features = ["tls"]}
tonic = {version = "0.14.3"}
prost = "0.14.3"
bytes = {version = "1.11.0", features = ["serde"]}
serde = {version = "1.0.228", default-features = false, features = ["derive", "alloc"]}
tonic-prost="0.14.3"

[target.'cfg(not(target_env = "msvc"))'.build-dependencies]
chrono = "0.4"
protobuf-src = "1"
tonic-build = "0.11"
chrono = "0.4.43"
protobuf-src = "2.1.1"
prost-build = "0.14.3"

[build-dependencies]
chrono = "0.4"
tonic-build = "0.11"
chrono = "0.4.43"
tonic-build = "0.14.3"
tonic-prost-build="0.14.3"
5 changes: 2 additions & 3 deletions common/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use chrono::Utc;
use std::process::Command;

fn commit_hash() -> Option<String> {
Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
Expand All @@ -25,8 +24,8 @@ fn main() {
#[cfg(not(target_env = "msvc"))]
std::env::set_var("PROTOC", protobuf_src::protoc());

tonic_build::configure()
tonic_prost_build::configure()
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
.compile(&["proto/server_status.proto"], &["proto"])
.compile_protos(&["proto/server_status.proto"], &["proto"])
.unwrap();
}
Loading