Skip to content

Commit f2c6410

Browse files
committed
ref: Separate cadence from metrics feature
1 parent dc5ea74 commit f2c6410

File tree

4 files changed

+101
-97
lines changed

4 files changed

+101
-97
lines changed

sentry-core/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ client = ["rand"]
2626
# and macros actually expand features (and extern crate) where they are used!
2727
debug-logs = ["dep:log"]
2828
test = ["client"]
29-
UNSTABLE_metrics = ["dep:cadence", "sentry-types/UNSTABLE_metrics"]
29+
UNSTABLE_metrics = ["sentry-types/UNSTABLE_metrics"]
30+
UNSTABLE_cadence = ["dep:cadence", "UNSTABLE_metrics"]
3031

3132
[dependencies]
3233
cadence = { version = "0.29.0", optional = true }

sentry-core/src/cadence.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::sync::Arc;
2+
3+
use cadence::MetricSink;
4+
5+
use crate::{Client, Hub};
6+
7+
/// A [`cadence`] compatible [`MetricSink`].
8+
///
9+
/// This will ingest all the emitted metrics to Sentry as well as forward them
10+
/// to the inner [`MetricSink`].
11+
#[derive(Debug)]
12+
pub struct SentryMetricSink<S> {
13+
client: Arc<Client>,
14+
sink: S,
15+
}
16+
17+
impl<S> SentryMetricSink<S> {
18+
/// Creates a new [`SentryMetricSink`], wrapping the given [`MetricSink`].
19+
pub fn try_new(sink: S) -> Result<Self, S> {
20+
let hub = Hub::current();
21+
let Some(client) = hub.client() else {
22+
return Err(sink);
23+
};
24+
25+
Ok(Self { client, sink })
26+
}
27+
}
28+
29+
impl<S> MetricSink for SentryMetricSink<S>
30+
where
31+
S: MetricSink,
32+
{
33+
fn emit(&self, metric: &str) -> std::io::Result<usize> {
34+
self.client.add_metric(metric);
35+
self.sink.emit(metric)
36+
}
37+
38+
fn flush(&self) -> std::io::Result<()> {
39+
if self.client.flush(None) {
40+
self.sink.flush()
41+
} else {
42+
Err(std::io::Error::new(
43+
std::io::ErrorKind::Other,
44+
"Flushing Client failed",
45+
))
46+
}
47+
}
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use cadence::{Counted, Distributed};
53+
use sentry_types::protocol::latest::EnvelopeItem;
54+
55+
use crate::test::with_captured_envelopes;
56+
57+
use super::*;
58+
59+
#[test]
60+
fn test_basic_metrics() {
61+
let envelopes = with_captured_envelopes(|| {
62+
let sink = SentryMetricSink::try_new(cadence::NopMetricSink).unwrap();
63+
64+
let client = cadence::StatsdClient::from_sink("sentry.test", sink);
65+
client.count("some.count", 1).unwrap();
66+
client.count("some.count", 10).unwrap();
67+
client
68+
.count_with_tags("count.with.tags", 1)
69+
.with_tag("foo", "bar")
70+
.send();
71+
client.distribution("some.distr", 1).unwrap();
72+
client.distribution("some.distr", 2).unwrap();
73+
client.distribution("some.distr", 3).unwrap();
74+
});
75+
assert_eq!(envelopes.len(), 1);
76+
77+
let mut items = envelopes[0].items();
78+
let Some(EnvelopeItem::Metrics(metrics)) = items.next() else {
79+
panic!("expected metrics");
80+
};
81+
let metrics = std::str::from_utf8(metrics).unwrap();
82+
83+
println!("{metrics}");
84+
85+
assert!(metrics.contains("sentry.test.count.with.tags:1|c|#foo:bar|T"));
86+
assert!(metrics.contains("sentry.test.some.count:11|c|T"));
87+
assert!(metrics.contains("sentry.test.some.distr:1:2:3|d|T"));
88+
assert_eq!(items.next(), None);
89+
}
90+
}

sentry-core/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ pub use crate::performance::*;
136136
pub use crate::scope::{Scope, ScopeGuard};
137137
pub use crate::transport::{Transport, TransportFactory};
138138

139+
#[cfg(all(feature = "client", feature = "UNSTABLE_cadence"))]
140+
mod cadence;
139141
// client feature
140142
#[cfg(feature = "client")]
141143
mod client;
@@ -145,10 +147,10 @@ mod hub_impl;
145147
mod metrics;
146148
#[cfg(feature = "client")]
147149
mod session;
150+
#[cfg(all(feature = "client", feature = "UNSTABLE_cadence"))]
151+
pub use crate::cadence::SentryMetricSink;
148152
#[cfg(feature = "client")]
149153
pub use crate::client::Client;
150-
#[cfg(all(feature = "client", feature = "UNSTABLE_metrics"))]
151-
pub use crate::metrics::SentryMetricSink;
152154

153155
// test utilities
154156
#[cfg(feature = "test")]

sentry-core/src/metrics.rs

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,13 @@
11
use std::collections::btree_map::Entry;
22
use std::collections::{BTreeMap, BTreeSet};
33
use std::fmt;
4-
use std::sync::mpsc::{sync_channel, Receiver, RecvTimeoutError, SyncSender};
5-
use std::sync::Arc;
4+
use std::sync::mpsc;
65
use std::thread::{self, JoinHandle};
76
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
87

9-
use cadence::MetricSink;
108
use sentry_types::protocol::latest::{Envelope, EnvelopeItem};
119

1210
use crate::client::TransportArc;
13-
use crate::{Client, Hub};
14-
15-
/// A [`cadence`] compatible [`MetricSink`].
16-
///
17-
/// This will ingest all the emitted metrics to Sentry as well as forward them
18-
/// to the inner [`MetricSink`].
19-
#[derive(Debug)]
20-
pub struct SentryMetricSink<S> {
21-
client: Arc<Client>,
22-
sink: S,
23-
}
24-
25-
impl<S> SentryMetricSink<S> {
26-
/// Creates a new [`SentryMetricSink`], wrapping the given [`MetricSink`].
27-
pub fn try_new(sink: S) -> Result<Self, S> {
28-
let hub = Hub::current();
29-
let Some(client) = hub.client() else {
30-
return Err(sink);
31-
};
32-
33-
Ok(Self { client, sink })
34-
}
35-
}
36-
37-
impl<S> MetricSink for SentryMetricSink<S>
38-
where
39-
S: MetricSink,
40-
{
41-
fn emit(&self, metric: &str) -> std::io::Result<usize> {
42-
self.client.add_metric(metric);
43-
self.sink.emit(metric)
44-
}
45-
46-
fn flush(&self) -> std::io::Result<()> {
47-
if self.client.flush(None) {
48-
self.sink.flush()
49-
} else {
50-
Err(std::io::Error::new(
51-
std::io::ErrorKind::Other,
52-
"Flushing Client failed",
53-
))
54-
}
55-
}
56-
}
5711

5812
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
5913
enum MetricType {
@@ -166,7 +120,7 @@ enum Task {
166120
}
167121

168122
pub struct MetricAggregator {
169-
sender: SyncSender<Task>,
123+
sender: mpsc::SyncSender<Task>,
170124
handle: Option<JoinHandle<()>>,
171125
}
172126

@@ -175,7 +129,7 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(10);
175129

176130
impl MetricAggregator {
177131
pub fn new(transport: TransportArc) -> Self {
178-
let (sender, receiver) = sync_channel(30);
132+
let (sender, receiver) = mpsc::sync_channel(30);
179133
let handle = thread::Builder::new()
180134
.name("sentry-metrics".into())
181135
.spawn(move || Self::worker_thread(receiver, transport))
@@ -240,15 +194,15 @@ impl MetricAggregator {
240194
let _ = self.sender.send(Task::Flush);
241195
}
242196

243-
fn worker_thread(receiver: Receiver<Task>, transport: TransportArc) {
197+
fn worker_thread(receiver: mpsc::Receiver<Task>, transport: TransportArc) {
244198
let mut buckets = AggregateMetrics::new();
245199
let mut last_flush = Instant::now();
246200

247201
loop {
248202
let timeout = FLUSH_INTERVAL.saturating_sub(last_flush.elapsed());
249203

250204
match receiver.recv_timeout(timeout) {
251-
Err(RecvTimeoutError::Timeout) | Ok(Task::Flush) => {
205+
Err(_) | Ok(Task::Flush) => {
252206
// flush
253207
Self::flush_buckets(std::mem::take(&mut buckets), &transport);
254208
last_flush = Instant::now();
@@ -343,46 +297,3 @@ impl Drop for MetricAggregator {
343297
}
344298
}
345299
}
346-
347-
#[cfg(test)]
348-
mod tests {
349-
use std::str::from_utf8;
350-
351-
use cadence::{Counted, Distributed};
352-
353-
use crate::test::with_captured_envelopes;
354-
355-
use super::*;
356-
357-
#[test]
358-
fn test_basic_metrics() {
359-
let envelopes = with_captured_envelopes(|| {
360-
let sink = SentryMetricSink::try_new(cadence::NopMetricSink).unwrap();
361-
362-
let client = cadence::StatsdClient::from_sink("sentry.test", sink);
363-
client.count("some.count", 1).unwrap();
364-
client.count("some.count", 10).unwrap();
365-
client
366-
.count_with_tags("count.with.tags", 1)
367-
.with_tag("foo", "bar")
368-
.send();
369-
client.distribution("some.distr", 1).unwrap();
370-
client.distribution("some.distr", 2).unwrap();
371-
client.distribution("some.distr", 3).unwrap();
372-
});
373-
assert_eq!(envelopes.len(), 1);
374-
375-
let mut items = envelopes[0].items();
376-
let Some(EnvelopeItem::Metrics(metrics)) = items.next() else {
377-
panic!("expected metrics");
378-
};
379-
let metrics = from_utf8(metrics).unwrap();
380-
381-
println!("{metrics}");
382-
383-
assert!(metrics.contains("sentry.test.count.with.tags:1|c|#foo:bar|T"));
384-
assert!(metrics.contains("sentry.test.some.count:11|c|T"));
385-
assert!(metrics.contains("sentry.test.some.distr:1:2:3|d|T"));
386-
assert_eq!(items.next(), None);
387-
}
388-
}

0 commit comments

Comments
 (0)