First commit
This commit is contained in:
commit
138803713a
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "pmr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
description = "A process manager in Rust"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
sysinfo = "0.33.1"
|
||||
tabled = "0.17.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
dirs = "5.0"
|
||||
serde_json = "1.0"
|
||||
crossterm = "0.28.1"
|
||||
once_cell = "1.18.0"
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Process Manager in Rust
|
||||
|
||||
## Usage
|
||||
Just like pm2
|
||||
|
||||
```bash
|
||||
pmr start --name [name] [program] -- [args]
|
||||
pmr list/ls
|
||||
pmr stop [id;name]
|
||||
pmr stop [id;name]
|
||||
pmr restart [id;name]
|
||||
pmr delete/rm [idname]
|
||||
```
|
50
instruction.md
Normal file
50
instruction.md
Normal file
@ -0,0 +1,50 @@
|
||||
# AI Instruction
|
||||
|
||||
1.
|
||||
在当前文件夹下创建rust工程,编写 pmr 命令行工具,用于守护进程的管理:
|
||||
1. 支持windowns、Linux、MacOS
|
||||
2. 支持命令直接启动,例如 pmr start --name [别名] [程序名] -- [参数]
|
||||
3. 支持配置文件启动,例如 pmr start --config config.json:
|
||||
{
|
||||
"name": "test",
|
||||
"program": "python",
|
||||
"args": ["-m", "test"]
|
||||
}
|
||||
4. 支持查看进程列表,例如 pmr list(alias命令:ls),在终端显示列表,例如┌────┬───────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
|
||||
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
|
||||
├────┼───────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
|
||||
│ 0 │ naive │ default │ N/A │ fork │ 1492097 │ 22h │ 39 │ online │ 0% │ 8.1mb │ root │ disabled │
|
||||
│ 1 │ naive-http │ default │ N/A │ fork │ 1492102 │ 22h │ 9 │ online │ 0% │ 8.3mb │ root │ disabled │
|
||||
└────┴───────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
|
||||
|
||||
5.
|
||||
每次执行pmr命令,检查用户目录下是否有.pmr 文件夹,如果没有,则创建,并在文件夹下创建dump.json文件,用于管理使用pmr命令启动的进程,例如:
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "test",
|
||||
"program": "python",
|
||||
"args": ["-m", "test"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "test2",
|
||||
"program": "python",
|
||||
"args": ["-m", "test2"]
|
||||
}
|
||||
]
|
||||
修改list(ls)命令,添加参数 --system,用于显示系统所有进程,如果不添加该参数,只显示在~/.pmr/dump.json中的进程
|
||||
增加stop命令,用于关闭指定进程,例如pmr stop 1,关闭~/.pmr/dump.json中id为1的进程,或者pmr stop test,关闭~/.pmr/dump.json中name为test的进程
|
||||
|
||||
6.
|
||||
验证start命令成功执行notepad程序后,相关信息是否写入dump.json,并且执行stop命令能正常结束进程
|
||||
|
||||
7.继续修改pmr start命令,使得以下几种方式均可成功:
|
||||
pmr start pmr_id // 启动存在于dump.json中的进程
|
||||
pmr start name // 启动存在于dump.json中的进程
|
||||
pmr start --config config.json // 从配置文件启动进程
|
||||
pmr start program_name // 从程序启动进程
|
||||
|
||||
8.增加pmr stop命令,用于关闭指定进程,例如pmr stop 1,关闭~/.pmr/dump.json中id为1的进程,或者pmr stop test,关闭~/.pmr/dump.json中name为test的进程
|
||||
|
||||
9.增加命令:restart, 例如 pmr restart 1,重启~/.pmr/dump.json中id为1的进程,或者pmr restart test,重启~/.pmr/dump.json中name为test的进程, pmr restart --config config.json, 从配置文件重启进程
|
1
src/base/mod.rs
Normal file
1
src/base/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod process;
|
12
src/base/process.rs
Normal file
12
src/base/process.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PmrProcessInfo {
|
||||
pub pmr_id: u32, // 自增ID
|
||||
pub pid: u32, // 进程PID
|
||||
pub name: String,
|
||||
pub namespace: String,
|
||||
pub status: String,
|
||||
pub program: String,
|
||||
pub args: Vec<String>,
|
||||
}
|
57
src/commands/delete.rs
Normal file
57
src/commands/delete.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use super::super::config::dump::DumpConfig;
|
||||
use super::list::list_processes;
|
||||
use super::stop::stop_process;
|
||||
|
||||
pub fn delete_process(target: &str) {
|
||||
let dump_config = DumpConfig::get_instance();
|
||||
match dump_config.list_processes() {
|
||||
Ok(processes) => {
|
||||
let mut found = false;
|
||||
|
||||
// 首先尝试将target解析为pmr_id
|
||||
if let Ok(pmr_id) = target.parse::<u32>() {
|
||||
if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) {
|
||||
// 如果进程还在运行,先停止它
|
||||
if process.pid > 0 {
|
||||
stop_process(target, false);
|
||||
}
|
||||
|
||||
// 从配置中删除进程
|
||||
dump_config
|
||||
.delete_process(pmr_id)
|
||||
.expect("Failed to delete process from dump file");
|
||||
println!("Successfully deleted process '{}'", process.name);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是pmr_id,尝试按name查找
|
||||
if !found {
|
||||
if let Some(process) = processes.iter().find(|p| p.name == target) {
|
||||
// 如果进程还在运行,先停止它
|
||||
if process.pid > 0 {
|
||||
stop_process(&process.pmr_id.to_string(), false);
|
||||
}
|
||||
|
||||
// 从配置中删除进程
|
||||
dump_config
|
||||
.delete_process(process.pmr_id)
|
||||
.expect("Failed to delete process from dump file");
|
||||
println!("Successfully deleted process '{}'", process.name);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
// 显示进程列表
|
||||
println!("\nCurrent process list:");
|
||||
list_processes(false);
|
||||
} else {
|
||||
eprintln!("No process found with id or name: {}", target);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read processes: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
109
src/commands/list.rs
Normal file
109
src/commands/list.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use crate::config::dump::DumpConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sysinfo::{System, Users};
|
||||
use tabled::{Table, Tabled};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PmrProcess {
|
||||
pub pmr_id: u32,
|
||||
pub pid: u32,
|
||||
pub name: String,
|
||||
pub namespace: String,
|
||||
pub program: String,
|
||||
pub args: Vec<String>,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Tabled)]
|
||||
struct ProcessInfo {
|
||||
id: String,
|
||||
name: String,
|
||||
namespace: String,
|
||||
version: String,
|
||||
pid: String,
|
||||
uptime: String,
|
||||
restarts: String,
|
||||
status: String,
|
||||
cpu: String,
|
||||
mem: String,
|
||||
user: String,
|
||||
}
|
||||
|
||||
pub fn read_pmr_processes() -> Vec<PmrProcess> {
|
||||
let dump_config = DumpConfig::get_instance();
|
||||
match dump_config.list_processes() {
|
||||
Ok(processes) => processes
|
||||
.into_iter()
|
||||
.map(|p| PmrProcess {
|
||||
pmr_id: p.pmr_id,
|
||||
pid: p.pid,
|
||||
name: p.name,
|
||||
namespace: p.namespace,
|
||||
program: p.program,
|
||||
args: p.args,
|
||||
status: p.status,
|
||||
})
|
||||
.collect(),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read processes: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_processes(system: bool) {
|
||||
if system {
|
||||
let mut sys = System::new_all();
|
||||
let users = Users::new_with_refreshed_list();
|
||||
sys.refresh_all();
|
||||
|
||||
let processes: Vec<ProcessInfo> = sys
|
||||
.processes()
|
||||
.iter()
|
||||
.map(|(&pid, process)| ProcessInfo {
|
||||
id: "0".to_string(),
|
||||
name: process.name().to_string_lossy().to_string(),
|
||||
namespace: "default".to_string(),
|
||||
version: "N/A".to_string(),
|
||||
pid: pid.to_string(),
|
||||
uptime: process.run_time().to_string(),
|
||||
restarts: "0".to_string(),
|
||||
status: process.status().to_string(),
|
||||
cpu: format!("{:.1}%", process.cpu_usage()),
|
||||
mem: format!("{:.1} MB", process.memory() as f64 / 1024.0 / 1024.0),
|
||||
user: process
|
||||
.user_id()
|
||||
.and_then(|uid| users.get_user_by_id(uid))
|
||||
.map_or("N/A".to_string(), |u| u.name().to_string()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let table = Table::new(processes).to_string();
|
||||
println!("{}", table);
|
||||
} else {
|
||||
let pmr_processes = read_pmr_processes();
|
||||
let processes: Vec<ProcessInfo> = pmr_processes
|
||||
.iter()
|
||||
.map(|p| ProcessInfo {
|
||||
id: p.pmr_id.to_string(),
|
||||
name: p.name.clone(),
|
||||
namespace: "pmr".to_string(),
|
||||
version: "N/A".to_string(),
|
||||
pid: p.pid.to_string(),
|
||||
uptime: "N/A".to_string(),
|
||||
restarts: "0".to_string(),
|
||||
status: if p.pid > 0 {
|
||||
"running".to_string()
|
||||
} else {
|
||||
"stopped".to_string()
|
||||
},
|
||||
cpu: "N/A".to_string(),
|
||||
mem: "N/A".to_string(),
|
||||
user: "N/A".to_string(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let table = Table::new(processes).to_string();
|
||||
println!("{}", table);
|
||||
}
|
||||
}
|
5
src/commands/mod.rs
Normal file
5
src/commands/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod delete;
|
||||
pub mod list;
|
||||
pub mod restart;
|
||||
pub mod start;
|
||||
pub mod stop;
|
64
src/commands/restart.rs
Normal file
64
src/commands/restart.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use super::super::base::process::PmrProcessInfo;
|
||||
use super::super::config::dump::DumpConfig;
|
||||
use super::list::list_processes;
|
||||
use super::start::start_process;
|
||||
use super::stop::stop_process;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn restart_process(config: Option<PathBuf>, target: Option<String>, args: Vec<String>) {
|
||||
let dump_config = DumpConfig::get_instance();
|
||||
|
||||
// 如果指定了target,先检查是否是已存在的进程
|
||||
if let Some(ref target_str) = target {
|
||||
if let Ok(processes) = dump_config.list_processes() {
|
||||
// 尝试将target解析为pmr_id
|
||||
if let Ok(pmr_id) = target_str.parse::<u32>() {
|
||||
if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) {
|
||||
restart_existing_process(process);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 按名称查找进程
|
||||
if let Some(process) = processes.iter().find(|p| p.name == *target_str) {
|
||||
restart_existing_process(process);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是重启已存在的进程,就当作普通的启动处理
|
||||
start_process(config, None, target, args);
|
||||
}
|
||||
|
||||
fn restart_existing_process(process: &PmrProcessInfo) {
|
||||
println!("正在重启进程 '{}'...", process.name);
|
||||
|
||||
// 先停止进程
|
||||
stop_process(&process.pmr_id.to_string(), false);
|
||||
|
||||
// 重新启动进程
|
||||
let mut cmd = std::process::Command::new(&process.program);
|
||||
cmd.args(&process.args)
|
||||
.stdout(std::process::Stdio::inherit())
|
||||
.stderr(std::process::Stdio::inherit());
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
let pid = child.id();
|
||||
println!("进程 '{}' 重启成功,新 PID: {}", process.name, pid);
|
||||
|
||||
let dump_config = DumpConfig::get_instance();
|
||||
dump_config
|
||||
.update_process_status(process.pmr_id, pid, "running".to_string())
|
||||
.expect("无法更新进程状态");
|
||||
|
||||
// 显示进程列表
|
||||
println!("\n当前进程列表:");
|
||||
list_processes(false);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("重启进程 '{}' 失败: {}", process.name, e);
|
||||
}
|
||||
}
|
||||
}
|
157
src/commands/start.rs
Normal file
157
src/commands/start.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use super::super::base::process::PmrProcessInfo;
|
||||
use super::super::config::dump::DumpConfig;
|
||||
use super::list::list_processes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Config {
|
||||
name: String,
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn start_process(config: Option<PathBuf>, name: Option<String>, target: Option<String>, args: Vec<String>) {
|
||||
let dump_config = DumpConfig::get_instance();
|
||||
|
||||
// 如果指定了target,先检查是否是已存在的进程
|
||||
if let Some(ref target_str) = target {
|
||||
if let Ok(processes) = dump_config.list_processes() {
|
||||
// 尝试将target解析为pmr_id
|
||||
if let Ok(pmr_id) = target_str.parse::<u32>() {
|
||||
if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) {
|
||||
start_existing_process(process);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 按名称查找进程
|
||||
if let Some(process) = processes.iter().find(|p| p.name == *target_str) {
|
||||
start_existing_process(process);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否已存在同名进程
|
||||
let process_name = name.clone().unwrap_or_else(|| {
|
||||
target.clone().unwrap_or_else(|| {
|
||||
if let Some(ref config_path) = config {
|
||||
let mut file = File::open(config_path).expect("Failed to open config file");
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).expect("Failed to read config file");
|
||||
let config: Config = serde_json::from_str(&contents).expect("Failed to parse config file");
|
||||
config.name
|
||||
} else {
|
||||
"unnamed".to_string()
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if let Ok(processes) = dump_config.list_processes() {
|
||||
if let Some(_existing) = processes.iter().find(|p| p.name == process_name) {
|
||||
println!("\n进程 '{}' 已经存在:", process_name);
|
||||
list_processes(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(config_path) = config {
|
||||
// 从配置文件启动
|
||||
let mut file = File::open(config_path).expect("无法打开配置文件");
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.expect("无法读取配置文件");
|
||||
|
||||
let config: Config = serde_json::from_str(&contents).expect("无法解析配置文件");
|
||||
|
||||
let mut cmd = Command::new(&config.program);
|
||||
cmd.args(&config.args)
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
let pid = child.id();
|
||||
println!("启动进程 '{}' PID: {}", process_name, pid);
|
||||
|
||||
dump_config
|
||||
.add_process(
|
||||
process_name,
|
||||
"default".to_string(),
|
||||
config.program,
|
||||
pid,
|
||||
"running".to_string(),
|
||||
config.args,
|
||||
)
|
||||
.expect("无法将进程添加到配置文件");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("启动进程失败: {}", e);
|
||||
}
|
||||
}
|
||||
} else if let Some(target_program) = target {
|
||||
// 直接启动程序
|
||||
let mut cmd = Command::new(&target_program);
|
||||
cmd.args(&args)
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
let pid = child.id();
|
||||
println!("启动进程 '{}' PID: {}", process_name, pid);
|
||||
|
||||
dump_config
|
||||
.add_process(
|
||||
process_name,
|
||||
"default".to_string(),
|
||||
target_program,
|
||||
pid,
|
||||
"running".to_string(),
|
||||
args,
|
||||
)
|
||||
.expect("无法将进程添加到配置文件");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("启动进程失败: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("错误: 必须指定 --config 或 target");
|
||||
}
|
||||
}
|
||||
|
||||
fn start_existing_process(process: &PmrProcessInfo) {
|
||||
if process.status == "running" {
|
||||
println!("进程 '{}' 已经在运行中,PID: {}", process.name, process.pid);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(&process.program);
|
||||
cmd.args(&process.args)
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
let pid = child.id();
|
||||
println!("启动进程 '{}' PID: {}", process.name, pid);
|
||||
|
||||
let dump_config = DumpConfig::get_instance();
|
||||
dump_config
|
||||
.update_process_status(process.pmr_id, pid, "running".to_string())
|
||||
.expect("无法更新进程状态");
|
||||
|
||||
// 显示进程列表
|
||||
println!("\n当前进程列表:");
|
||||
list_processes(false);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("启动进程 '{}' 失败: {}", process.name, e);
|
||||
}
|
||||
}
|
||||
}
|
83
src/commands/stop.rs
Normal file
83
src/commands/stop.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use super::super::config::dump::DumpConfig;
|
||||
use super::list::list_processes;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn stop_process(target: &str, show_list: bool) {
|
||||
let dump_config = DumpConfig::get_instance();
|
||||
match dump_config.list_processes() {
|
||||
Ok(processes) => {
|
||||
let mut found = false;
|
||||
|
||||
// 首先尝试将target解析为pmr_id
|
||||
if let Ok(pmr_id) = target.parse::<u32>() {
|
||||
if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) {
|
||||
if process.pid > 0 {
|
||||
// 在Windows上使用taskkill命令终止进程
|
||||
let output = Command::new("taskkill")
|
||||
.args(&["/PID", &process.pid.to_string(), "/F"])
|
||||
.output()
|
||||
.expect("无法执行taskkill命令");
|
||||
|
||||
if output.status.success() {
|
||||
println!("已停止进程 '{}' (PID: {})", process.name, process.pid);
|
||||
dump_config
|
||||
.update_process_status(process.pmr_id, 0, "stopped".to_string())
|
||||
.expect("无法更新进程状态");
|
||||
found = true;
|
||||
} else {
|
||||
eprintln!(
|
||||
"停止进程失败 '{}' (PID: {}): {}",
|
||||
process.name,
|
||||
process.pid,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
println!("进程 '{}' 已经停止", process.name);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是pmr_id,尝试按name查找
|
||||
if !found {
|
||||
if let Some(process) = processes.iter().find(|p| p.name == target) {
|
||||
if process.pid > 0 {
|
||||
// 在Windows上使用taskkill命令终止进程
|
||||
let output = Command::new("taskkill")
|
||||
.args(&["/PID", &process.pid.to_string(), "/F"])
|
||||
.output()
|
||||
.expect("无法执行taskkill命令");
|
||||
|
||||
if output.status.success() {
|
||||
println!("已停止进程 '{}' (PID: {})", process.name, process.pid);
|
||||
dump_config
|
||||
.update_process_status(process.pmr_id, 0, "stopped".to_string())
|
||||
.expect("无法更新进程状态");
|
||||
} else {
|
||||
eprintln!(
|
||||
"停止进程失败 '{}' (PID: {}): {}",
|
||||
process.name,
|
||||
process.pid,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
println!("进程 '{}' 已经停止", process.name);
|
||||
}
|
||||
} else {
|
||||
eprintln!("未找到进程: {}", target);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据show_list参数决定是否显示进程列表
|
||||
if show_list {
|
||||
println!("\n当前进程列表:");
|
||||
list_processes(false);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("读取进程列表失败: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
102
src/config/dump.rs
Normal file
102
src/config/dump.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use super::super::base::process::PmrProcessInfo;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Mutex;
|
||||
use std::{fs, io, path::PathBuf};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct DumpData {
|
||||
processes: Vec<PmrProcessInfo>,
|
||||
}
|
||||
|
||||
pub struct DumpConfig {
|
||||
path: PathBuf,
|
||||
data: Mutex<DumpData>,
|
||||
}
|
||||
|
||||
static INSTANCE: OnceCell<DumpConfig> = OnceCell::new();
|
||||
|
||||
impl DumpConfig {
|
||||
fn new() -> io::Result<Self> {
|
||||
let home_dir = dirs::home_dir()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Home directory not found"))?;
|
||||
|
||||
let config_dir = home_dir.join(".pmr");
|
||||
if !config_dir.exists() {
|
||||
fs::create_dir_all(&config_dir)?;
|
||||
}
|
||||
|
||||
let dump_file = config_dir.join("dump.json");
|
||||
let data = if dump_file.exists() {
|
||||
let file_contents = fs::read_to_string(&dump_file)?;
|
||||
serde_json::from_str(&file_contents)?
|
||||
} else {
|
||||
let initial_data = DumpData {
|
||||
processes: Vec::new(),
|
||||
};
|
||||
fs::write(&dump_file, serde_json::to_string_pretty(&initial_data)?)?;
|
||||
initial_data
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
path: dump_file,
|
||||
data: Mutex::new(data),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_instance() -> &'static DumpConfig {
|
||||
INSTANCE.get_or_init(|| Self::new().expect("Failed to initialize DumpConfig"))
|
||||
}
|
||||
|
||||
fn save_data(&self, data: &DumpData) -> io::Result<()> {
|
||||
let json = serde_json::to_string_pretty(data)?;
|
||||
fs::write(&self.path, json)
|
||||
}
|
||||
|
||||
pub fn add_process(
|
||||
&self,
|
||||
name: String,
|
||||
namespace: String,
|
||||
program: String,
|
||||
pid: u32,
|
||||
status: String,
|
||||
args: Vec<String>,
|
||||
) -> io::Result<()> {
|
||||
let mut data = self.data.lock().unwrap();
|
||||
let new_id = data.processes.iter().map(|p| p.pmr_id).max().unwrap_or(0) + 1;
|
||||
|
||||
data.processes.push(PmrProcessInfo {
|
||||
pmr_id: new_id,
|
||||
name,
|
||||
namespace,
|
||||
pid,
|
||||
status,
|
||||
program,
|
||||
args,
|
||||
});
|
||||
|
||||
self.save_data(&data)
|
||||
}
|
||||
|
||||
pub fn delete_process(&self, id: u32) -> io::Result<()> {
|
||||
let mut data = self.data.lock().unwrap();
|
||||
data.processes.retain(|p| p.pmr_id != id);
|
||||
self.save_data(&data)
|
||||
}
|
||||
|
||||
pub fn list_processes(&self) -> io::Result<Vec<PmrProcessInfo>> {
|
||||
let data = self.data.lock().unwrap();
|
||||
Ok(data.processes.clone())
|
||||
}
|
||||
|
||||
pub fn update_process_status(&self, pmr_id: u32, pid: u32, status: String) -> io::Result<()> {
|
||||
let mut data = self.data.lock().unwrap();
|
||||
if let Some(process) = data.processes.iter_mut().find(|p| p.pmr_id == pmr_id) {
|
||||
process.pid = pid;
|
||||
process.status = status;
|
||||
self.save_data(&data)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
1
src/config/mod.rs
Normal file
1
src/config/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod dump;
|
125
src/main.rs
Normal file
125
src/main.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::path::PathBuf;
|
||||
mod base;
|
||||
mod commands;
|
||||
mod config;
|
||||
use commands::delete::delete_process;
|
||||
use commands::list::list_processes;
|
||||
use commands::restart::restart_process;
|
||||
use commands::start::start_process;
|
||||
use commands::stop::stop_process;
|
||||
use config::dump::DumpConfig;
|
||||
|
||||
fn config_init() -> std::io::Result<()> {
|
||||
// 使用DumpConfig初始化配置
|
||||
let _ = DumpConfig::get_instance();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "pmr")]
|
||||
#[command(about = "Process Manager in Rust", long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Start a process
|
||||
Start {
|
||||
/// Config file path
|
||||
#[arg(short, long)]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
/// Process name
|
||||
#[arg(short, long)]
|
||||
name: Option<String>,
|
||||
|
||||
/// Target (can be pmr_id, name, or program to run)
|
||||
target: Option<String>,
|
||||
|
||||
/// Arguments for the program
|
||||
#[arg(last = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
|
||||
/// List running processes
|
||||
#[command(alias = "ls")]
|
||||
List {
|
||||
/// Show all system processes
|
||||
#[arg(long)]
|
||||
system: bool,
|
||||
},
|
||||
|
||||
/// Delete a process
|
||||
#[command(alias = "rm")]
|
||||
Delete {
|
||||
/// Process ID or name
|
||||
target: String,
|
||||
},
|
||||
|
||||
/// Stop a process
|
||||
Stop {
|
||||
/// Process ID or name
|
||||
target: String,
|
||||
},
|
||||
|
||||
/// Restart a process
|
||||
Restart {
|
||||
/// Config file path
|
||||
#[arg(short, long)]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
/// Target (can be pmr_id, name, or program to run)
|
||||
target: Option<String>,
|
||||
|
||||
/// Arguments for the program
|
||||
#[arg(last = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = config_init() {
|
||||
eprintln!("Failed to initialize .pmr directory: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Start {
|
||||
config,
|
||||
name,
|
||||
target,
|
||||
args,
|
||||
} => {
|
||||
if config.is_none() && target.is_none() {
|
||||
eprintln!("错误: 必须指定 --config 或 target");
|
||||
return;
|
||||
}
|
||||
start_process(config.clone(), name.clone(), target.clone(), args);
|
||||
}
|
||||
Commands::List { system } => {
|
||||
list_processes(system);
|
||||
}
|
||||
Commands::Delete { target } => {
|
||||
delete_process(&target);
|
||||
}
|
||||
Commands::Stop { target } => {
|
||||
stop_process(&target, true);
|
||||
}
|
||||
Commands::Restart {
|
||||
config,
|
||||
target,
|
||||
args,
|
||||
} => {
|
||||
if config.is_none() && target.is_none() {
|
||||
eprintln!("错误: 必须指定 --config 或 target");
|
||||
return;
|
||||
}
|
||||
restart_process(config.clone(), target.clone(), args);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user