# rspm **Repository Path**: vcqr/rspm ## Basic Information - **Project Name**: rspm - **Description**: RSPM (Rust Process Manager)是一个使用 Rust 实现的类似 PM2 的进程管理工具。它采用 CLI + Daemon 架构,通过 gRPC 进行通信,提供进程生命周期管理、自动重启、负载均衡、日志管理和系统监控等功能。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-12 - **Last Updated**: 2026-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: 任务调度, scheduler, Pm2, 服务管理, Dashboard ## README # RSPM 实现文档 ## 项目概述 rspm (Rust Process Manager) 是一个类似 pm2 的进程管理工具,使用 Rust 语言实现。本文档记录了完整的实现过程。 ## 一、需求分析 根据 PRD.md,核心功能包括: 1. **后台运行(守护进程)** - 将应用放在后台运行,终端关闭不影响服务 2. **自动重启** - 应用崩溃时自动重启,使用指数退避算法 3. **负载均衡** - 开启多进程充分利用多核 CPU 4. **日志管理** - 统一收集标准输出和错误日志,支持日志分割 5. **监控** - 提供实时的 CPU、内存监控 6. **分布式管理** - 支持通过 gRPC 进行远程管理 ## 二、架构设计 ### 2.1 整体架构 采用 CLI + Daemon 架构: ``` +----------------+ (gRPC) +-------------------+ | CLI (rspm) | <----------------------> | Daemon (rspmd) | +----------------+ +-------------------+ (用户交互) (核心调度引擎) | +-----------+-----------+ | | +---------------+ +---------------+ | Process A | | Process B | | (子进程管理) | | (日志收集) | +---------------+ +---------------+ ``` ### 2.2 模块划分 ``` rspm/ ├── rspm-common/ # 共享库:类型定义、错误处理、工具函数 ├── rspm-proto/ # gRPC 协议定义:proto 文件和生成的代码 ├── rspm-cli/ # CLI 工具:用户命令行接口 └── rspm-daemon/ # 守护进程:进程管理、监控、日志、gRPC 服务 ``` ### 2.3 进程状态机 ``` STOPPED -> STARTING -> RUNNING -> STOPPING -> ERRORED | | v v ERRORED STOPPING ``` ## 三、实现步骤 ### 步骤 1:项目结构设计 创建 Rust workspace 项目,包含四个子项目: ```toml # Cargo.toml (workspace root) [workspace] resolver = "3" members = [ "rspm-common", "rspm-proto", "rspm-cli", "rspm-daemon", ] ``` ### 步骤 2:实现共享模块 (rspm-common) #### 2.1 配置定义 (config.rs) ```rust pub struct ProcessConfig { pub name: String, pub command: String, pub args: Vec, pub env: HashMap, pub cwd: Option, pub instances: u32, pub autorestart: bool, pub max_restarts: u32, pub max_memory_mb: u32, pub watch: bool, pub watch_paths: Vec, pub log_file: Option, pub error_file: Option, pub log_max_size: u64, pub log_max_files: u32, } ``` #### 2.2 错误类型 (error.rs) ```rust #[derive(Error, Debug)] pub enum RspmError { #[error("Process not found: {0}")] ProcessNotFound(String), #[error("Failed to start process: {0}")] StartFailed(String), #[error("Daemon not running")] DaemonNotRunning, #[error("IO error: {0}")] IoError(#[from] std::io::Error), // ... 更多错误类型 } ``` #### 2.3 进程状态和重启策略 (process.rs) 实现指数退避重启策略: ```rust pub struct RestartPolicy { pub max_restarts: u32, pub restart_count: u32, pub restart_window_secs: u64, pub base_delay_ms: u64, // 基础延迟 1000ms pub max_delay_ms: u64, // 最大延迟 60000ms pub restart_times: Vec, } impl RestartPolicy { /// 计算指数退避延迟 pub fn calculate_delay(&mut self) -> Option { // 清理过期的重启记录 let window_ago = now - Duration::from_secs(self.restart_window_secs); self.restart_times.retain(|&t| t > window_ago); // 检查是否超过最大重启次数 if self.restart_times.len() >= self.max_restarts as usize { return None; } // 计算指数退避: 1s -> 2s -> 4s -> 8s -> ... let delay_ms = self.base_delay_ms * (2u64.pow(count - 1)); Some(Duration::from_millis(delay_ms.min(self.max_delay_ms))) } } ``` ### 步骤 3:定义 gRPC 协议 (rspm-proto) #### 3.1 Proto 文件定义 ```protobuf syntax = "proto3"; package rspm; // 进程状态枚举 enum ProcessState { STOPPED = 0; STARTING = 1; RUNNING = 2; STOPPING = 3; ERRORED = 4; } // 进程配置 message ProcessConfig { string name = 1; string command = 2; repeated string args = 3; map env = 4; string cwd = 5; int32 instances = 6; bool autorestart = 7; int32 max_restarts = 8; int32 max_memory_mb = 9; // ... } // 服务定义 service ProcessManager { rpc StartProcess(StartProcessRequest) returns (StartProcessResponse); rpc StopProcess(StopProcessRequest) returns (StopProcessResponse); rpc RestartProcess(RestartProcessRequest) returns (RestartProcessResponse); rpc ListProcesses(ListProcessesRequest) returns (ListProcessesResponse); rpc StreamLogs(StreamLogsRequest) returns (stream LogEntry); // ... } ``` #### 3.2 构建脚本 (build.rs) ```rust fn main() -> Result<(), Box> { tonic_build::configure() .build_server(true) .build_client(true) .compile_protos(&["proto/rspm.proto"], &["proto"])?; Ok(()) } ``` ### 步骤 4:实现守护进程核心模块 #### 4.1 进程管理器 (ProcessManager) 核心数据结构: ```rust pub struct ProcessManager { processes: Arc>>, state_store: Arc, log_writer: Arc, monitor: Arc, event_tx: broadcast::Sender, start_time: Instant, } ``` 主要方法实现: ```rust impl ProcessManager { /// 启动新进程 pub async fn start_process(&self, config: ProcessConfig) -> Result { // 检查进程是否已存在 // 为每个实例创建 ManagedProcess // 启动进程并捕获 stdout/stderr // 保存配置到状态存储 // 返回进程 ID } /// 停止进程 pub async fn stop_process(&self, id: &str, force: bool) -> Result<()> { // 发送 SIGTERM (或 SIGKILL if force) // 等待进程退出 // 更新状态 } /// 事件循环 - 监控和自动重启 fn start_event_loop(&self) { tokio::spawn(async move { loop { // 检查进程是否存活 // 如果进程死亡且需要重启,计算退避延迟并重启 // 更新进程统计信息 // 检查内存限制 } }); } } ``` #### 4.2 托管进程 (ManagedProcess) ```rust pub struct ManagedProcess { pub info: ProcessInfo, pub config: ProcessConfig, pub child: Option, pub restart_policy: RestartPolicy, pub start_time: Option, pub log_writer: Option>, } impl ManagedProcess { pub async fn start(&mut self, log_writer: Option>) -> Result<()> { // 构建 Command // 设置环境变量和工作目录 // 配置 stdout/stderr 管道 // 启动进程 // 启动日志读取任务 } pub async fn stop(&mut self, force: bool) -> Result<()> { // 发送终止信号 // 等待进程退出 // 清理资源 } } ``` #### 4.3 日志系统 (LogWriter) ```rust pub struct LogWriter { base_dir: PathBuf, stdout_file: RwLock>, stderr_file: RwLock>, max_size: RwLock, max_files: RwLock, log_tx: broadcast::Sender, } impl LogWriter { pub async fn write_stdout(&self, process_id: &str, line: &str) { // 写入日志文件 // 广播给订阅者 // 检查是否需要轮转 } fn rotate_stdout(&self) -> std::io::Result<()> { // 删除最旧的文件 // 重命名现有文件 (.1 -> .2) // 创建新文件 } } ``` #### 4.4 监控模块 (Monitor) ```rust pub struct Monitor { system: RwLock, } impl Monitor { pub fn get_process_stats(&self, pid: i32) -> Option { // 刷新进程信息 // 获取 CPU 使用率 // 获取内存使用量 } } ``` #### 4.5 gRPC 服务器 (RpcServer) ```rust pub struct RpcServer { process_manager: Arc, start_time: std::time::Instant, } impl RpcServer { #[cfg(unix)] pub async fn serve_unix(self, socket_path: &str) -> Result<()> { // 创建 Unix Domain Socket // 设置权限 // 启动 gRPC 服务器 } } #[tonic::async_trait] impl GrpcProcessManager for RpcServer { async fn start_process(&self, request: Request) -> Result, Status> { // 转换 proto 类型 // 调用 ProcessManager // 返回响应 } // ... 实现其他 RPC 方法 } ``` ### 步骤 5:实现 CLI 工具 #### 5.1 命令定义 (main.rs) 使用 clap 定义命令行接口: ```rust #[derive(Parser, Debug)] #[command(name = "rspm")] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Debug, Subcommand)] enum Commands { Start { name: Option, command: Option, instances: u32, ... }, Stop { id: Option, pid: Option, pname: Option, force: bool }, Restart { id: Option, pid: Option, pname: Option }, Delete { id: Option, pid: Option, pname: Option }, List { name: Option }, Show { id: Option, pid: Option, pname: Option }, Logs { id: Option, pid: Option, pname: Option, follow: bool, lines: u32 }, Scale { id: Option, pid: Option, pname: Option, instances: u32 }, StopAll, Status, StartDaemon, StopDaemon, } ``` #### 5.2 CLI 参数说明 所有进程操作命令支持三种方式指定进程标识符: | 方式 | 参数 | 说明 | |------|------|------| | 位置参数 | `` | 最简洁,自动判断是 ID 还是名字 | | 明确指定 ID | `--id ` | 明确指定数字进程 ID | | 明确指定名字 | `--name ` | 明确指定进程名字 | **示例**: ```bash # 方式 1: 位置参数(最简洁) rspm stop 46 rspm restart myapp rspm show 46 # 方式 2: 使用 --id 参数(明确指定数字 ID) rspm stop --id 46 rspm restart --id 46 # 方式 3: 使用 --name 参数(明确指定名字) rspm stop --name myapp rspm restart --name myapp ``` **参数验证**: - ❌ 不能同时使用多个标识符(如 `rspm stop 46 --id 46` 会报错) - ❌ 必须指定至少一个标识符(如 `rspm stop` 会报错) #### 5.3 gRPC 客户端 (GrpcClient) ```rust pub struct GrpcClient { client: ProcessManagerClient, } impl GrpcClient { #[cfg(unix)] pub async fn connect(socket_path: &str) -> Result { // 使用 Unix Domain Socket 连接 let channel = Endpoint::try_from("unix://unused")? .connect_with_connector(tower::service_fn(move |_: Uri| { UnixStream::connect(&path) })) .await?; Ok(Self { client: ProcessManagerClient::new(channel) }) } } ``` ## 四、关键技术点 ### 4.1 Unix Domain Socket 通信 客户端连接代码: ```rust use hyper_util::rt::TokioIo; let channel = Endpoint::try_from("unix://unused")? .connect_with_connector(tower::service_fn(move |_: Uri| { let path = path.clone(); async move { let stream = UnixStream::connect(&path).await?; Ok::<_, std::io::Error>(TokioIo::new(stream)) } })) .await?; ``` ### 4.2 异步进程管理 使用 tokio::process 管理子进程: ```rust let mut cmd = Command::new(&config.command); cmd.args(&config.args) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(true); let mut child = cmd.spawn()?; // 异步读取输出 let stdout = child.stdout.take().unwrap(); tokio::spawn(async move { let reader = BufReader::new(stdout); let mut lines = reader.lines(); while let Ok(Some(line)) = lines.next_line().await { log_writer.write_stdout(&process_id, &line).await; } }); ``` ### 4.3 状态持久化 使用 JSON 文件保存进程配置: ```rust pub struct StateStore { path: PathBuf, processes: Arc>>, } impl StateStore { pub async fn load(&self) -> Result<()> { let content = fs::read_to_string(&self.path).await?; let saved: HashMap = serde_json::from_str(&content)?; *self.processes.write().await = saved; Ok(()) } pub async fn save(&self) -> Result<()> { let processes = self.processes.read().await; let content = serde_json::to_string_pretty(&*processes)?; fs::write(&self.path, content).await?; Ok(()) } } ``` ### 4.4 类型冲突解决 Proto 生成的类型与自定义类型同名,使用类型别名: ```rust type ProtoProcessConfig = rspm_proto::ProcessConfig; type ProtoProcessInfo = rspm_proto::ProcessInfo; fn to_proto_process_info(info: &ProcessInfo) -> ProtoProcessInfo { ... } fn from_proto_process_config(proto: &ProtoProcessConfig) -> ProcessConfig { ... } ``` ## 六、依赖说明 ```toml # 核心依赖 tokio = { version = "1", features = ["full"] } # 异步运行时 tonic = "0.12" # gRPC 框架 prost = "0.13" # Protocol Buffers clap = { version = "4", features = ["derive"] } # CLI 参数解析 sysinfo = "0.33" # 系统监控 chrono = "0.4" # 时间处理 serde = { version = "1", features = ["derive"] } # 序列化 serde_json = "1" # JSON 处理 tracing = "0.1" # 日志 tracing-subscriber = "0.3" # 日志订阅 anyhow = "1" # 错误处理 thiserror = "2" # 自定义错误 uuid = "1" # UUID 生成 tabled = "0.17" # 表格输出 colored = "3" # 彩色输出 ``` ## 七、构建和运行 ### 7.1 前置要求 - Rust 1.70+ (edition 2024) - protoc (Protocol Buffers 编译器) ### 7.2 构建命令 ```bash # 设置 protoc 路径(如果需要) export PROTOC=/path/to/protoc # 构建 cargo build # 发布构建 cargo build --release --features embed-static ``` ### 7.3 运行命令 ```bash # 启动守护进程 ./target/debug/rspmd # 使用 CLI(另一个终端) ./target/debug/rspm start --name myapp -- /bin/sleep 100 ./target/debug/rspm list ./target/debug/rspm status ./target/debug/rspm stop myapp ``` ## 八、后续优化方向 1. **文件监控自动重启** - 使用 notify crate 监控文件变化(已实现配置文件热重载) 2. **开机自启** - 生成 systemd/launchd 服务文件 3. **Web Dashboard** - 添加 Web UI ✅ (已完成,访问 http://127.0.0.1:6681) 4. **集群管理** - 支持远程节点管理 5. **日志流式传输** - 完善 StreamLogs 实现 ✅ (已实现历史日志读取和实时流式传输) 6. **配置文件支持** - 支持 ecosystem.config.js 类似配置 ✅ (已支持 YAML/JSON/TOML) 7. **定时任务管理** - 支持 Cron 表达式、间隔执行、一次性执行 ✅ (已完成) 8. **静态文件服务器** - 快速启动静态文件服务器,支持目录列表 ✅ (已完成) ## 九、Web Dashboard RSPM 内置了 Web 管理界面,可以方便地查看和管理进程。 ### 访问地址 ``` http://127.0.0.1:6681 ``` ### 功能特性 - 📊 **统计面板** - 总进程数、运行中进程、守护进程运行时间、版本 - 📋 **进程列表** - 显示所有进程的状态、CPU、内存、运行时间 - 🎮 **进程操作** - 启动、停止、重启、删除进程 - 📜 **日志查看** - Tab 卡片方式显示 Stdout/Stderr,支持自动滚动 - 🔄 **自动刷新** - 每 5 秒自动更新数据 - 🌓 **主题切换** - 支持亮色/暗黑主题 ### 2026-03-01 最新修复 1. **图标显示** - 所有图标路径数据完整,与 Lucide React 一致 2. **日志 Tab** - Stdout/Stderr 改为 Tab 卡片视图,节省空间 3. **自动滚动** - 日志刷新时自动滚动到底部 4. **状态区分** - Auto/Manual、On/Off 按钮颜色明显区分 5. **图标颜色** - 统计卡片背景图标颜色与数值一致 6. **Footer 信息** - 底部显示版本号和运行时间 7. **蒙版修复** - 模态框蒙版覆盖全界面 8. **API 修复** - 启动和删除操作正常工作 ### 技术实现 - Axum Web 框架 - REST API - 现代化深色 UI(单页面应用) - 静态文件嵌入(rust-embed) ## 十、文件清单 ``` rspm/ ├── Cargo.toml # Workspace 配置 ├── CODEBUDDY.md # CodeBuddy 指引 ├── PRD.md # 需求文档 ├── IMPLEMENTATION.md # 本实现文档 ├── rspm-common/ │ ├── Cargo.toml │ └── src/ │ ├── lib.rs │ ├── config.rs # 进程配置 │ ├── config_format.rs # 配置格式支持 │ ├── config_loader.rs # 配置加载器 │ ├── process.rs # 进程状态和重启策略 │ ├── error.rs # 错误类型 │ ├── constants.rs # 常量定义和 banner │ ├── table.rs # 表格渲染(原 rspm-table) │ └── utils.rs # 工具函数 ├── rspm-proto/ │ ├── Cargo.toml │ ├── build.rs # Proto 构建脚本 │ ├── proto/ │ │ └── rspm.proto # gRPC 协议定义 │ └── src/ │ └── lib.rs ├── rspm-cli/ │ ├── Cargo.toml │ └── src/ │ ├── main.rs # CLI 入口 │ ├── client/ │ │ ├── mod.rs │ │ └── grpc_client.rs # gRPC 客户端 │ └── commands/ │ ├── mod.rs │ ├── start.rs │ ├── stop.rs │ ├── restart.rs │ ├── delete.rs │ ├── list.rs │ ├── show.rs │ ├── logs.rs │ ├── scale.rs │ ├── status.rs │ ├── daemon.rs │ ├── load.rs │ └── serve.rs # 静态文件服务器命令 └── rspm-daemon/ ├── Cargo.toml └── src/ ├── main.rs # 守护进程入口 ├── lib.rs ├── manager/ │ ├── mod.rs │ ├── process_manager.rs # 进程管理器 │ ├── managed_process.rs # 托管进程 │ └── state_store.rs # SQLite 状态存储 ├── monitor/ │ ├── mod.rs │ └── monitor.rs # 系统监控 ├── log_watcher/ │ ├── mod.rs │ └── log_writer.rs # 日志写入器和轮转 ├── config_watcher/ │ ├── mod.rs │ └── watcher.rs # 配置文件热重载 ├── server/ │ ├── mod.rs │ └── grpc.rs # gRPC 服务器 ├── web/ │ ├── mod.rs │ ├── server.rs # Web 服务器 │ └── api.rs # REST API └── static_server/ └── mod.rs # 静态文件服务器管理 ``` ## 八、CLI 命令参考 ### 进程管理命令 | 命令 | 说明 | 示例 | |------|------|------| | `start` | 启动新进程 | `rspm start myapp -- node server.js` | | `stop` | 停止进程 | `rspm stop 46` 或 `rspm stop --name myapp` | | `restart` | 重启进程 | `rspm restart 46` 或 `rspm restart --id 46` | | `delete` | 删除进程 | `rspm delete 46` 或 `rspm delete --name myapp` | | `delete --all` | 删除所有进程 | `rspm delete --all` | | `list` | 列出所有进程 | `rspm list` | | `show` | 显示进程详情 | `rspm show 46` 或 `rspm show --name myapp` | | `logs` | 查看进程日志 | `rspm logs 46` 或 `rspm logs --name myapp` | | `logs -n ` | 查看最近 N 行历史日志 | `rspm logs 46 -n 100` | | `scale` | 缩放进程实例数 | `rspm scale 46 3` 或 `rspm scale --name myapp 3` | | `stop-all` | 停止所有进程 | `rspm stop-all` | ### 定时任务命令 (schedule) | 命令 | 说明 | 示例 | |------|------|------| | `schedule create` | 创建定时任务 | `rspm schedule create -n backup --cron "0 0 2 * * *" --process myapp --action restart` | | `schedule list` | 列出所有定时任务 | `rspm schedule list` | | `schedule show` | 显示定时任务详情 | `rspm schedule show ` | | `schedule delete` | 删除定时任务 | `rspm schedule delete ` | | `schedule delete --all` | 删除所有定时任务 | `rspm schedule delete --all` | | `schedule pause` | 暂停定时任务 | `rspm schedule pause ` | | `schedule resume` | 恢复定时任务 | `rspm schedule resume ` | | `schedule history` | 查看执行历史 | `rspm schedule history --limit 50` | #### schedule create 参数 ```bash # 格式 1: 使用 -- 分隔符(推荐,类似 start 命令) rspm schedule create -n --interval --action execute -- [args...] # 格式 2: 使用 --command 和 --args rspm schedule create -n --interval --action execute --command --args="arg1,arg2,..." ``` **OPTIONS**: - `-n, --name ` - 任务名称(必需) - `--cron ` - Cron 表达式(6字段:秒 分 时 天 月 周) - `--interval ` - 间隔执行(秒) - `--once