# Qubit Progress
[](https://github.com/qubit-ltd/rs-progress/actions/workflows/ci.yml)
[](https://qubit-ltd.github.io/rs-progress/coverage/)
[](https://crates.io/crates/qubit-progress)
[](https://www.rust-lang.org)
[](LICENSE)
[](README.md)
面向 Rust 生态的通用进度汇报抽象。
## 什么时候使用
当一个操作需要汇报进度,但又不希望进度 API 绑定到某个具体业务领域时,可以使用
`qubit-progress`:
- 安装程序需要依次汇报准备、复制、校验、清理等阶段;
- 批处理任务需要统一的计数器和耗时信息;
- 命令行工具希望在控制台输出和日志输出之间切换;
- 库代码希望暴露进度快照,但不依赖具体运行时。
这个 crate 不是调度器、任务执行器或 UI 框架。它只定义进度事件模型和基础 reporter。
## 概览
Qubit Progress 将进度建模为不可变事件。一个进度事件本身包含:
- `ProgressPhase`:生命周期阶段,例如 started、running、finished、failed、canceled。
- `ProgressStage`:多阶段操作的可选阶段信息。
- `ProgressCounters`:通用的 total、completed、active、succeeded、failed 计数器。
- `std::time::Duration` 表示的已耗时。
除此之外,这个 crate 还提供:
- `ProgressEventBuilder`:链式构造器,可以直接组装进度事件,而不必先手动拼好 counters 和 stage。
- `ProgressReporter`:接收进度事件的 trait。
- `Progress`:单次操作的生命周期辅助对象,负责 elapsed 和按间隔汇报 running 事件。
- `RunningProgressGuard` 和 `RunningProgressPointHandle`:
`Progress::spawn_running_reporter` 返回的后台 running 进度汇报辅助对象,
适合 worker 线程更新共享业务状态、后台汇报线程读取快照并发送事件。
- `NoOpProgressReporter`、`StdoutProgressReporter`、
`StderrProgressReporter`、`WriterProgressReporter` 和
`LoggerProgressReporter`:可复用的 reporter 实现。
`ProgressReporter` 是一个刻意保持轻量的 trait:若内置 reporter 不够用,业务系统可以按自身需求实现自定义 reporter,例如在图形界面上驱动进度条、在桌面应用的状态区刷新进度,或向 Web 前端转发进度更新。本 crate 不绑定任何 UI 或传输层;把事件接到具体框架或通道上由你的集成代码完成。
业务 crate 应保留自己的领域状态,并在汇报进度时转换成 `ProgressEvent`。领域错误、日志、指标和链路追踪应使用各自机制,不应附加到进度事件上。
## 安装
```toml
[dependencies]
qubit-progress = "0.4"
```
## 快速开始
```rust
use std::time::Duration;
use qubit_progress::{
ProgressEvent,
ProgressReporter,
StdoutProgressReporter,
};
let reporter = StdoutProgressReporter::default();
let event = ProgressEvent::builder()
.running()
.total(4)
.completed(2)
.active(1)
.stage_named("copy", "Copy files")
.elapsed(Duration::from_secs(2))
.build();
reporter.report(&event);
```
当调用方已经有完整的 counters 或 stage 元数据时,底层构造器仍然可用;普通进度汇报
代码优先使用 builder。
## 汇报一次操作的生命周期
当一个操作需要 started、周期性 running 和终态事件时,使用 `Progress`。
`Progress` 负责记录耗时并对 `running` 回调做时间间隔节流;业务代码仍然维护自己的状态,并在汇报时转换成 `ProgressCounters`。
```rust
use std::time::Duration;
use qubit_progress::{
ProgressCounters,
Progress,
StdoutProgressReporter,
};
let reporter = StdoutProgressReporter::default();
let mut progress = Progress::new(&reporter, Duration::from_secs(5));
let started = progress.report_started(ProgressCounters::new(Some(3)));
assert!(started.elapsed().is_zero());
let mut completed = 0;
for _task in 0..3 {
// ... 执行一个工作单元 ...
completed += 1;
let counters = ProgressCounters::new(Some(3)).with_completed_count(completed);
let _running_event = progress.report_running_if_due(counters);
}
let final_counters = ProgressCounters::new(Some(3))
.with_completed_count(3)
.with_succeeded_count(3);
let finished = progress.report_finished(final_counters);
assert!(finished.elapsed() >= started.elapsed());
```
`report_running_if_due` 只有在真正发出事件时才返回 `Some(event)`。该方法不会阻塞等待
下一次汇报周期:未到期时会立即返回 `None`,到期时会同步调用 reporter 并返回刚发出的
事件(因此是否阻塞取决于 reporter 本身的实现)。实战中常见写法是:每完成一个工作
单元就调用一次;到汇报间隔时会自动发出事件,未到间隔时这次调用基本没有效果。如果
外部调度器或后台线程已经控制了汇报间隔,可以直接调用 `report_running`。
## 在后台线程中汇报 running 进度
当工作在一个或多个 worker 线程中执行,但 running 事件希望放到单独的后台汇报线程中
时,使用 `Progress::spawn_running_reporter`。worker 继续维护自己的业务状态;后台
汇报线程只负责等待超时或 `RunningProgressPointHandle::report` 信号,然后调用你提供
的快照闭包,把当前业务状态转换为新的 `ProgressCounters`。
这种模式适合并行执行器:正数汇报间隔下,worker 的热路径不需要直接调用 reporter;
当间隔是 `Duration::ZERO` 时,又可以在每个 worker 进度点之后唤醒后台循环,而且不会
忙等。协调线程持有 `RunningProgressGuard` guard,worker 只拿
`RunningProgressPointHandle`,结束时由协调线程调用 `stop_and_join`。
下面的示例用 [`qubit-atomic`](https://crates.io/crates/qubit-atomic) 的
[`AtomicCount`](https://docs.rs/qubit-atomic/latest/qubit_atomic/struct.AtomicCount.html)
作为跨线程共享的完成计数。只有在你采用这一写法时,才需要在 `Cargo.toml` 里增加对应依赖:
```toml
qubit-atomic = "0.10"
```
```rust
use std::{
sync::Arc,
thread,
time::Duration,
};
use qubit_atomic::AtomicCount;
use qubit_progress::{
Progress,
ProgressCounters,
StdoutProgressReporter,
};
let reporter = StdoutProgressReporter::default();
let completed = Arc::new(AtomicCount::zero());
let progress = Progress::new(&reporter, Duration::ZERO);
let running_progress =
progress.spawn_running_reporter(scope, move || {
ProgressCounters::new(Some(3))
.with_completed_count(loop_completed.get())
});
let progress_point_handle = running_progress.point_handle();
let mut handles = Vec::new();
for _ in 0..3 {
let completed_worker = Arc::clone(&completed);
let point = progress_point_handle.clone();
handles.push(scope.spawn(move || {
completed_worker.inc();
point.report();
}));
}
for handle in handles {
handle.join().unwrap();
}
running_progress.stop_and_join();
});
```
如果汇报间隔是正数,`RunningProgressPointHandle::report` 是 no-op;后台汇报线程会
通过超时等待定时醒来。这样 worker 可以无条件调用它,同时 stop/join 仍由
协调线程持有的 guard 负责。
## 阶段化进度
`ProgressStage` 描述操作处于哪个业务阶段。它和生命周期阶段不同:复制文件这个
stage 可以处于 running、finished、failed 或 canceled 等 phase。
```rust
use std::time::Duration;
use qubit_progress::{
ProgressEvent,
ProgressPhase,
ProgressStage,
};
let stage = ProgressStage::new("verify", "Verify installation")
.with_index(2)
.with_total_stages(5)
.with_weight(0.2);
let event = ProgressEvent::builder()
.phase(ProgressPhase::Running)
.total(10)
.completed(7)
.elapsed(Duration::from_secs(12))
.stage(stage)
.build();
assert_eq!(event.phase(), ProgressPhase::Running);
assert_eq!(event.counters().completed_count(), 7);
```
## 计数语义
`ProgressCounters` 支持已知总量和未知总量两类进度:
- `total_count: Some(n)` 表示可以计算百分比和剩余数量。
- `total_count: None` 表示开放式操作,或暂时不知道总量。
- `completed_count` 表示已经到达终态的工作单元数量。
- `active_count` 表示当前正在执行的工作单元数量。
- `succeeded_count` 和 `failed_count` 是可选的聚合结果计数,供能汇报这些
信息的业务使用。
当总量已知且为零时,进度被视为完成:
```rust
use qubit_progress::ProgressCounters;
let counters = ProgressCounters::new(Some(0));
assert_eq!(counters.progress_percent(), Some(100.0));
```
## Reporter 行为
Reporter 本质上会产生副作用。它可以写终端、写文件、发日志、更新 UI 桥接层,
也可以在测试中记录事件。如果 reporter panic,调用方自行决定传播还是隔离。
`WriterProgressReporter` 会写出紧凑的人类可读文本。
`StdoutProgressReporter` 和 `StderrProgressReporter` 是基于
`WriterProgressReporter` 的便捷 reporter,分别输出到标准输出和标准错误。
`LoggerProgressReporter` 通过 `log` crate 输出,并支持配置 target 和 level。
## 公共 API 速查
- `ProgressPhase`:生命周期阶段枚举。
- `ProgressStage`:阶段 id、名称、索引、总阶段数和可选的调用方权重。
如果调用方用 weight 计算加权进度,应传入有限且非负的值;本 crate
会按原样保存该值,不负责校验。
- `ProgressCounters`:通用计数器,提供剩余数量和百分比辅助方法。
- `ProgressEvent`:不可变事件,携带 phase、stage、counters 和 elapsed。
- `ProgressEventBuilder`:进度事件的链式构造器。
- `ProgressReporter`:接收进度事件的 trait。
- `Progress`:单次进度操作的生命周期辅助对象。
- `RunningProgressGuard`:持有 scoped 后台汇报线程的生命周期 guard。
- `RunningProgressPointHandle`:可克隆的 worker 侧 running point 句柄。
- `NoOpProgressReporter`:忽略所有事件的 reporter。
- `StdoutProgressReporter`:标准输出便捷 reporter。
- `StderrProgressReporter`:标准错误便捷 reporter。
- `WriterProgressReporter<W>`:基于 writer 的人类可读 reporter。
- `LoggerProgressReporter`:基于 `log` crate 的 reporter。
## 文档
- API 文档:[docs.rs/qubit-progress](https://docs.rs/qubit-progress)
- Crate 发布页:[crates.io/crates/qubit-progress](https://crates.io/crates/qubit-progress)
- 源码仓库:[github.com/qubit-ltd/rs-progress](https://github.com/qubit-ltd/rs-progress)
## 测试和 CI
在 crate 根目录运行快速本地检查:
```bash
cargo test
cargo clippy --all-targets -- -D warnings
```
要尽量匹配仓库 CI 环境,运行:
```bash
./align-ci.sh
./ci-check.sh
./coverage.sh json
```
`./align-ci.sh` 会同步本地工具链和 CI 相关配置;`./ci-check.sh` 运行与流水线一致的
检查。修改需要覆盖率体现的行为时,可以运行 `./coverage.sh`。
## 贡献
欢迎 issue 和 pull request。请保持改动聚焦;行为变化时补充或更新测试;公共 API 或
用户可见行为变化时同步更新 README 或 rustdoc。
贡献代码即表示你同意贡献内容使用同一份 [Apache License, Version 2.0](LICENSE) 许可。
## 许可和版权
Copyright © 2026 Haixing Hu, Qubit Co. Ltd.
本软件使用 [Apache License, Version 2.0](LICENSE) 许可。
## 作者和维护
**胡海星** — Qubit Co. Ltd.
| **源码仓库** | [github.com/qubit-ltd/rs-progress](https://github.com/qubit-ltd/rs-progress) |
| **API 文档** | [docs.rs/qubit-progress](https://docs.rs/qubit-progress) |
| **Crate 发布** | [crates.io/crates/qubit-progress](https://crates.io/crates/qubit-progress) |