diff --git a/src/cli/import.rs b/src/cli/import.rs index 3f8822a..928a98d 100644 --- a/src/cli/import.rs +++ b/src/cli/import.rs @@ -1,82 +1,150 @@ +use super::Item; use colored::Colorize; use macros_rs::{crashln, string}; -use serde::Deserialize; -use std::{collections::HashMap, fs}; +use serde::{Deserialize, Serialize}; + +use std::{ + collections::HashMap, + fs::{self, OpenOptions}, + io::prelude::*, +}; use pmc::{ + file::Exists, helpers, process::{Env, Runner}, }; #[derive(Deserialize, Debug)] struct ProcessWrapper { #[serde(alias = "process")] list: HashMap, } -#[derive(Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] struct Process { script: String, server: Option, watch: Option, #[serde(default)] env: Env, } -#[derive(Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] struct Watch { path: String, } impl Process { fn get_watch_path(&self) -> Option { self.watch.as_ref().and_then(|w| Some(w.path.clone())) } } pub fn read_hcl(path: &String) { let mut servers: Vec = vec![]; println!("{} Applying action importProcess", *helpers::SUCCESS); let contents = match fs::read_to_string(path) { Ok(contents) => contents, Err(err) => crashln!("{} Cannot read file to import.\n{}", *helpers::FAIL, string!(err).white()), }; let hcl_parsed: ProcessWrapper = match hcl::from_str(&contents) { Ok(hcl) => hcl, Err(err) => crashln!("{} Cannot parse imported file.\n{}", *helpers::FAIL, string!(err).white()), }; for (name, item) in hcl_parsed.list { let mut runner = Runner::new(); let server_name = &item.server.clone().unwrap_or("local".into()); let (kind, list_name) = super::format(server_name); runner = super::Internal { id: 0, server_name, kind: kind.clone(), runner: runner.clone(), } .create(&item.script, &Some(name.clone()), &item.get_watch_path(), true); println!("{} Imported {kind}process {name}", *helpers::SUCCESS); match runner.find(&name, server_name) { Some(id) => { let mut p = runner.get(id); p.stop(); p.set_env(item.env); p.restart(); } None => crashln!("{} Failed to write to ({name})", *helpers::FAIL), } if !servers.contains(&list_name) { servers.push(list_name); } } servers.iter().for_each(|server| super::Internal::list(&string!("default"), &server)); println!("{} Applied startProcess to imported items", *helpers::SUCCESS); } + +pub fn export_hcl(item: &Item, path: &Option) { + println!("{} Applying action exportProcess", *helpers::SUCCESS); + + let runner = Runner::new(); + + let fetch_process = |id: usize| { + let process = runner.try_info(id); + let mut watch_parsed = None; + let mut env_parsed = HashMap::new(); + + let current_env: HashMap = std::env::vars().collect(); + let path = path.clone().unwrap_or(format!("{}.hcl", process.name.clone())); + + if process.watch.enabled { + watch_parsed = Some(Watch { path: process.watch.path.clone() }) + } + + for (key, value) in process.env.clone() { + if let Some(current_value) = current_env.get(&key) { + if current_value != &value { + env_parsed.insert(key, value); + } + } else { + env_parsed.insert(key, value); + } + } + + let data = hcl::block! { + process (process.name.clone()) { + script = (process.script.clone()) + server = ("") + watch = (watch_parsed) + env = (env_parsed) + } + }; + + let serialized = hcl::to_string(&data).unwrap(); + + if Exists::check(&path).file() { + let mut file = OpenOptions::new().write(true).append(true).open(path.clone()).unwrap(); + if let Err(err) = writeln!(file, "{}", serialized) { + crashln!("{} Error writing to file.\n{}", *helpers::FAIL, string!(err).white()) + } + } else { + if let Err(err) = fs::write(path.clone(), serialized) { + crashln!("{} Error writing file.\n{}", *helpers::FAIL, string!(err).white()) + } + } + + println!("{} Exported process {id} to {path}", *helpers::SUCCESS); + }; + + match item { + Item::Id(id) => fetch_process(*id), + Item::Name(name) => match runner.find(&name, &string!("internal")) { + Some(id) => fetch_process(id), + None => crashln!("{} Process ({name}) not found", *helpers::FAIL), + }, + } +} diff --git a/src/main.rs b/src/main.rs index 134d280..5eb3aed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,256 +1,266 @@ mod cli; mod daemon; mod globals; mod webui; use clap::{Parser, Subcommand}; use clap_verbosity_flag::{LogLevel, Verbosity}; use macros_rs::{str, string, then}; use update_informer::{registry, Check}; use crate::{ cli::{internal::Internal, Args, Item}, globals::defaults, }; #[derive(Copy, Clone, Debug, Default)] struct NoneLevel; impl LogLevel for NoneLevel { fn default() -> Option { None } } #[derive(Parser)] #[command(version = str!(cli::get_version(false)))] struct Cli { #[command(subcommand)] command: Commands, #[clap(flatten)] verbose: Verbosity, } #[derive(Subcommand)] enum Daemon { /// Reset process index #[command(visible_alias = "reset_position")] Reset, /// Stop daemon #[command(visible_alias = "kill")] Stop, /// Restart daemon #[command(visible_alias = "restart", visible_alias = "start")] Restore { /// Daemon api #[arg(long)] api: bool, /// WebUI using api #[arg(long)] webui: bool, }, /// Check daemon health #[command(visible_alias = "info", visible_alias = "status")] Health { /// Format output #[arg(long, default_value_t = string!("default"))] format: String, }, } #[derive(Subcommand)] enum Server { /// Add new server #[command(visible_alias = "add")] New, /// List servers #[command(visible_alias = "ls")] List { /// Format output #[arg(long, default_value_t = string!("default"))] format: String, }, /// Remove server #[command(visible_alias = "rm", visible_alias = "delete")] Remove { /// Server name name: String, }, /// Set default server #[command(visible_alias = "set")] Default { /// Server name name: Option, }, } // add pmc restore command #[derive(Subcommand)] enum Commands { /// Import process from environment file #[command(visible_alias = "add")] Import { /// Path of file to import path: String, }, + /// Export environment file from process + #[command(visible_alias = "get")] + Export { + #[clap(value_parser = cli::validate::)] + item: Item, + /// Path to export file + path: Option, + }, /// Start/Restart a process #[command(visible_alias = "restart")] Start { /// Process name #[arg(long)] name: Option, #[clap(value_parser = cli::validate::)] args: Args, /// Watch to reload path #[arg(long)] watch: Option, /// Server #[arg(short, long)] server: Option, /// Reset environment values #[arg(short, long)] reset_env: bool, }, /// Stop/Kill a process #[command(visible_alias = "kill")] Stop { #[clap(value_parser = cli::validate::)] item: Item, /// Server #[arg(short, long)] server: Option, }, /// Stop then remove a process #[command(visible_alias = "rm", visible_alias = "delete")] Remove { #[clap(value_parser = cli::validate::)] item: Item, /// Server #[arg(short, long)] server: Option, }, /// Get env of a process #[command(visible_alias = "cmdline")] Env { #[clap(value_parser = cli::validate::)] item: Item, /// Server #[arg(short, long)] server: Option, }, /// Get information of a process #[command(visible_alias = "info")] Details { #[clap(value_parser = cli::validate::)] item: Item, /// Format output #[arg(long, default_value_t = string!("default"))] format: String, /// Server #[arg(short, long)] server: Option, }, /// List all processes #[command(visible_alias = "ls")] List { /// Format output #[arg(long, default_value_t = string!("default"))] format: String, /// Server #[arg(short, long)] server: Option, }, /// Restore all processes #[command(visible_alias = "resurrect")] Restore { /// Server #[arg(short, long)] server: Option, }, /// Save all processes to dumpfile #[command(visible_alias = "store")] Save { /// Server #[arg(short, long)] server: Option, }, /// Get logs from a process Logs { #[clap(value_parser = cli::validate::)] item: Item, #[arg(long, default_value_t = 15, help = "")] lines: usize, /// Server #[arg(short, long)] server: Option, }, /// Flush a process log #[command(visible_alias = "clean", visible_alias = "log_rotate")] Flush { #[clap(value_parser = cli::validate::)] item: Item, /// Server #[arg(short, long)] server: Option, }, /// Daemon management #[command(visible_alias = "agent", visible_alias = "bgd")] Daemon { #[command(subcommand)] command: Daemon, }, /// Server management #[command(visible_alias = "remote", visible_alias = "srv")] Server { #[command(subcommand)] command: Server, }, } fn main() { let cli = Cli::parse(); let mut env = env_logger::Builder::new(); let level = cli.verbose.log_level_filter(); let informer = update_informer::new(registry::Crates, "pmc", env!("CARGO_PKG_VERSION")); if let Some(version) = informer.check_version().ok().flatten() { println!("{} New version is available: {version}", *pmc::helpers::WARN); } globals::init(); env.filter_level(level).init(); match &cli.command { Commands::Import { path } => cli::import::read_hcl(path), + Commands::Export { item, path } => cli::import::export_hcl(item, path), Commands::Start { name, args, watch, server, reset_env } => cli::start(name, args, watch, reset_env, &defaults(server)), Commands::Stop { item, server } => cli::stop(item, &defaults(server)), Commands::Remove { item, server } => cli::remove(item, &defaults(server)), Commands::Restore { server } => Internal::restore(&defaults(server)), Commands::Save { server } => Internal::save(&defaults(server)), Commands::Env { item, server } => cli::env(item, &defaults(server)), Commands::Details { item, format, server } => cli::info(item, format, &defaults(server)), Commands::List { format, server } => Internal::list(format, &defaults(server)), Commands::Logs { item, lines, server } => cli::logs(item, lines, &defaults(server)), Commands::Flush { item, server } => cli::flush(item, &defaults(server)), Commands::Daemon { command } => match command { Daemon::Stop => daemon::stop(), Daemon::Reset => daemon::reset(), Daemon::Health { format } => daemon::health(format), Daemon::Restore { api, webui } => daemon::restart(api, webui, level.as_str() != "OFF"), }, Commands::Server { command } => match command { Server::New => cli::server::new(), Server::Remove { name } => cli::server::remove(name), Server::Default { name } => cli::server::default(name), Server::List { format } => cli::server::list(format, cli.verbose.log_level()), }, }; if !matches!(&cli.command, Commands::Daemon { .. }) && !matches!(&cli.command, Commands::Server { .. }) && !matches!(&cli.command, Commands::Save { .. }) && !matches!(&cli.command, Commands::Env { .. }) + && !matches!(&cli.command, Commands::Export { .. }) { then!(!daemon::pid::exists(), daemon::restart(&false, &false, false)); } } diff --git a/src/process/mod.rs b/src/process/mod.rs index 23e8e46..632e223 100644 --- a/src/process/mod.rs +++ b/src/process/mod.rs @@ -1,658 +1,660 @@ mod unix; use crate::{ config, config::structs::Server, file, helpers, service::{run, stop, ProcessMetadata}, }; use std::{ env, fs::File, path::PathBuf, sync::{Arc, Mutex}, }; use nix::{ sys::signal::{kill, Signal}, unistd::Pid, }; use chrono::serde::ts_milliseconds; use chrono::{DateTime, Utc}; use global_placeholders::global; use macros_rs::{crashln, string, ternary, then}; use psutil::process; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use utoipa::ToSchema; #[derive(Serialize, Deserialize, ToSchema)] pub struct ItemSingle { pub info: Info, pub stats: Stats, pub watch: Watch, pub log: Log, pub raw: Raw, } #[derive(Serialize, Deserialize, ToSchema)] pub struct Info { pub id: usize, pub pid: i64, pub name: String, pub status: String, #[schema(value_type = String, example = "/path")] pub path: PathBuf, pub uptime: String, pub command: String, pub children: Vec, } #[derive(Serialize, Deserialize, ToSchema)] pub struct Stats { pub restarts: u64, pub start_time: i64, pub cpu_percent: Option, pub memory_usage: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct MemoryInfo { pub rss: u64, pub vms: u64, } #[derive(Serialize, Deserialize, ToSchema)] pub struct Log { pub out: String, pub error: String, } #[derive(Serialize, Deserialize, ToSchema)] pub struct Raw { pub running: bool, pub crashed: bool, pub crashes: u64, } #[derive(Clone)] pub struct LogInfo { pub out: String, pub error: String, } #[derive(Serialize, Deserialize, ToSchema)] pub struct ProcessItem { pid: i64, id: usize, cpu: String, mem: String, name: String, restarts: u64, status: String, uptime: String, #[schema(example = "/path")] watch_path: String, #[schema(value_type = String, example = "2000-01-01T01:00:00.000Z")] start_time: DateTime, } #[derive(Clone)] pub struct ProcessWrapper { pub id: usize, pub runner: Arc>, } pub type Env = BTreeMap; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Process { pub id: usize, pub pid: i64, pub env: Env, pub name: String, pub path: PathBuf, pub script: String, pub restarts: u64, pub running: bool, pub crash: Crash, pub watch: Watch, pub children: Vec, #[serde(with = "ts_milliseconds")] pub started: DateTime, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Crash { pub crashed: bool, pub value: u64, } #[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct Watch { pub enabled: bool, #[schema(example = "/path")] pub path: String, pub hash: String, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Runner { pub id: id::Id, #[serde(skip)] pub remote: Option, pub list: BTreeMap, } #[derive(Clone, Debug)] pub struct Remote { address: String, token: Option, pub config: RemoteConfig, } #[derive(Clone, Debug, Deserialize)] pub struct RemoteConfig { pub shell: String, pub args: Vec, pub log_path: String, } pub enum Status { Offline, Running, } impl Status { pub fn to_bool(&self) -> bool { match self { Status::Offline => false, Status::Running => true, } } } macro_rules! lock { ($runner:expr) => {{ match $runner.lock() { Ok(runner) => runner, Err(err) => crashln!("Unable to lock mutex: {err}"), } }}; } fn kill_children(children: Vec) { for pid in children { if let Err(err) = kill(Pid::from_raw(pid as i32), Signal::SIGTERM) { log::error!("Failed to stop pid {pid}: {err:?}"); }; } } impl Runner { pub fn new() -> Self { dump::read() } pub fn refresh(&self) -> Self { Runner::new() } pub fn connect(name: String, Server { address, token }: Server, verbose: bool) -> Option { let remote_config = match config::from(&address, token.as_deref()) { Ok(config) => config, Err(err) => { log::error!("{err}"); return None; } }; if let Ok(dump) = dump::from(&address, token.as_deref()) { then!(verbose, println!("{} Fetched remote (name={name}, address={address})", *helpers::SUCCESS)); Some(Runner { remote: Some(Remote { token, address: string!(address), config: remote_config, }), ..dump }) } else { None } } pub fn start(&mut self, name: &String, command: &String, path: PathBuf, watch: &Option) -> &mut Self { if let Some(remote) = &self.remote { if let Err(err) = http::create(remote, name, command, path, watch) { crashln!("{} Failed to start create {name}\nError: {:#?}", *helpers::FAIL, err); }; } else { let id = self.id.next(); let config = config::read().runner; let crash = Crash { crashed: false, value: 0 }; let watch = match watch { Some(watch) => Watch { enabled: true, path: string!(watch), hash: hash::create(file::cwd().join(watch)), }, None => Watch { enabled: false, path: string!(""), hash: string!(""), }, }; let pid = run(ProcessMetadata { args: config.args, name: name.clone(), shell: config.shell, command: command.clone(), log_path: config.log_path, env: unix::env(), }); self.list.insert( id, Process { id, pid, path, watch, crash, restarts: 0, running: true, children: vec![], name: name.clone(), started: Utc::now(), script: command.clone(), env: env::vars().collect(), }, ); } return self; } pub fn restart(&mut self, id: usize, dead: bool) -> &mut Self { if let Some(remote) = &self.remote { if let Err(err) = http::restart(remote, id) { crashln!("{} Failed to start process {id}\nError: {:#?}", *helpers::FAIL, err); }; } else { let process = self.process(id); let config = config::read().runner; let Process { path, script, name, .. } = process.clone(); kill_children(process.children.clone()); stop(process.pid); if let Err(err) = std::env::set_current_dir(&path) { process.running = false; process.children = vec![]; process.crash.crashed = true; println!("{} Failed to set working directory {:?}\nError: {:#?}", *helpers::FAIL, path, err); } else { let mut temp_env = process.env.iter().map(|(key, value)| format!("{}={}", key, value)).collect::>(); temp_env.extend(unix::env()); process.pid = run(ProcessMetadata { args: config.args, name: name.clone(), shell: config.shell, log_path: config.log_path, command: script.to_string(), env: temp_env, }); process.running = true; process.children = vec![]; process.started = Utc::now(); process.crash.crashed = false; process.env.extend(env::vars().collect::()); then!(dead, process.restarts += 1); then!(dead, process.crash.value += 1); then!(!dead, process.crash.value = 0); } } return self; } pub fn remove(&mut self, id: usize) { if let Some(remote) = &self.remote { if let Err(err) = http::remove(remote, id) { crashln!("{} Failed to stop remove {id}\nError: {:#?}", *helpers::FAIL, err); }; } else { self.stop(id); self.list.remove(&id); self.save(); } } pub fn set_id(&mut self, id: id::Id) { self.id = id; self.id.next(); self.save(); } pub fn set_status(&mut self, id: usize, status: Status) { self.process(id).running = status.to_bool(); self.save(); } pub fn items(&self) -> BTreeMap { self.list.clone() } pub fn items_mut(&mut self) -> &mut BTreeMap { &mut self.list } pub fn save(&self) { then!(self.remote.is_none(), dump::write(&self)) } pub fn count(&mut self) -> usize { self.list().count() } pub fn is_empty(&self) -> bool { self.list.is_empty() } pub fn exists(&self, id: usize) -> bool { self.list.contains_key(&id) } pub fn info(&self, id: usize) -> Option<&Process> { self.list.get(&id) } + pub fn try_info(&self, id: usize) -> &Process { self.list.get(&id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL)) } + pub fn size(&self) -> Option<&usize> { self.list.iter().map(|(k, _)| k).max() } pub fn list<'l>(&'l mut self) -> impl Iterator { self.list.iter_mut().map(|(k, v)| (k, v)) } pub fn process(&mut self, id: usize) -> &mut Process { self.list.get_mut(&id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL)) } pub fn pid(&self, id: usize) -> i64 { self.list.get(&id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL)).pid } pub fn get(self, id: usize) -> ProcessWrapper { ProcessWrapper { id, runner: Arc::new(Mutex::new(self)), } } pub fn set_crashed(&mut self, id: usize) -> &mut Self { self.process(id).crash.crashed = true; return self; } pub fn set_env(&mut self, id: usize, env: Env) -> &mut Self { self.process(id).env.extend(env); return self; } pub fn clear_env(&mut self, id: usize) -> &mut Self { if let Some(remote) = &self.remote { if let Err(err) = http::clear_env(remote, id) { crashln!("{} Failed to clear environment on {id}\nError: {:#?}", *helpers::FAIL, err); }; } else { self.process(id).env = BTreeMap::new(); } return self; } pub fn set_children(&mut self, id: usize, children: Vec) -> &mut Self { self.process(id).children = children; return self; } pub fn new_crash(&mut self, id: usize) -> &mut Self { self.process(id).crash.value += 1; return self; } pub fn stop(&mut self, id: usize) -> &mut Self { if let Some(remote) = &self.remote { if let Err(err) = http::stop(remote, id) { crashln!("{} Failed to stop process {id}\nError: {:#?}", *helpers::FAIL, err); }; } else { let process = self.process(id); kill_children(process.children.clone()); stop(process.pid); process.running = false; process.crash.crashed = false; process.crash.value = 0; process.children = vec![]; } return self; } pub fn flush(&mut self, id: usize) -> &mut Self { if let Some(remote) = &self.remote { if let Err(err) = http::flush(remote, id) { crashln!("{} Failed to flush process {id}\nError: {:#?}", *helpers::FAIL, err); }; } else { self.process(id).logs().flush(); } return self; } pub fn rename(&mut self, id: usize, name: String) -> &mut Self { if let Some(remote) = &self.remote { if let Err(err) = http::rename(remote, id, name) { crashln!("{} Failed to rename process {id}\nError: {:#?}", *helpers::FAIL, err); }; } else { self.process(id).name = name; } return self; } pub fn watch(&mut self, id: usize, path: &str, enabled: bool) -> &mut Self { let process = self.process(id); process.watch = Watch { enabled, path: string!(path), hash: ternary!(enabled, hash::create(process.path.join(path)), string!("")), }; return self; } pub fn find(&self, name: &str, server_name: &String) -> Option { let mut runner = self.clone(); if !matches!(&**server_name, "internal" | "local") { let Some(servers) = config::servers().servers else { crashln!("{} Failed to read servers", *helpers::FAIL) }; if let Some(server) = servers.get(server_name) { runner = match Runner::connect(server_name.clone(), server.get(), false) { Some(remote) => remote, None => crashln!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address), }; } else { crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL) }; } runner.list.iter().find(|(_, p)| p.name == name).map(|(id, _)| *id) } pub fn fetch(&self) -> Vec { let mut processes: Vec = Vec::new(); for (id, item) in self.items() { let mut memory_usage: Option = None; let mut cpu_percent: Option = None; if let Ok(process) = process::Process::new(item.pid as u32) { let mem_info_psutil = process.memory_info().ok(); cpu_percent = Some(super::service::get_process_cpu_usage_percentage(item.pid as i64)); memory_usage = Some(MemoryInfo { rss: mem_info_psutil.as_ref().unwrap().rss(), vms: mem_info_psutil.as_ref().unwrap().vms(), }); } let cpu_percent = match cpu_percent { Some(percent) => format!("{:.2}%", percent), None => string!("0.00%"), }; let memory_usage = match memory_usage { Some(usage) => helpers::format_memory(usage.rss), None => string!("0b"), }; let status = if item.running { string!("online") } else { match item.crash.crashed { true => string!("crashed"), false => string!("stopped"), } }; processes.push(ProcessItem { id, status, pid: item.pid, cpu: cpu_percent, mem: memory_usage, restarts: item.restarts, name: item.name.clone(), start_time: item.started, watch_path: item.watch.path.clone(), uptime: helpers::format_duration(item.started), }); } return processes; } } impl LogInfo { pub fn flush(&self) { if let Err(err) = File::create(&self.out) { log::debug!("{err}"); crashln!("{} Failed to purge logs (path={})", *helpers::FAIL, self.error); } if let Err(err) = File::create(&self.error) { log::debug!("{err}"); crashln!("{} Failed to purge logs (path={})", *helpers::FAIL, self.error); } } } impl Process { /// Get a log paths of the process item pub fn logs(&self) -> LogInfo { let name = self.name.replace(" ", "_"); LogInfo { out: global!("pmc.logs.out", name.as_str()), error: global!("pmc.logs.error", name.as_str()), } } } impl ProcessWrapper { /// Stop the process item pub fn stop(&mut self) { lock!(self.runner).stop(self.id).save(); } /// Restart the process item pub fn restart(&mut self) { lock!(self.runner).restart(self.id, false).save(); } /// Rename the process item pub fn rename(&mut self, name: String) { lock!(self.runner).rename(self.id, name).save(); } /// Enable watching a path on the process item pub fn watch(&mut self, path: &str) { lock!(self.runner).watch(self.id, path, true).save(); } /// Disable watching on the process item pub fn disable_watch(&mut self) { lock!(self.runner).watch(self.id, "", false).save(); } /// Set the process item as crashed pub fn crashed(&mut self) { lock!(self.runner).restart(self.id, true).save(); } /// Get the borrowed runner reference (lives till program end) pub fn get_runner(&mut self) -> &Runner { Box::leak(Box::new(lock!(self.runner))) } /// Append new environment values to the process item pub fn set_env(&mut self, env: Env) { lock!(self.runner).set_env(self.id, env).save(); } /// Clear environment values of the process item pub fn clear_env(&mut self) { lock!(self.runner).clear_env(self.id).save(); } /// Get a json dump of the process item pub fn fetch(&self) -> ItemSingle { let mut runner = lock!(self.runner); let item = runner.process(self.id); let config = config::read().runner; let mut memory_usage: Option = None; let mut cpu_percent: Option = None; if let Ok(process) = process::Process::new(item.pid as u32) { let mem_info_psutil = process.memory_info().ok(); cpu_percent = Some(super::service::get_process_cpu_usage_percentage(item.pid as i64)); memory_usage = Some(MemoryInfo { rss: mem_info_psutil.as_ref().unwrap().rss(), vms: mem_info_psutil.as_ref().unwrap().vms(), }); } let status = if item.running { string!("online") } else { match item.crash.crashed { true => string!("crashed"), false => string!("stopped"), } }; ItemSingle { info: Info { status, id: item.id, pid: item.pid, name: item.name.clone(), path: item.path.clone(), children: item.children.clone(), uptime: helpers::format_duration(item.started), command: format!("{} {} '{}'", config.shell, config.args.join(" "), item.script.clone()), }, stats: Stats { cpu_percent, memory_usage, restarts: item.restarts, start_time: item.started.timestamp_millis(), }, watch: Watch { enabled: item.watch.enabled, hash: item.watch.hash.clone(), path: item.watch.path.clone(), }, log: Log { out: item.logs().out, error: item.logs().error, }, raw: Raw { running: item.running, crashed: item.crash.crashed, crashes: item.crash.value, }, } } } pub mod dump; pub mod hash; pub mod http; pub mod id;