48个版本 (5个稳定版)
1.4.0 | 2024年4月28日 |
---|---|
1.3.0 | 2024年3月23日 |
1.2.0 | 2024年2月21日 |
1.0.0 | 2023年12月2日 |
0.2.1 | 2015年12月27日 |
#271 在 性能分析
每月758,991次下载
在 19 个Crates中使用(17 个直接使用)
215KB
3K SLoC
Cadence
适用于Rust的扩展Statsd客户端!
Cadence 是一种快速灵活的方法,可以从您的应用程序中发出Statsd指标。
特性
- 支持 通过UDP(或可选的Unix套接字)向Statsd发出计数器、计时器、直方图、分布、仪表、测量器和集合。
- 通过
MetricSink
特性支持替代后端。 - 支持 Datadog 风格的指标标签。
- 宏 以简化发出指标的常见调用
- 用于发送指标的一个简单而灵活的API。
安装
要在项目中使用 cadence
,请将其添加到您的 Cargo.toml
文件中的依赖项中。
[dependencies]
cadence = "x.y.z"
这就足够了!
用法
以下展示了Cadence的一些使用示例。这些示例从简单开始,逐步展示如何在生产应用程序中使用Cadence。
简单使用
以下展示了Cadence的简单使用方法。在这个例子中,我们仅导入客户端,创建一个将写入某个虚拟指标服务器的实例,并发送一些指标。
use std::net::UdpSocket;
use cadence::prelude::*;
use cadence::{StatsdClient, UdpMetricSink, DEFAULT_PORT};
// Create client that will write to the given host over UDP.
//
// Note that you'll probably want to actually handle any errors creating
// the client when you use it for real in your application. We're just
// using .unwrap() here since this is an example!
let host = ("metrics.example.com", DEFAULT_PORT);
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let sink = UdpMetricSink::from(host, socket).unwrap();
let client = StatsdClient::from_sink("my.metrics", sink);
// Emit metrics!
client.incr("some.counter");
client.time("some.methodCall", 42);
client.gauge("some.thing", 7);
client.meter("some.value", 5);
缓冲UDP接收器
虽然通过UDP发送指标非常快,但频繁网络调用的开销可能会开始累积。这尤其适用于您正在编写一个高性能应用程序,该应用程序发出大量指标。
为确保指标不会干扰您应用程序的性能,您可能需要使用一个在发送前先缓冲多个指标的单个网络操作的MetricSink
实现。为此,有BufferedUdpMetricSink
。下面给出了使用此接收器的示例。
use std::net::UdpSocket;
use cadence::prelude::*;
use cadence::{StatsdClient, BufferedUdpMetricSink, DEFAULT_PORT};
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
socket.set_nonblocking(true).unwrap();
let host = ("metrics.example.com", DEFAULT_PORT);
let sink = BufferedUdpMetricSink::from(host, socket).unwrap();
let client = StatsdClient::from_sink("my.prefix", sink);
client.count("my.counter.thing", 29);
client.time("my.service.call", 214);
client.incr("some.event");
如您所见,使用此缓冲UDP接收器并不比使用常规的非缓冲UDP接收器复杂。
此接收器的唯一缺点是,直到缓冲区满,指标不会写入Statsd服务器。如果您有一个不断发出指标的繁忙应用程序,这不应该是个问题。然而,如果您的应用程序只是偶尔发出指标,这个接收器可能会导致指标延迟一段时间,直到缓冲区填满。在这种情况下,使用UdpMetricSink
可能更有意义,因为它不做任何缓冲。
异步指标接收器队列
为确保发出指标不会干扰您应用程序的性能(尽管发出指标通常非常快),确保指标在应用程序线程之外的不同线程中发出可能是个好主意。
为此,有QueuingMetricSink
。这个接收器允许您将任何其他指标接收器包装起来,并通过队列将其发送到它,因为它在另一个线程中异步发出指标,与应用程序流程不同。
包装接收器的需求是它是线程安全的,这意味着它实现了Send
和Sync
特性。如果您在使用来自Cadence的另一个接收器与QueuingMetricSink
,您不必担心:它们都是线程安全的。
下面给出了使用QueuingMetricSink
包装缓冲UDP指标接收器的示例。这是在生产环境中使用Cadence的首选方式。
use std::net::UdpSocket;
use cadence::prelude::*;
use cadence::{StatsdClient, QueuingMetricSink, BufferedUdpMetricSink, DEFAULT_PORT};
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
socket.set_nonblocking(true).unwrap();
let host = ("metrics.example.com", DEFAULT_PORT);
let udp_sink = BufferedUdpMetricSink::from(host, socket).unwrap();
let queuing_sink = QueuingMetricSink::from(udp_sink);
let client = StatsdClient::from_sink("my.prefix", queuing_sink);
client.count("my.counter.thing", 29);
client.time("my.service.call", 214);
在上面的示例中,我们使用了队列接收器的默认构造函数,它创建了一个无界队列,没有最大大小,将客户端发送指标的线程连接到包装接收器运行的背景线程。如果您想创建一个有界队列并具有最大大小,您可以使用with_capacity
构造函数。下面给出了一个示例。
use std::net::UdpSocket;
use cadence::prelude::*;
use cadence::{StatsdClient, QueuingMetricSink, BufferedUdpMetricSink,
DEFAULT_PORT};
// Queue with a maximum capacity of 128K elements
const QUEUE_SIZE: usize = 128 * 1024;
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
socket.set_nonblocking(true).unwrap();
let host = ("metrics.example.com", DEFAULT_PORT);
let udp_sink = BufferedUdpMetricSink::from(host, socket).unwrap();
let queuing_sink = QueuingMetricSink::with_capacity(udp_sink, QUEUE_SIZE);
let client = StatsdClient::from_sink("my.prefix", queuing_sink);
client.count("my.counter.thing", 29);
client.time("my.service.call", 214);
client.incr("some.event");
使用具有容量设置的QueuingMetricSink
意味着当队列满时,通过StatsdClient
发出指标的尝试将失败。虽然这很糟糕,但如果您使用的是无界队列,未发送的指标会逐渐消耗更多和更多的内存,直到您的应用程序耗尽所有内存。
使用无界队列意味着指标发送可以吸收发送指标的速度减慢,直到您的应用程序耗尽内存。使用有界队列将限制发送指标在您的应用程序中使用的内存量。这是Cadence用户必须自己决定的权衡。
还可以为QueuingMetricSink
提供错误处理程序,以便在包装接收器因任何原因无法发送指标时调用。
use std::net::UdpSocket;
use cadence::prelude::*;
use cadence::{StatsdClient, QueuingMetricSink, BufferedUdpMetricSink,
DEFAULT_PORT};
// Queue with a maximum capacity of 128K elements
const QUEUE_SIZE: usize = 128 * 1024;
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
socket.set_nonblocking(true).unwrap();
let host = ("metrics.example.com", DEFAULT_PORT);
let udp_sink = BufferedUdpMetricSink::from(host, socket).unwrap();
let queuing_sink = QueuingMetricSink::builder()
.with_capacity(QUEUE_SIZE)
.with_error_handler(|e| {
eprintln!("Error while sending metrics: {:?}", e);
})
.build(udp_sink);
let client = StatsdClient::from_sink("my.prefix", queuing_sink);
client.count("my.counter.thing", 29);
client.time("my.service.call", 214);
使用带标签的
通过使用Cadence StatsdClient
结构中的每个_with_tags
方法添加指标标签。下面给出了使用这些方法的示例。请注意,标签是Statsd协议的扩展,因此可能不是所有服务器都支持。
有关更多信息,请参阅Datadog文档。
use cadence::prelude::*;
use cadence::{Metric, StatsdClient, NopMetricSink};
let client = StatsdClient::from_sink("my.prefix", NopMetricSink);
let res = client.count_with_tags("my.counter", 29)
.with_tag("host", "web03.example.com")
.with_tag_value("beta-test")
.try_send();
assert_eq!(
concat!(
"my.prefix.my.counter:29|c|#",
"host:web03.example.com,",
"beta-test"
),
res.unwrap().as_metric_str()
);
默认标签
可以使用构建器在构建时向StatsdClient
添加默认标签。默认标签会添加到StatsdClient
发出的每个指标中,无需在构建客户端后进行任何额外操作。请注意,标签是Statsd协议的扩展,因此可能不是所有服务器都支持。
有关更多信息,请参阅Datadog文档。
use cadence::prelude::*;
use cadence::{Metric, StatsdClient, NopMetricSink};
let client = StatsdClient::builder("my.prefix", NopMetricSink)
.with_tag("env", "prod")
.with_tag("app", "auth")
.build();
let res = client.count_with_tags("my.counter", 29)
.with_tag("host", "web03.example.com")
.with_tag_value("beta-test")
.try_send();
assert_eq!(
concat!(
"my.prefix.my.counter:29|c|#",
"env:prod,",
"app:auth,",
"host:web03.example.com,",
"beta-test"
),
res.unwrap().as_metric_str()
);
值打包
值打包允许将多个值作为单个指标发送到直方图、分布和计时器类型。Cadence客户端接受直方图、分布和计时器方法的Vec<T>
,并将多个值格式化为以下描述。请注意,此功能是Datadog扩展,因此可能不支持您的服务器。它由Datadog代理的版本>=v6.25.0 && <v7.0.0
或>=v7.25.0
支持。
打包指标具有以下格式
<METRIC_NAME>:<VALUE1>:<VALUE2>:<VALUE3>|<TYPE>|#<TAG_KEY_1>:<TAG_VALUE_1>,<TAG_2>`
有关更多信息,请参阅Datadog 文档。
use cadence::prelude::*;
use cadence::{Metric, StatsdClient, NopMetricSink};
let client = StatsdClient::from_sink("my.prefix", NopMetricSink);
let res = client.distribution_with_tags("my.distribution", vec![29, 30, 31, 32])
.with_tag("host", "web03.example.com")
.with_tag_value("beta-test")
.try_send();
assert_eq!(
concat!(
"my.prefix.my.distribution:29:30:31:32|d|#",
"host:web03.example.com,",
"beta-test"
),
res.unwrap().as_metric_str()
);
实现的特性
Cadence StatsdClient
结构体使用的每个发送指标的方法都实现为特性。还有一个特性将所有这些其他特性组合在一起。如果我们愿意,我们可以只使用其中一个特性类型来引用客户端实例。如果您想在单元测试代码时用模拟版本替换实际Cadence客户端,或者想将客户端使用的所有实现细节抽象化,这可能会很有用。
这些特性都导出在预定义模块中。它们也在主模块中可用,但通常不会这样使用。
use cadence::prelude::*;
use cadence::{StatsdClient, UdpMetricSink, DEFAULT_PORT};
pub struct User {
id: u64,
username: String,
email: String
}
// Here's a simple DAO (Data Access Object) that doesn't do anything but
// uses a metric client to keep track of the number of times the
// 'getUserById' method gets called.
pub struct MyUserDao {
metrics: Box<dyn MetricClient>
}
impl MyUserDao {
// Create a new instance that will use the StatsdClient
pub fn new<T: MetricClient + 'static>(metrics: T) -> MyUserDao {
MyUserDao { metrics: Box::new(metrics) }
}
/// Get a new user by their ID
pub fn get_user_by_id(&self, id: u64) -> Option<User> {
self.metrics.incr("getUserById");
None
}
}
// Create a new Statsd client that writes to "metrics.example.com"
let host = ("metrics.example.com", DEFAULT_PORT);
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let sink = UdpMetricSink::from(host, socket).unwrap();
let metrics = StatsdClient::from_sink("counter.example", sink);
// Create a new instance of the DAO that will use the client
let dao = MyUserDao::new(metrics);
// Try to lookup a user by ID!
match dao.get_user_by_id(123) {
Some(u) => println!("Found a user!"),
None => println!("No user!")
};
静默指标发送和错误处理
在发送指标时,有时您并不关心尝试发送它的结果,或者您可能只是不想与代码的其他部分一起处理它。为了处理这种情况,Cadence允许您设置默认的错误处理器。当发送指标出现错误时,将调用此处理器,以便调用代码不必处理它们。
以下提供了一个配置错误处理器及其可能被调用的示例。
use cadence::prelude::*;
use cadence::{MetricError, StatsdClient, NopMetricSink};
fn my_error_handler(err: MetricError) {
println!("Metric error! {}", err);
}
let client = StatsdClient::builder("prefix", NopMetricSink)
.with_error_handler(my_error_handler)
.build();
// When sending metrics via the `MetricBuilder` used for assembling tags,
// callers may opt into sending metrics quietly via the `.send()` method
// as opposed to the `.try_send()` method
client.count_with_tags("some.counter", 42)
.with_tag("region", "us-east-2")
.send();
自定义指标接收器
Cadence StatsdClient
使用MetricSink
特性的实现将指标发送到指标服务器。Cadence库的大多数用户可能想使用包装BufferedMetricSink
实例的QueuingMetricSink。
但是,也许您想做一些现有接收器不支持的事情。以下是一个创建自定义接收器的示例。
use std::io;
use cadence::prelude::*;
use cadence::{StatsdClient, MetricSink, DEFAULT_PORT};
pub struct MyMetricSink;
impl MetricSink for MyMetricSink {
fn emit(&self, metric: &str) -> io::Result<usize> {
// Your custom metric sink implementation goes here!
Ok(0)
}
}
let sink = MyMetricSink;
let client = StatsdClient::from_sink("my.prefix", sink);
client.count("my.counter.thing", 42);
client.time("my.method.time", 25);
client.incr("some.other.counter");
自定义UDP套接字
大多数Cadence StatsdClient
用户将使用它通过UDP套接字发送指标。如果您需要自定义套接字,例如您想使用阻塞模式的套接字但设置写入超时,您可以像以下演示的那样做。
use std::net::UdpSocket;
use std::time::Duration;
use cadence::prelude::*;
use cadence::{StatsdClient, UdpMetricSink, DEFAULT_PORT};
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
socket.set_write_timeout(Some(Duration::from_millis(1))).unwrap();
let host = ("metrics.example.com", DEFAULT_PORT);
let sink = UdpMetricSink::from(host, socket).unwrap();
let client = StatsdClient::from_sink("my.prefix", sink);
client.count("my.counter.thing", 29);
client.time("my.service.call", 214);
client.incr("some.event");
client.set("users.uniques", 42);
Unix套接字
Cadence还支持使用UnixMetricSink
或BufferedUnixMetricSink
与Unix数据报套接字一起使用。Unix套接字可用于将指标发送到与您的应用程序在同一台机器(物理机器、虚拟机、Pod中的容器)上运行的服务器或代理。Unix套接字与UDP套接字在几个重要方面类似
- 在不存在或不被监听的套接字上发送指标会导致错误。
- 在已连接的套接字上发送的指标保证将被投递(即它们是可靠的,而不是UDP套接字)。然而,由于各种环境和服务器特定的原因,指标仍然可能不会被服务器读取。
以下提供了一个使用接收器的示例。
use std::os::unix::net::UnixDatagram;
use cadence::prelude::*;
use cadence::{StatsdClient, BufferedUnixMetricSink};
let socket = UnixDatagram::unbound().unwrap();
socket.set_nonblocking(true).unwrap();
let sink = BufferedUnixMetricSink::from("/run/statsd.sock", socket);
let client = StatsdClient::from_sink("my.prefix", sink);
client.count("my.counter.thing", 29);
client.time("my.service.call", 214);
client.incr("some.event");
client.set("users.uniques", 42);
注意:此功能仅在Unix平台上(Linux、BSD、MacOS)可用。
其他
有关Cadence的更多信息,请参阅仓库根目录中的README。
依赖项
~345KB