Page MenuHomePhorge

No OneTemporary

Size
26 KB
Referenced Files
None
Subscribers
None
diff --git a/src/cli.rs b/src/cli.rs
index 629d8b9..a3c8cc9 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,338 +1,332 @@
use crate::structs::Args;
use colored::Colorize;
use global_placeholders::global;
use macros_rs::{crashln, string, ternary};
use psutil::process::{MemoryInfo, Process};
use serde::Serialize;
use serde_json::json;
use std::env;
use pmc::{
config,
file::{self, Exists},
helpers::{self, ColoredString},
process::Runner,
};
use tabled::{
settings::{
object::{Columns, Rows},
style::{BorderColor, Style},
themes::Colorization,
Color, Rotate,
},
Table, Tabled,
};
pub fn get_version(short: bool) -> String {
return match short {
true => format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
false => format!("{} ({} {}) [{}]", env!("CARGO_PKG_VERSION"), env!("GIT_HASH"), env!("BUILD_DATE"), env!("PROFILE")),
};
}
pub fn start(name: &Option<String>, args: &Option<Args>, watch: &Option<String>) {
let mut runner = Runner::new();
let config = config::read();
match args {
Some(Args::Id(id)) => {
println!("{} Applying action restartProcess on ({id})", *helpers::SUCCESS);
-
let item = runner.get(*id).restart();
- if let Some(name) = name {
- item.rename(name.clone())
- }
-
- if let Some(path) = watch {
- item.watch(path.clone())
- }
+ name.as_ref().map(|n| item.rename(n.clone())).unwrap_or(());
+ watch.as_ref().map(|p| item.watch(p.clone())).unwrap_or(());
println!("{} restarted ({id}) ✓", *helpers::SUCCESS);
list(&string!("default"));
}
Some(Args::Script(script)) => {
let name = match name {
Some(name) => string!(name),
None => string!(script.split_whitespace().next().unwrap_or_default()),
};
println!("{} Creating process with ({name})", *helpers::SUCCESS);
if name.ends_with(".ts") || name.ends_with(".js") {
let script = format!("{} {script}", config.runner.node);
runner.start(&name, &script, watch).save();
} else {
runner.start(&name, script, watch).save();
}
println!("{} created ({name}) ✓", *helpers::SUCCESS);
list(&string!("default"));
}
None => {}
}
}
pub fn stop(id: &usize) {
println!("{} Applying action stopProcess on ({id})", *helpers::SUCCESS);
let mut runner = Runner::new();
runner.get(*id).stop();
println!("{} stopped ({id}) ✓", *helpers::SUCCESS);
list(&string!("default"));
}
pub fn remove(id: &usize) {
println!("{} Applying action removeProcess on ({id})", *helpers::SUCCESS);
Runner::new().remove(*id);
println!("{} removed ({id}) ✓", *helpers::SUCCESS);
}
pub fn info(id: &usize, format: &String) {
#[derive(Clone, Debug, Tabled)]
struct Info {
#[tabled(rename = "error log path ")]
log_error: String,
#[tabled(rename = "out log path")]
log_out: String,
#[tabled(rename = "cpu percent")]
cpu_percent: String,
#[tabled(rename = "memory usage")]
memory_usage: String,
#[tabled(rename = "path hash")]
hash: String,
#[tabled(rename = "watching")]
watch: String,
#[tabled(rename = "exec cwd")]
path: String,
#[tabled(rename = "script command ")]
command: String,
#[tabled(rename = "script id")]
id: String,
restarts: u64,
uptime: String,
pid: String,
name: String,
status: ColoredString,
}
impl Serialize for Info {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let trimmed_json = json!({
"id": &self.id.trim(),
"pid": &self.pid.trim(),
"name": &self.name.trim(),
"path": &self.path.trim(),
"restarts": &self.restarts,
"watch": &self.watch.trim(),
"watch": &self.hash.trim(),
"uptime": &self.uptime.trim(),
"status": &self.status.0.trim(),
"log_out": &self.log_out.trim(),
"cpu": &self.cpu_percent.trim(),
"command": &self.command.trim(),
"mem": &self.memory_usage.trim(),
"log_error": &self.log_error.trim(),
});
trimmed_json.serialize(serializer)
}
}
if let Some(home) = home::home_dir() {
let item = Runner::new().get(*id).clone();
let mut memory_usage: Option<MemoryInfo> = None;
let mut cpu_percent: Option<f32> = None;
if let Ok(mut process) = Process::new(item.pid as u32) {
memory_usage = process.memory_info().ok();
cpu_percent = process.cpu_percent().ok();
}
let cpu_percent =
match cpu_percent {
Some(percent) => format!("{:.2}%", percent),
None => string!("0%"),
};
let memory_usage = match memory_usage {
Some(usage) => helpers::format_memory(usage.rss()),
None => string!("0b"),
};
let status =
if item.running {
"online ".green().bold()
} else {
match item.crash.crashed {
true => "crashed ",
false => "stopped ",
}
.red()
.bold()
};
let path = file::make_relative(&item.path, &home)
.map(|relative_path| relative_path.to_string_lossy().into_owned())
.unwrap_or_else(|| crashln!("{} Unable to get your current directory", *helpers::FAIL));
let data = vec![Info {
cpu_percent,
memory_usage,
id: string!(id),
restarts: item.restarts,
name: item.name.clone(),
path: format!("{} ", path),
status: ColoredString(status),
log_out: global!("pmc.logs.out", item.name.as_str()),
log_error: global!("pmc.logs.error", item.name.as_str()),
command: format!("/bin/bash -c '{}'", item.script.clone()),
pid: ternary!(item.running, format!("{}", item.pid), string!("n/a")),
hash: ternary!(item.watch.enabled, format!("{} ", item.watch.hash), string!("none ")),
watch: ternary!(item.watch.enabled, format!("{path}/{} ", item.watch.path), string!("disabled ")),
uptime: ternary!(item.running, format!("{}", helpers::format_duration(item.started)), string!("none")),
}];
let table = Table::new(data.clone())
.with(Rotate::Left)
.with(Style::rounded().remove_horizontals())
.with(Colorization::exact([Color::FG_CYAN], Columns::first()))
.with(BorderColor::filled(Color::FG_BRIGHT_BLACK))
.to_string();
if let Ok(json) = serde_json::to_string(&data[0]) {
match format.as_str() {
"raw" => println!("{:?}", data[0]),
"json" => println!("{json}"),
_ => {
println!("{}\n{table}\n", format!("Describing process with id ({id})").on_bright_white().black());
println!(" {}", format!("Use `pmc logs {id} [--lines <num>]` to display logs").white());
println!(" {}", format!("Use `pmc env {id}` to display environment variables").white());
}
};
};
} else {
crashln!("{} Impossible to get your home directory", *helpers::FAIL);
}
}
pub fn logs(id: &usize, lines: &usize) {
let item = Runner::new().get(*id).clone();
let log_error = global!("pmc.logs.error", item.name.as_str());
let log_out = global!("pmc.logs.out", item.name.as_str());
if Exists::file(log_error.clone()).unwrap() && Exists::file(log_out.clone()).unwrap() {
println!("{}", format!("Showing last {lines} lines for process [{id}] (change the value with --lines option)").yellow());
file::logs(*lines, &log_error, *id, "error", &item.name);
file::logs(*lines, &log_out, *id, "out", &item.name);
} else {
crashln!("{} Logs for process ({id}) not found", *helpers::FAIL);
}
}
#[cfg(target_os = "macos")]
pub fn env(id: &usize) {
let item = Runner::new().get(*id).clone();
for (key, value) in item.env.iter() {
println!("{}: {}", key, value.green());
}
}
pub fn list(format: &String) {
let mut runner = Runner::new();
let mut processes: Vec<ProcessItem> = Vec::new();
#[derive(Tabled, Debug)]
struct ProcessItem {
id: ColoredString,
name: String,
pid: String,
uptime: String,
#[tabled(rename = "↺")]
restarts: String,
status: ColoredString,
cpu: String,
mem: String,
#[tabled(rename = "watching")]
watch: String,
}
impl serde::Serialize for ProcessItem {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let trimmed_json = json!({
"cpu": &self.cpu.trim(),
"mem": &self.mem.trim(),
"id": &self.id.0.trim(),
"pid": &self.pid.trim(),
"name": &self.name.trim(),
"watch": &self.watch.trim(),
"uptime": &self.uptime.trim(),
"status": &self.status.0.trim(),
"restarts": &self.restarts.trim(),
});
trimmed_json.serialize(serializer)
}
}
if runner.is_empty() {
println!("{} Process table empty", *helpers::SUCCESS);
} else {
for (id, item) in runner.items() {
let mut memory_usage: Option<MemoryInfo> = None;
let mut cpu_percent: Option<f32> = None;
if let Ok(mut process) = Process::new(item.pid as u32) {
memory_usage = process.memory_info().ok();
cpu_percent = process.cpu_percent().ok();
}
let cpu_percent = match cpu_percent {
Some(percent) => format!("{:.0}%", percent),
None => string!("0%"),
};
let memory_usage = match memory_usage {
Some(usage) => helpers::format_memory(usage.rss()),
None => string!("0b"),
};
let status = if item.running {
"online ".green().bold()
} else {
match item.crash.crashed {
true => "crashed ",
false => "stopped ",
}
.red()
.bold()
};
processes.push(ProcessItem {
status: ColoredString(status),
cpu: format!("{cpu_percent} "),
mem: format!("{memory_usage} "),
restarts: format!("{} ", item.restarts),
name: format!("{} ", item.name.clone()),
id: ColoredString(id.to_string().cyan().bold()),
pid: ternary!(item.running, format!("{} ", item.pid), string!("n/a ")),
watch: ternary!(item.watch.enabled, format!("{} ", item.watch.path), string!("disabled ")),
uptime: ternary!(item.running, format!("{} ", helpers::format_duration(item.started)), string!("none ")),
});
}
let table = Table::new(&processes)
.with(Style::rounded().remove_verticals())
.with(BorderColor::filled(Color::FG_BRIGHT_BLACK))
.with(Colorization::exact([Color::FG_BRIGHT_CYAN], Rows::first()))
.to_string();
if let Ok(json) = serde_json::to_string(&processes) {
match format.as_str() {
"raw" => println!("{:?}", processes),
"json" => println!("{json}"),
"default" => println!("{table}"),
_ => {}
};
};
}
}
diff --git a/src/file.rs b/src/file.rs
index d93327e..ceef43e 100644
--- a/src/file.rs
+++ b/src/file.rs
@@ -1,121 +1,96 @@
-use crate::helpers;
+use crate::{helpers, log};
use anyhow::Error;
use colored::Colorize;
use macros_rs::{crashln, str, string, ternary};
use std::{
env,
fs::{self, File},
io::{self, BufRead, BufReader},
path::{Path, PathBuf, StripPrefixError},
thread::sleep,
time::Duration,
};
pub fn logs(lines_to_tail: usize, log_file: &str, id: usize, log_type: &str, item_name: &str) {
let file = File::open(log_file).unwrap();
let reader = BufReader::new(file);
let lines: Vec<String> = reader.lines().collect::<io::Result<_>>().unwrap();
let color = ternary!(log_type == "out", "green", "red");
println!("{}", format!("\n{log_file} last {lines_to_tail} lines:").bright_black());
let start_index = if lines.len() > lines_to_tail { lines.len() - lines_to_tail } else { 0 };
for (_, line) in lines.iter().skip(start_index).enumerate() {
println!("{} {}", format!("{}|{} |", id, item_name).color(color), line);
}
}
pub fn cwd() -> PathBuf {
match env::current_dir() {
Ok(path) => path,
Err(_) => crashln!("{} Unable to find current working directory", *helpers::FAIL),
}
}
pub fn make_relative(current: &Path, home: &Path) -> Option<std::path::PathBuf> {
match current.strip_prefix(home) {
Ok(relative_path) => Some(Path::new("~").join(relative_path)),
Err(StripPrefixError { .. }) => None,
}
}
pub struct Exists;
impl Exists {
pub fn folder(dir_name: String) -> Result<bool, Error> { Ok(Path::new(str!(dir_name)).is_dir()) }
pub fn file(file_name: String) -> Result<bool, Error> { Ok(Path::new(str!(file_name)).exists()) }
}
pub fn read<T: serde::de::DeserializeOwned>(path: String) -> T {
- let mut retry_count = 0;
- let max_retries = 5;
-
- let contents = loop {
- match fs::read_to_string(&path) {
- Ok(contents) => break contents,
- Err(err) => {
- retry_count += 1;
- if retry_count >= max_retries {
- crashln!("{} Cannot find dumpfile.\n{}", *helpers::FAIL, string!(err).white());
- } else {
- println!("{} Error reading dumpfile. Retrying... (Attempt {}/{})", *helpers::FAIL, retry_count, max_retries);
- }
- }
- }
- sleep(Duration::from_secs(1));
+ let contents = match fs::read_to_string(&path) {
+ Ok(contents) => contents,
+ Err(err) => crashln!("{} Cannot find dumpfile.\n{}", *helpers::FAIL, string!(err).white()),
};
- retry_count = 0;
-
- loop {
- match toml::from_str(&contents).map_err(|err| string!(err)) {
- Ok(parsed) => break parsed,
- Err(err) => {
- retry_count += 1;
- if retry_count >= max_retries {
- crashln!("{} Cannot parse dumpfile.\n{}", *helpers::FAIL, err.white());
- } else {
- println!("{} Error parsing dumpfile. Retrying... (Attempt {}/{})", *helpers::FAIL, retry_count, max_retries);
- }
- }
- }
- sleep(Duration::from_secs(1));
+ match toml::from_str(&contents).map_err(|err| string!(err)) {
+ Ok(parsed) => parsed,
+ Err(err) => crashln!("{} Cannot parse dumpfile.\n{}", *helpers::FAIL, err.white()),
}
}
pub fn read_rmp<T: serde::de::DeserializeOwned>(path: String) -> T {
let mut retry_count = 0;
let max_retries = 5;
let bytes = loop {
match fs::read(&path) {
Ok(contents) => break contents,
Err(err) => {
retry_count += 1;
if retry_count >= max_retries {
- crashln!("{} Cannot find dumpfile.\n{}", *helpers::FAIL, string!(err).white());
+ log!("{} Cannot find file.\n{}", *helpers::FAIL, string!(err).white());
} else {
- println!("{} Error reading dumpfile. Retrying... (Attempt {}/{})", *helpers::FAIL, retry_count, max_retries);
+ log!("{} Error reading file. Retrying... (Attempt {}/{})", *helpers::FAIL, retry_count, max_retries);
}
}
}
sleep(Duration::from_secs(1));
};
retry_count = 0;
loop {
match rmp_serde::from_slice(&bytes) {
Ok(parsed) => break parsed,
Err(err) => {
retry_count += 1;
if retry_count >= max_retries {
- crashln!("{} Cannot parse dumpfile.\n{}", *helpers::FAIL, string!(err).white());
+ log!("{} Cannot parse file.\n{}", *helpers::FAIL, string!(err).white());
} else {
- println!("{} Error parsing dumpfile. Retrying... (Attempt {}/{})", *helpers::FAIL, retry_count, max_retries);
+ log!("{} Error parsing file. Retrying... (Attempt {}/{})", *helpers::FAIL, retry_count, max_retries);
}
}
}
sleep(Duration::from_secs(1));
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 09cecb7..e86684d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,41 +1,42 @@
pub mod config;
pub mod file;
pub mod helpers;
+pub mod log;
pub mod process;
#[repr(transparent)]
pub struct Callback(pub extern "C" fn());
unsafe impl cxx::ExternType for Callback {
type Id = cxx::type_id!("Callback");
type Kind = cxx::kind::Trivial;
}
#[cxx::bridge]
pub mod service {
#[repr(u8)]
enum Fork {
Parent,
Child,
}
pub struct ProcessMetadata {
pub name: String,
pub shell: String,
pub command: String,
pub log_path: String,
pub args: Vec<String>,
}
unsafe extern "C++" {
include!("pmc/src/include/process.h");
include!("pmc/src/include/bridge.h");
include!("pmc/src/include/fork.h");
type Callback = crate::Callback;
pub fn stop(pid: i64) -> i64;
pub fn run(metadata: ProcessMetadata) -> i64;
pub fn try_fork(nochdir: bool, noclose: bool, callback: Callback) -> i32;
}
}
diff --git a/src/process/log.rs b/src/log.rs
similarity index 70%
rename from src/process/log.rs
rename to src/log.rs
index 558cef3..48796eb 100644
--- a/src/process/log.rs
+++ b/src/log.rs
@@ -1,17 +1,25 @@
use chrono::Local;
use global_placeholders::global;
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
pub struct Logger {
file: File,
}
impl Logger {
pub fn new() -> io::Result<Self> {
let file = OpenOptions::new().create(true).append(true).open(global!("pmc.log"))?;
Ok(Logger { file })
}
pub fn write(&mut self, message: &str) { writeln!(&mut self.file, "[{}] {}", Local::now().format("%Y-%m-%d %H:%M:%S%.3f"), message).unwrap() }
}
+
+#[macro_export]
+macro_rules! log {
+ ($message:expr $(, $arg:expr)*) => {
+ let mut log = crate::log::Logger::new().unwrap();
+ log.write(format!($message $(, $arg)*).as_str());
+ };
+}
diff --git a/src/process/mod.rs b/src/process/mod.rs
index fba7498..88d7261 100644
--- a/src/process/mod.rs
+++ b/src/process/mod.rs
@@ -1,246 +1,244 @@
macro_rules! assign {
($obj:expr, {$($field:ident),* $(,)?}) => {$($obj.$field = $field.clone();)*};
}
-mod dump;
-mod log;
-
use crate::{
config, file, helpers,
service::{run, stop, ProcessMetadata},
};
use chrono::serde::ts_milliseconds;
use chrono::{DateTime, Utc};
use macros_rs::{clone, crashln, string, then};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::{env, path::PathBuf};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Process {
pub id: usize,
pub pid: i64,
pub name: String,
pub path: PathBuf,
pub script: String,
pub env: HashMap<String, String>,
#[serde(with = "ts_milliseconds")]
pub started: DateTime<Utc>,
pub restarts: u64,
pub running: bool,
pub crash: Crash,
pub watch: Watch,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Crash {
pub crashed: bool,
pub value: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Watch {
pub enabled: bool,
pub path: String,
pub hash: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Runner {
pub id: id::Id,
pub list: BTreeMap<usize, Process>,
}
pub enum Status {
Offline,
Running,
}
impl Status {
pub fn to_bool(&self) -> bool {
match self {
Status::Offline => false,
Status::Running => true,
}
}
}
impl Runner {
pub fn new() -> Self {
let dump = dump::read();
let runner = Runner { id: dump.id, list: dump.list };
dump::write(&runner);
return runner;
}
pub fn start(&mut self, name: &String, command: &String, watch: &Option<String>) -> &mut Self {
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,
});
self.list.insert(
id,
Process {
id,
pid,
watch,
crash,
restarts: 0,
running: true,
path: file::cwd(),
name: name.clone(),
started: Utc::now(),
script: command.clone(),
env: env::vars().collect(),
},
);
dump::write(&self);
return self;
}
pub fn restart(&mut self, id: usize, name: String, dead: bool) -> &mut Self {
let item = self.get(id);
let Process { path, script, .. } = item.clone();
if let Err(err) = std::env::set_current_dir(&item.path) {
crashln!("{} Failed to set working directory {:?}\nError: {:#?}", *helpers::FAIL, path, err);
};
item.stop();
let config = config::read().runner;
item.crash.crashed = false;
item.pid = run(ProcessMetadata {
command: script,
args: config.args,
name: name.clone(),
shell: config.shell,
log_path: config.log_path,
});
item.watch = Watch {
enabled: false,
path: string!(""),
hash: string!(""),
};
item.name = name;
item.running = true;
item.started = Utc::now();
then!(dead, item.restarts += 1);
// assign!(item, {name, pid, watch});
return self;
}
pub fn remove(&mut self, id: usize) {
self.stop(id);
self.list.remove(&id);
dump::write(&self);
}
pub fn set_id(&mut self, id: id::Id) {
self.id = id;
self.id.next();
dump::write(&self);
}
pub fn set_status(&mut self, id: usize, status: Status) {
let item = self.get(id);
item.running = status.to_bool();
dump::write(&self);
}
pub fn save(&self) { dump::write(&self); }
pub fn count(&mut self) -> usize { self.list().count() }
pub fn is_empty(&self) -> bool { self.list.is_empty() }
pub fn items(&mut self) -> &mut BTreeMap<usize, Process> { &mut self.list }
pub fn list<'a>(&'a mut self) -> impl Iterator<Item = (&'a usize, &'a mut Process)> { self.list.iter_mut().map(|(k, v)| (k, v)) }
pub fn get(&mut self, id: usize) -> &mut Process { self.list.get_mut(&id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL)) }
pub fn set_crashed(&mut self, id: usize) -> &mut Self {
let item = self.get(id);
item.crash.crashed = true;
return self;
}
pub fn new_crash(&mut self, id: usize) -> &mut Self {
let item = self.get(id);
item.crash.value += 1;
return self;
}
pub fn stop(&mut self, id: usize) -> &mut Self {
let item = self.get(id);
stop(item.pid);
item.running = false;
item.crash.crashed = false;
item.crash.value = 0;
return self;
}
pub fn rename(&mut self, id: usize, name: String) -> &mut Self {
let item = self.get(id);
item.name = name;
return self;
}
pub fn watch(&mut self, id: usize, path: String) -> &mut Self {
let item = self.get(id);
item.watch = Watch {
enabled: true,
path: clone!(path),
hash: hash::create(item.path.join(path)),
};
return self;
}
}
impl Process {
pub fn stop(&mut self) { Runner::new().stop(self.id).save(); }
pub fn watch(&mut self, path: String) { Runner::new().watch(self.id, path).save(); }
pub fn rename(&mut self, name: String) { Runner::new().rename(self.id, name).save(); }
pub fn restart(&mut self) -> &mut Process {
Runner::new().restart(self.id, clone!(self.name), false).save();
return self;
}
pub fn crashed(&mut self) -> &mut Process {
Runner::new().new_crash(self.id).save();
Runner::new().restart(self.id, clone!(self.name), true).save();
return self;
}
}
+pub mod dump;
pub mod hash;
pub mod id;

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 19, 4:59 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
506623
Default Alt Text
(26 KB)

Event Timeline