11 个版本 (破坏性更新)
0.9.0 | 2022 年 6 月 5 日 |
---|---|
0.8.0 | 2021 年 3 月 26 日 |
0.7.0 | 2020 年 10 月 31 日 |
0.4.0 | 2020 年 5 月 25 日 |
0.1.3 | 2019 年 2 月 12 日 |
#31 在 性能分析
53,524 每月下载量
在 4 个crate 中使用
53KB
987 行
metered-rs
快速、易用的 Rust 性能指标!
Metered 帮助您在生产环境中测量程序的性能。受到 Coda Hale 的 Java 指标库的启发,Metered 通过提供声明式和过程宏来测量程序,而无需更改您的逻辑,使得实时测量变得简单。
Metered 考虑到以下原则构建
-
高可用性但无魔法:测量代码只需在代码上添加注解即可。Metered 允许您从裸指标构建自己的指标注册表,或使用过程宏自动生成。它不使用共享的全局变量或静态变量。
-
恒定,非常低的开销:良好的可用性不应该带来开销;唯一的开销是实际指标后端(例如计数器、仪表、直方图)带来的开销,而 Metered 中提供的那些在初始化后不会分配。Metered 将生成指标注册表作为常规 Rust
struct
,因此查找指标时不需要查找。Metered 提供了无同步和线程安全的指标后端,以便单线程或无共享架构不需要为同步付费。在可能的情况下,Metered 提供的线程安全指标后端使用无锁数据结构。 -
可扩展:指标只是实现了具有特定行为的
Metric
特性的常规类型。Metered 的宏允许您引用任何 Rust 类型,从而实现用户可扩展的属性!
许多指标只有在获得精确统计数据时才有意义。当涉及到低延迟、高范围的直方图时,没有比 Gil Tene 的高动态范围直方图 更好的选择,而 Metered 默认使用 官方 Rust 端口。
变更日志
- 0.9.0:
- 将整型指标包装而不是下溢/上溢
- 提供方法以递增或递减 int 指标超过 1,适用于批量计算
- 添加对
Clear
的泛型实现(由 @plankton6 贡献) - 向
HdrHistogram
添加 len 方法(由 @plankton6 贡献) - 代码质量修复和依赖项更新
- 0.8.0:
- 通过
OnResultMut
而不是OnResult
来更新指标,以支持需要可变访问结果的指标 - 例如,消费一个Stream
(由 @w4 贡献)
- 通过
- 0.7.0:
- 公开内部指标后端类型
Throughput
(修复问题 #30) - 为所有顶级指标实现
Deref
- 公开内部指标后端类型
Throughput
- 向
error_count
属性添加skip_cleared
选项(由 @w4 贡献)- 引入一个新的
Clearable
特征,该特征公开了实现Clear
的指标的行 为(为了向后兼容)。目前仅在计数器上实现。 - 默认行为可以通过构建时功能
error-count-skip-cleared-by-default
控制
- 引入一个新的
- 公开内部指标后端类型
- 0.6.0:
- 扩展
error_count
宏,允许报告nested
枚举错误变体,为嵌套错误提供零成本的错误跟踪(由 @w4 贡献)
- 扩展
- 0.5.0:
- 将内部指标公开(由 @nemosupremo 贡献)
- 提供
error_count
宏以生成针对错误枚举的定制的ErrorCount
指标计数变体(由 @w4 贡献) - 使用
Drop
自动触发不依赖于结果值的指标(影响InFlight
、ResponseTime
、Throughput
)
- 0.4.0:
- 将
allow(missing_docs)
添加到生成的结构体中(这允许在 Rust 代码中使用带有 lint 级别 warn(missing_docs) 或甚至 deny(missing_docs) 的 metered 结构体)(由 @reyk 贡献) - 为生成的注册表实现
Clear
(由 @eliaslevy 贡献) - 为
RefCell<HdrHistogram>
实现Histogram
和Clear
(由 @eliaslevy 贡献) - 引入具有微秒精度的
Instant
(由 @eliaslevy 贡献)- API 破坏性更改:将
Instant.elapsed_millis
重命名为elapsed_time
,并引入一个新关联常量ONE_SEC
,用于指定 instant 单位中的一秒。
- API 破坏性更改:将
- 通过重新导出使
AtomicTxPerSec
和TxPerSec
可见(由 @eliaslevy 贡献) - 将
StdInstant
作为TxPerSec
中T: Instant
的默认类型参数(由 @eliaslevy 贡献) - 修改 HdrHistogram 以与 serde_prometheus 一起使用(由 @w4 贡献)
- 与 serde_prometheus 和任何 HTTP 服务器一起使用。
- 升级依赖项
indexmap
: 1.1 -> 1.3hdrhistogram
: 6.3 -> 7.1parking_lot
: 0.9 -> 0.10
- 将
- 0.3.0:
- 修复以在
async
测量方法中保留跨度。 - 更新 nightly 示例以使用新语法和 Tokio 0.2-alpha(使用 std futures,需要 Rust >= 1.39,夜间或非夜间)
- 将依赖项更新到使用
syn
、proc-macro2
和quote
1.0
- 修复以在
- 0.2.2:
- 在
#measured
方法中,异步支持不再依赖于异步闭包,因此客户端代码不需要使用async_closure
功能门。 - 更新依赖版本
- 在
- 0.2.1:
- 在某些情况下,Serde 会为
PhantomData
标记在ResponseTime
和Throughput
指标中序列化 "nulls"。现在它们被明确排除。
- 在某些情况下,Serde 会为
- 0.2.0:
- 支持
.await
语法用户(不再需要await!()
)
- 支持
- 0.1.3:
- 修复了
#[measure]
'ed 方法中的提前返回问题 - 移除了
AtomicRefCell
的使用,这有时会导致 panic。 - 支持自定义注册可见性。
- 支持
async
+await!()
宏用户。
- 修复了
使用 Metered
Metered 提供了多种实用的指标,开箱即用。
HitCount
:一个计数器,跟踪代码被触发的次数。ErrorCount
:一个计数器,跟踪返回了多少错误(适用于任何返回 stdResult
的表达式)InFlight
:一个仪表,跟踪有多少请求是活跃的ResponseTime
:基于 HdrHistogram 的表达式的持续时间统计Throughput
:基于 HdrHistogram 的每秒调用表达式的次数统计
这些指标通常应用于方法,使用提供的程序宏生成样板代码。
为了获得更高的性能,这些库存指标可以根据需要定制为使用非线程安全(!Sync
/!Send
)数据结构,但在可能的情况下,默认使用线程安全的数据结构,这些数据结构使用无锁策略实现。这是一个为了在各种情况下都有效而做出的折衷选择。
Metered 被设计为无开销的抽象——也就是说,高级别的人体工程学不应该比手动添加指标更昂贵。值得注意的是,库存指标在首次初始化后不会分配内存。然而,它们在每个方法调用时都会触发,因此使用较轻的指标(例如 HitCount
)在热代码路径中可能很有趣,而在高级别入口点中使用较重的指标(例如 Throughput
、ResponseTime
)可能更有利。
如果您需要缺失的指标,或者您想自定义指标(例如,跟踪特定错误发生的次数,或根据您的返回类型做出反应),您可以通过实现 metered::metric::Metric
特性来简单地实现自己的指标。
计量工具不使用静态变量或共享的全局状态。相反,它允许您使用所需的指标自行构建自己的指标注册表,或者可以使用方法属性为您生成指标注册表。计量工具将为每个带有 metered
属性注解的 impl
块生成一个注册表,该注册表名称由 registry
参数提供。默认情况下,计量工具期望注册表以 self.metrics
的方式访问,但可以使用 registry_expr
属性参数来覆盖表达式。有关更多示例,请参阅演示。
计量工具将生成继承自 Debug
和 serde::Serialize
的指标注册表,以便轻松提取您的指标。计量工具为每个带有 measure
属性的方法生成一个子注册表,从而组织层次结构化的指标。这确保了在生成的注册表中访问指标的时间始终是常数(并且,在可能的情况下,缓存友好),除了指标本身之外没有其他开销。
计量工具可以愉快地测量任何方法,无论它是 async
还是非 async
,并且指标将按预期工作(例如,ResponseTime
将返回跨 await
调用的完成时间)。
目前,计量工具不提供连接到外部指标存储或监控系统的桥梁。此类支持计划在单独的模块中(欢迎贡献!)。
所需的 Rust 版本
计量工具从 1.31.0 版本的稳定版 Rust 开始工作。
它不使用任何夜间功能。在某些时候可能会出现一个 nightly
功能标志来使用即将推出的 Rust 功能(如 const fn
),以及计量工具依赖项中的类似功能,但这优先级较低(欢迎贡献)。
使用过程宏的示例(推荐)
use metered::{metered, Throughput, HitCount};
#[derive(Default, Debug, serde::Serialize)]
pub struct Biz {
metrics: BizMetrics,
}
#[metered(registry = BizMetrics)]
impl Biz {
#[measure([HitCount, Throughput])]
pub fn biz(&self) {
let delay = std::time::Duration::from_millis(rand::random::<u64>() % 200);
std::thread::sleep(delay);
}
}
在上面的代码片段中,我们将测量 biz
方法的 HitCount
和 Throughput
。
这是通过首先注解 impl
块并指定计量工具应给指标注册表提供的名称(在此处为 BizMetrics
)来实现的。稍后,计量工具将假设访问该存储库的表达式是 self.metrics
,因此我们需要在 Biz
中具有 BizMetrics
类型的 metrics
字段。可以通过指定另一个注册表达式来使用另一个字段名,例如 #[metered(registry = BizMetrics, registry_expr = self.my_custom_metrics)]
。
然后,我们必须使用 measure
属性来注释我们想要测量的方法,指定我们希望应用的度量标准:这里的度量标准只是实现 Metric
特质的结构类型,您可以定义自己的。因为没有魔法,我们必须确保 self.metrics
可以被访问,并且这只能在具有 &self
或 &mut self
接收器的 method
上工作。
让我们再次看看 biz
的代码:它是一个阻塞方法,在 0 到 200ms 之间返回,使用 rand::random
。由于 random
具有随机分布,我们可以期望平均睡眠时间为约 100ms。这意味着每秒大约有 10 次调用。
在接下来的测试中,我们启动 5 个线程,每个线程将调用 biz()
200 次。因此,我们可以期望命中计数为 1000,大约需要 20 秒(这意味着 20 个样本,因为我们每秒收集一个样本),并且大约每秒 50 次调用(每个线程 10 次,有 5 个线程)。
use std::thread;
use std::sync::Arc;
fn test_biz() {
let biz = Arc::new(Biz::default());
let mut threads = Vec::new();
for _ in 0..5 {
let biz = Arc::clone(&biz);
let t = thread::spawn(move || {
for _ in 0..200 {
biz.biz();
}
});
threads.push(t);
}
for t in threads {
t.join().unwrap();
}
// Print the results!
let serialized = serde_yaml::to_string(&*biz).unwrap();
println!("{}", serialized);
}
然后我们可以使用 serde 将我们的类型序列化为 YAML
metrics:
biz:
hit_count: 1000
throughput:
- samples: 20
min: 35
max: 58
mean: 49.75
stdev: 5.146600819958742
90%ile: 55
95%ile: 55
99%ile: 58
99.9%ile: 58
99.99%ile: 58
- ~
我们看到我们确实有每秒 49.75 次调用的平均值,这与我们的预期相符。
支持这些统计数据的 Hdr Histogram 能够提供比固定百分位数更多的信息,但在使用文本时这是一个实用的视图。为了更好的性能分析,请观看 Gil Tene 的演讲 ;-)。
宏参考
metered
属性
#[metered(注册表=您的注册表名称,注册表表达式=
self.wrapper.my_registry
)]
registry
是必需的,并且必须是一个有效的 Rust 标识符。
registry_expr
默认为 self.metrics
,其他值必须是一个有效的 Rust 表达式。此设置允许您配置解析为注册表的表达式。请注意,这会触发该表达式的不可变借用。
visibility
默认为 pub(crate)
,并且必须是一个有效的结构 Rust 可见性(例如,pub
、<nothing>
、pub(self)
等)。此设置允许您更改生成的注册表 struct
的可见性。注册表字段始终是公共的,并以蛇形方法或度量标准命名。
measure
属性
单个度量标准
#[measure(path::to::MyMetric<u64>)]
或
#[measure(类型=path::to::MyMetric<u64>)]
多个度量标准
#[measure([path::to::MyMetric<u64>,path::AnotherMetric])]
或
#[measure(类型=[path::to::MyMetric<u64>,path::AnotherMetric])]
type
关键字是被允许的,因为其他关键字计划用于未来的额外属性(例如,实例化选项)。
当将 measure
属性应用于 impl
块时,它适用于所有具有 measure
属性的方法。如果一个方法不需要额外的度量信息,可以通过简单地使用 #[measure]
来注释它,并且 impl
块的 measure
配置将被应用。
measure
关键字可以在一个 impl
块或方法上多次添加,这将添加到应用度量列表中。添加相同的度量多次会导致名称冲突。
设计
Metered 的自定义属性解析支持使用保留关键字和任意的 Rust 语法。代码已被提取到 Synattra 项目中,该项目在 Syn 解析器之上提供了用于属性解析的有用方法。
Metered 的度量可以包装任何代码片段,无论它们是否是 async
块,都使用卫生宏来模拟类似于面向方面编程的方法。该代码已被提取到 Aspect-rs 项目中!
许可协议
在以下任一许可协议下获得许可
- Apache 许可协议 2.0 版(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可协议(LICENSE-MIT 或 http://opensource.org/licenses/MIT)
由您选择。
贡献
除非您明确声明,否则根据 Apache-2.0 许可协议定义的您提交的任何有意包含在工作中的贡献,将按照上述方式双许可,不附加任何其他条款或条件。
依赖项
~3–9.5MB
~80K SLoC