Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2880895
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
26 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Mar 19, 4:59 PM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
506623
Default Alt Text
(26 KB)
Attached To
Mode
rPMC Process Management Controller
Attached
Detach File
Event Timeline
Log In to Comment