diff --git a/README.md b/README.md index dd37f32..539759e 100644 --- a/README.md +++ b/README.md @@ -59,38 +59,9 @@ and to collect performance data in a reliable, low-overhead, and elegant way. ## Config -Psh default config located `/etc/psh/config.toml`. - -```toml -[auth] -token = "" - -[component] -path = "path/to/component.wasm" -args = ["arg1", "arg2", "arg3"] - -[daemon] # when run as SysV daemon -pid_file = "/tmp/psh.pid" -stdout_file = "/tmp/psh.stdout" -stderr_file = "/tmp/psh.stderr" -working_directory = "/" - -[remote] -enable = true - -[remote.rpc] -endpoint = "http://rpc.optimatist.com" -duration = 1 -instance_id_file = "/etc/psh/instance.id" - -[remote.otlp] -endpoint = "http://otel-col.optimatist.com" -protocol = "Grpc" - -[remote.otlp.timeout] -secs = 3 -nanos = 0 -``` +The default config is located in `/etc/psh/config.toml`. + +See [config template](./doc/config.toml) ## Contribution Guide diff --git a/doc/config.toml b/doc/config.toml new file mode 100644 index 0000000..4ca76e3 --- /dev/null +++ b/doc/config.toml @@ -0,0 +1,24 @@ +[daemon] +pid_file = "/tmp/psh.pid" +stdout = "/tmp/psh.stdout" +stderr = "/tmp/psh.stderr" +workdir = "/" + +[daemon.wasm] +enable = false +path = "" +args = [] + +[remote] +token = "" + +[remote.rpc] +enable = false +addr = "https://rpc.optimatist.com" +# in seconds +heartbeat_interval = 1 +instance_id_file = "/etc/psh/instance.id" + +[remote.otlp] +enable = false +addr = "https://otel-col.optimatist.com" diff --git a/src/config.rs b/src/config.rs index 6ae6c0d..0e1e554 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,217 +12,71 @@ // You should have received a copy of the GNU Lesser General Public License along with Performance Savior Home (PSH). If not, // see . -use std::fs::{create_dir_all, File}; -use std::io::{Read, Write}; -use std::mem; -use std::path::Path; +use std::{fs, path::Path}; -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; +use anyhow::Result; +use serde::Deserialize; -use crate::daemon::DaemonConfig; -use crate::otlp::config::OtlpConfig; -use crate::services::config::RpcConfig; +const TEMPLATE: &str = include_str!("../doc/config.toml"); -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct PshConfig { - #[serde(rename = "auth")] - auth: AuthConfig, - #[serde(rename = "component")] - component_conf: ComponentConfig, - daemon: DaemonConfig, +#[derive(Deserialize)] +pub struct Config { + pub daemon: DaemonConfig, pub remote: RemoteConfig, } -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct RemoteConfig { - pub enable: bool, - pub rpc: RpcConfig, - #[serde(rename = "otlp")] - pub otlp_conf: OtlpConfig, +#[derive(Deserialize)] +pub struct DaemonConfig { + pub pid_file: String, + pub stdout: String, + pub stderr: String, + pub workdir: String, + pub wasm: DaemonWasmConfig, } -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct AuthConfig { - token: String, +#[derive(Clone, Deserialize)] +pub struct DaemonWasmConfig { + pub enable: bool, + pub path: String, + pub args: Vec, } -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct ComponentConfig { - path: String, - args: Vec, +#[derive(Deserialize)] +pub struct RemoteConfig { + pub token: String, + pub rpc: RpcConfig, + pub otlp: OtlpConfig, } -impl ComponentConfig { - #![allow(dead_code)] - pub fn new(path: String, args: Vec) -> Self { - Self { path, args } - } - - pub fn get_component_args(&mut self) -> Option> { - if self.path.is_empty() { - return None; - } - let mut args = Vec::with_capacity(1 + self.args.len()); - args.push(mem::take(&mut self.path)); - args.extend(mem::take(&mut self.args)); - - Some(args) - } +#[derive(Deserialize)] +pub struct RpcConfig { + pub enable: bool, + pub addr: String, + /// in seconds + pub heartbeat_interval: u64, + pub instance_id_file: String, } -impl PshConfig { - #[allow(dead_code)] - pub fn new( - component_conf: ComponentConfig, - otlp_conf: OtlpConfig, - daemon: DaemonConfig, - rpc: RpcConfig, - auth: AuthConfig, - enable_remote: bool, - ) -> Self { - Self { - component_conf, - daemon, - auth, - remote: RemoteConfig { - enable: enable_remote, - rpc, - otlp_conf, - }, - } - } - - pub fn read_config>(path: P) -> Result { - let path = path.as_ref(); - if !path.exists() { - Self::default().generate_config(path, false)?; - } - let mut config_str = String::new(); - let mut config_file = File::open(path).context("The config not exists.")?; - config_file.read_to_string(&mut config_str)?; - - let conf: Self = toml::from_str(&config_str)?; - Ok(conf) - } - - /// When overwrite set to true, it will overwrite the config file. - pub fn generate_config>(&self, path: P, overwrite: bool) -> Result<()> { - let path = path.as_ref(); - if !overwrite && path.exists() { - return Ok(()); - } - create_dir_all(path.parent().expect("no parent directory"))?; - - let s = toml::to_string(self)?; - - let mut f = File::options() - .create(true) - .truncate(true) - .write(true) - .open(path)?; - - f.write_all(s.as_bytes())?; - Ok(()) - } - - #[allow(dead_code)] - pub fn otlp_conf(&mut self) -> OtlpConfig { - mem::take(&mut self.remote.otlp_conf) - } - - pub fn get_component_args(&mut self) -> Option> { - self.component_conf.get_component_args() - } - - pub fn daemon(&self) -> &DaemonConfig { - &self.daemon - } - - #[allow(dead_code)] - pub fn rpc(&mut self) -> RpcConfig { - mem::take(&mut self.remote.rpc) - } - - pub fn take_token(&mut self) -> String { - mem::take(&mut self.auth.token) - } +#[derive(Deserialize)] +pub struct OtlpConfig { + pub enable: bool, + pub addr: String, } -#[cfg(test)] -mod tests { - use crate::config::{AuthConfig, ComponentConfig, PshConfig}; - use crate::daemon::DaemonConfig; - use crate::otlp::config::OtlpConfig; - use crate::services::config::RpcConfig; - - const CONFIG_STR: &str = r#"[auth] -token = "" - -[component] -path = "cpu.wasm" -args = ["1", "2", "3"] - -[daemon] -pid_file = "/tmp/psh.pid" -stdout_file = "/tmp/psh.stdout" -stderr_file = "/tmp/psh.stderr" -working_directory = "/" - -[remote] -enable = true - -[remote.rpc] -addr = "https://rpc.optimatist.com" -duration = 1 -instance_id_file = "/etc/psh/instance.id" - -[remote.otlp] -endpoint = "https://otel-col.optimatist.com" -protocol = "Grpc" - -[remote.otlp.timeout] -secs = 3 -nanos = 0 -"#; - - const TEST_CONF_PATH: &str = "./target/config.toml"; - - #[test] - fn conf_str_convert_work() { - let cf = PshConfig::new( - ComponentConfig::new( - "cpu.wasm".to_owned(), - vec!["1".to_owned(), "2".to_owned(), "3".to_owned()], - ), - OtlpConfig::default(), - DaemonConfig::default(), - RpcConfig::default(), - AuthConfig::default(), - true, - ); - let s = toml::to_string(&cf).unwrap(); - assert_eq!(s, CONFIG_STR); - - let str_to_cf: PshConfig = toml::from_str(CONFIG_STR).unwrap(); - assert_eq!(cf, str_to_cf); +pub fn read_or_gen

(path: P) -> Result +where + P: AsRef, +{ + let path = path.as_ref(); + if !path.exists() { + fs::write(path, TEMPLATE)?; } + let cfg = fs::read_to_string(path)?; + let cfg: Config = toml::from_str(&cfg)?; + Ok(cfg) +} - #[test] - fn generate_config_work() { - let cf = PshConfig::new( - ComponentConfig::new( - "cpu.wasm".to_owned(), - vec!["1".to_owned(), "2".to_owned(), "3".to_owned()], - ), - OtlpConfig::default(), - DaemonConfig::default(), - RpcConfig::default(), - AuthConfig::default(), - false, - ); - cf.generate_config(TEST_CONF_PATH, true).unwrap(); - let conf = PshConfig::read_config(TEST_CONF_PATH).unwrap(); - assert_eq!(conf, cf); - } +#[test] +fn parse_config_template() { + toml::from_str::(TEMPLATE).unwrap(); } diff --git a/src/daemon.rs b/src/daemon.rs index eae3499..1c493ad 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -14,74 +14,37 @@ use std::fs::File; +use anyhow::Result; use daemonize::Daemonize; -use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Daemon { - pid_file: String, - stdout_file: String, - stderr_file: String, - working_directory: String, -} +use crate::config::{DaemonConfig, DaemonWasmConfig}; -impl Default for Daemon { - fn default() -> Self { - Self { - pid_file: "/tmp/psh.pid".to_owned(), - stdout_file: "/tmp/psh.stdout".to_owned(), - stderr_file: "/tmp/psh.stderr".to_owned(), - working_directory: "/".to_owned(), - } - } -} +/// run the process as daemon +pub fn spawn_daemon(cfg: DaemonConfig) -> Result<()> { + let stdout = File::create(cfg.stdout)?; + let stderr = File::create(cfg.stderr)?; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct DaemonConfig { - pid_file: String, - stdout_file: String, - stderr_file: String, - working_directory: String, -} + let daemonize = Daemonize::new() + .pid_file(cfg.pid_file) + .chown_pid_file(true) // is optional, see `Daemonize` documentation + .working_directory(cfg.workdir) + .user("root") + .group("root") + .umask(0o027) // Set umask, `0o027` by default. + .stdout(stdout) // by default, stdout is redirect to `/tmp/psh.stdout`. + .stderr(stderr); // by default, stderr is redirect to `/tmp/psh.stderr`. -impl Default for DaemonConfig { - fn default() -> Self { - Self { - pid_file: "/tmp/psh.pid".to_owned(), - stdout_file: "/tmp/psh.stdout".to_owned(), - stderr_file: "/tmp/psh.stderr".to_owned(), - working_directory: "/".to_owned(), - } - } -} + daemonize.start()?; -impl Daemon { - pub fn new(config: DaemonConfig) -> Self { - Self { - pid_file: config.pid_file, - stdout_file: config.stdout_file, - stderr_file: config.stderr_file, - working_directory: config.working_directory, - } - } - - /// run the process as daemon - pub fn daemon(self) -> anyhow::Result<()> { - let stdout = File::create(self.stdout_file)?; - let stderr = File::create(self.stderr_file)?; - - let daemonize = Daemonize::new() - .pid_file(self.pid_file) - .chown_pid_file(true) // is optional, see `Daemonize` documentation - .working_directory(self.working_directory) - .user("root") - .group("root") - .umask(0o027) // Set umask, `0o027` by default. - .stdout(stdout) // by default, stdout is redirect to `/tmp/psh.stdout`. - .stderr(stderr); // by default, stderr is redirect to `/tmp/psh.stderr`. - - daemonize.start()?; + Ok(()) +} - Ok(()) +pub fn get_daemon_wasm_args(cfg: DaemonWasmConfig) -> Option> { + if !cfg.enable { + return None; } + let mut vec = Vec::with_capacity(cfg.args.len() + 1); + vec.push(cfg.path); + vec.extend(cfg.args); + Some(vec) } diff --git a/src/main.rs b/src/main.rs index 4452579..0abd60b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,8 @@ use args::Args; use chrono::offset::LocalResult; use chrono::{TimeZone, Utc}; use clap::Parser; -use config::{PshConfig, RemoteConfig}; +use config::RemoteConfig; +use daemon::{get_daemon_wasm_args, spawn_daemon}; use log::log_init; use opentelemetry_otlp::ExportConfig; use runtime::{Task, TaskRuntime}; @@ -48,20 +49,17 @@ fn main() -> Result<()> { } let mut args = Args::parse(); - let mut psh_config = PshConfig::read_config(&args.config).unwrap_or_else(|e| { - tracing::warn!("{e}, use default Psh config."); - PshConfig::default() - }); + let cfg = config::read_or_gen(args.config.clone())?; // When running as a daemon, it ignores all other cli arguments let component_args = if args.systemd() || args.daemon() { - psh_config.get_component_args() + get_daemon_wasm_args(cfg.daemon.wasm.clone()) } else { args.get_component_args() }; if args.daemon() { - daemon::Daemon::new(psh_config.daemon().clone()).daemon()?; + spawn_daemon(cfg.daemon)?; } let mut task_rt = TaskRuntime::new()?; @@ -78,7 +76,7 @@ fn main() -> Result<()> { thread::spawn(move || -> Result<()> { let rt = tokio::runtime::Runtime::new()?; - let tasks = async_tasks(psh_config.remote.clone(), psh_config.take_token(), task_rt); + let tasks = async_tasks(cfg.remote, task_rt); rt.block_on(tasks)?; Ok(()) }) @@ -88,22 +86,17 @@ fn main() -> Result<()> { Ok(()) } -async fn async_tasks( - remote_cfg: RemoteConfig, - token: String, - mut task_rt: TaskRuntime, -) -> Result<()> { - let token_ = token.clone(); - +async fn async_tasks(remote_cfg: RemoteConfig, mut task_rt: TaskRuntime) -> Result<()> { + let token_cloned = remote_cfg.token.clone(); let rpc_task = async move { - if !remote_cfg.enable { + if !remote_cfg.rpc.enable { let handle = task_rt.spawn(None)?; handle.join().expect("TaskRuntime has panicked"); return Ok(()); } - let duration = Duration::from_secs(remote_cfg.rpc.duration); - let mut client = RpcClient::new(remote_cfg.rpc, token_).await?; + let duration = Duration::from_secs(remote_cfg.rpc.heartbeat_interval); + let mut client = RpcClient::new(remote_cfg.rpc, token_cloned).await?; task_rt.spawn(Some(client.clone()))?; client.send_info().await?; loop { @@ -131,12 +124,15 @@ async fn async_tasks( }; let otlp_task = async { - if !remote_cfg.enable { + if !remote_cfg.otlp.enable { return Ok(()); } - let export_conf: ExportConfig = remote_cfg.otlp_conf.into(); - otlp::otlp_tasks(export_conf, token).await?; + let export_conf = ExportConfig { + endpoint: Some(remote_cfg.otlp.addr), + ..Default::default() + }; + otlp::otlp_tasks(export_conf, remote_cfg.token).await?; Ok::<(), Error>(()) }; diff --git a/src/services/config.rs b/src/services/config.rs deleted file mode 100644 index 889b16a..0000000 --- a/src/services/config.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023-2024 Optimatist Technology Co., Ltd. All rights reserved. -// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -// -// This file is part of PSH. -// -// PSH is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -// -// PSH is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even -// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License along with Performance Savior Home (PSH). If not, -// see . - -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct RpcConfig { - pub addr: String, - pub duration: u64, - pub instance_id_file: String, -} - -impl Default for RpcConfig { - fn default() -> Self { - Self { - addr: "https://rpc.optimatist.com".to_owned(), - duration: 1, - instance_id_file: "/etc/psh/instance.id".to_string(), - } - } -} diff --git a/src/services/mod.rs b/src/services/mod.rs index a79ab36..1985e45 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -15,6 +15,5 @@ pub mod pb { tonic::include_proto!("psh"); } -pub mod config; pub mod host_info; pub mod rpc; diff --git a/src/services/rpc.rs b/src/services/rpc.rs index 596d80b..380477f 100644 --- a/src/services/rpc.rs +++ b/src/services/rpc.rs @@ -16,8 +16,8 @@ use anyhow::Result; use tonic::transport::{Channel, ClientTlsConfig, Endpoint}; use tonic::Request; -use super::config::RpcConfig; use super::pb::{self, DataRequest}; +use crate::config::RpcConfig; use crate::services::host_info::RawInfo; use crate::services::pb::psh_service_client::PshServiceClient; use crate::services::pb::HostInfoRequest;