Skip to content

Commit af245c2

Browse files
committed
fix proxy mode
1 parent 358257c commit af245c2

7 files changed

Lines changed: 223 additions & 64 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
**Internal**:
1010

1111
- Require timestamps and verification in auth signatures. ([#6069](https://github.com/getsentry/relay/pull/6069))
12+
- Internally handle outcomes as metrics. ([#6107](https://github.com/getsentry/relay/pull/6107))
1213
- Have relay generate metric billing outcomes. ([#6066](https://github.com/getsentry/relay/pull/6066))
1314
- Update sentry-conventions to 0.12.0.
1415

relay-server/src/service.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use crate::services::metrics::RouterService;
1717
use crate::services::objectstore::Objectstore;
1818
#[cfg(feature = "processing")]
1919
use crate::services::objectstore::ObjectstoreService;
20-
use crate::services::outcome::{OutcomeProducerService, TrackOutcome};
20+
use crate::services::outcome::{
21+
ClientReportOutcomeProducerService, NullOutcomeProducerService, OutcomeProducerService,
22+
TrackOutcome,
23+
};
2124
use crate::services::processor::{
2225
self, EnvelopeProcessor, EnvelopeProcessorService, EnvelopeProcessorServicePool,
2326
};
@@ -37,7 +40,7 @@ use anyhow::Result;
3740
use axum::extract::FromRequestParts;
3841
use axum::http::request::Parts;
3942
use relay_cogs::Cogs;
40-
use relay_config::Config;
43+
use relay_config::{Config, EmitOutcomes, RelayMode};
4144
#[cfg(feature = "processing")]
4245
use relay_config::{RedisConfigRef, RedisConfigsRef};
4346
#[cfg(feature = "processing")]
@@ -192,16 +195,25 @@ impl ServiceState {
192195
// Create an address for the `EnvelopeProcessor`, which can be injected into the
193196
// other services.
194197
let (processor, processor_rx) = match config.relay_mode() {
195-
relay_config::RelayMode::Proxy => channel(ProxyProcessorService::name()),
196-
relay_config::RelayMode::Managed => channel(EnvelopeProcessorService::name()),
198+
RelayMode::Proxy => channel(ProxyProcessorService::name()),
199+
RelayMode::Managed => channel(EnvelopeProcessorService::name()),
197200
};
198201

199202
let (aggregator, aggregator_rx) = channel(RouterService::name());
200203

201-
let outcome_aggregator = services.start(OutcomeProducerService::new(
202-
Arc::clone(&config),
203-
aggregator.clone(),
204-
));
204+
let outcome_aggregator = match config.emit_outcomes() {
205+
EmitOutcomes::None => services.start(NullOutcomeProducerService::new()),
206+
_ => match config.relay_mode() {
207+
RelayMode::Proxy => services.start(ClientReportOutcomeProducerService::new(
208+
&config,
209+
processor.clone(),
210+
)),
211+
RelayMode::Managed => services.start(OutcomeProducerService::new(
212+
Arc::clone(&config),
213+
aggregator.clone(),
214+
)),
215+
},
216+
};
205217

206218
let (global_config, global_config_rx) =
207219
GlobalConfigService::new(config.clone(), upstream_relay.clone());
@@ -261,7 +273,7 @@ impl ServiceState {
261273
);
262274

263275
let (processor_pool, aggregator_handle, autoscaling) = match config.relay_mode() {
264-
relay_config::RelayMode::Proxy => {
276+
RelayMode::Proxy => {
265277
services.start_with(
266278
ProxyProcessorService::new(
267279
config.clone(),
@@ -275,7 +287,7 @@ impl ServiceState {
275287
);
276288
(None, None, None)
277289
}
278-
relay_config::RelayMode::Managed => {
290+
RelayMode::Managed => {
279291
let processor_pool = create_processor_pool(&config)?;
280292

281293
let router = RouterService::new(
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use std::time::Duration;
2+
3+
use crate::services::outcome::{DiscardReason, Outcome, TrackOutcome};
4+
use crate::services::processor::{EnvelopeProcessor, SubmitClientReports};
5+
use crate::utils::SleepHandle;
6+
use hashbrown::HashMap;
7+
use relay_config::Config;
8+
use relay_event_schema::protocol::{ClientReport, DiscardedEvent};
9+
use relay_metrics::{MetricNamespace, UnixTimestamp};
10+
use relay_quotas::Scoping;
11+
use relay_system::{Addr, Service};
12+
13+
/// Service implementing the [`TrackOutcome`] interface only emitting [`ClientReport`]s.
14+
///
15+
/// This implementation is only useful for Relay instances running in Proxy mode.
16+
#[derive(Debug)]
17+
pub struct ClientReportOutcomeProducerService {
18+
processor: Addr<EnvelopeProcessor>,
19+
buckets: HashMap<BucketKey, ClientReport>,
20+
bucket_interval: u32,
21+
flush_interval: u32,
22+
flush_handle: SleepHandle,
23+
}
24+
25+
impl ClientReportOutcomeProducerService {
26+
pub fn new(config: &Config, processor: Addr<EnvelopeProcessor>) -> Self {
27+
let agg = &config
28+
.aggregator_config_for(MetricNamespace::Outcomes)
29+
.aggregator;
30+
31+
Self {
32+
processor,
33+
buckets: Default::default(),
34+
bucket_interval: agg.bucket_interval,
35+
flush_interval: agg.initial_delay,
36+
flush_handle: SleepHandle::idle(),
37+
}
38+
}
39+
40+
fn handle_message(&mut self, msg: TrackOutcome) {
41+
let offset =
42+
u64::try_from(msg.timestamp.timestamp()).unwrap_or(0) / u64::from(self.bucket_interval);
43+
44+
let bucket_key = BucketKey {
45+
offset,
46+
scoping: msg.scoping,
47+
};
48+
49+
let discarded_events: fn(&mut ClientReport) -> &mut _ = match msg.outcome {
50+
Outcome::Filtered(_) => |cr| &mut cr.filtered_events,
51+
Outcome::FilteredSampling(_) => |cr| &mut cr.filtered_sampling_events,
52+
Outcome::RateLimited(_) => |cr| &mut cr.rate_limited_events,
53+
Outcome::Invalid(DiscardReason::InvalidSignature | DiscardReason::MissingSignature) => {
54+
|cr| &mut cr.discarded_events
55+
}
56+
_ => {
57+
relay_log::debug!(
58+
"Outcome '{}' cannot be converted to client report",
59+
msg.outcome
60+
);
61+
return;
62+
}
63+
};
64+
65+
let client_report = self.buckets.entry(bucket_key).or_default();
66+
67+
discarded_events(client_report).push(DiscardedEvent {
68+
reason: msg.outcome.to_reason().unwrap_or_default().to_string(),
69+
category: msg.category,
70+
quantity: msg.quantity,
71+
});
72+
73+
match self.flush_interval {
74+
0 => self.do_flush(),
75+
v => self.flush_handle.set_if_idle(Duration::from_secs(v.into())),
76+
}
77+
}
78+
79+
fn do_flush(&mut self) {
80+
for (bucket_key, client_report) in self.buckets.drain() {
81+
let BucketKey { offset, scoping } = bucket_key;
82+
83+
let timestamp = Some(offset)
84+
// May be zero as we default timestamps out of range to 0.
85+
.filter(|offset| *offset > 0)
86+
.map(|offset| offset * u64::from(self.bucket_interval))
87+
.map(UnixTimestamp::from_secs);
88+
89+
let client_report = ClientReport {
90+
timestamp,
91+
..client_report
92+
};
93+
94+
self.processor.send(SubmitClientReports {
95+
client_reports: vec![client_report],
96+
scoping,
97+
});
98+
}
99+
}
100+
101+
fn handle_shutdown(&mut self) {
102+
self.flush_interval = 0;
103+
self.do_flush();
104+
}
105+
}
106+
107+
impl Service for ClientReportOutcomeProducerService {
108+
type Interface = TrackOutcome;
109+
110+
async fn run(mut self, mut rx: relay_system::Receiver<Self::Interface>) {
111+
let mut shutdown = relay_system::Controller::shutdown_handle();
112+
relay_log::info!("client report outcome producer started");
113+
114+
loop {
115+
tokio::select! {
116+
biased;
117+
118+
() = &mut self.flush_handle => self.do_flush(),
119+
Some(message) = rx.recv() => self.handle_message(message),
120+
_ = shutdown.notified() => self.handle_shutdown(),
121+
122+
else => break,
123+
}
124+
}
125+
self.do_flush();
126+
relay_log::info!("client report outcome producer stopped");
127+
}
128+
}
129+
130+
/// Contains everything to construct a `TrackOutcome`, except quantity
131+
#[derive(Debug, PartialEq, Eq, Hash)]
132+
struct BucketKey {
133+
/// The time slot for which outcomes are aggregated.
134+
///
135+
/// The offset follows the formula: `timestamp = offset * bucket_interval`.
136+
offset: u64,
137+
/// Scoping of the outcome.
138+
scoping: Scoping,
139+
}

relay-server/src/services/outcome/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ use relay_quotas::ReasonCode;
99
use relay_sampling::config::RuleId;
1010
use relay_sampling::evaluation::MatchedRuleIds;
1111

12+
mod client_report;
1213
pub mod metric;
1314
mod service;
1415

1516
use crate::envelope::{AttachmentType, ItemType};
1617

18+
pub use self::client_report::*;
1719
pub use self::service::*;
1820

1921
/// The numerical identifier of the outcome category (Accepted, Filtered, ...)

relay-server/src/services/outcome/service.rs

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@
77
use std::net::IpAddr;
88
use std::sync::Arc;
99

10-
use crate::services::metrics::{Aggregator, MergeBuckets};
11-
use crate::services::outcome::{self, Outcome};
12-
use crate::statsd::RelayCounters;
1310
use chrono::{DateTime, Utc};
14-
use relay_config::{Config, EmitOutcomes};
11+
use relay_config::Config;
1512
use relay_event_schema::protocol::EventId;
1613
use relay_quotas::{DataCategory, Scoping};
1714
use relay_statsd::metric;
1815
use relay_system::{Addr, FromMessage, Interface, NoResponse, Service};
1916

17+
use crate::services::metrics::{Aggregator, MergeBuckets};
18+
use crate::services::outcome::{self, Outcome};
19+
use crate::statsd::RelayCounters;
20+
2021
/// Tracks an [`Outcome`] of an Envelope item.
2122
///
2223
/// See the module level documentation for more information.
@@ -48,62 +49,56 @@ impl FromMessage<Self> for TrackOutcome {
4849
}
4950
}
5051

52+
/// Service implementing the [`TrackOutcome`] interface, dropping all outcomes.
5153
#[derive(Debug)]
52-
enum OutcomeProducer {
53-
Metric(Addr<Aggregator>),
54-
Disabled,
54+
pub struct NullOutcomeProducerService {
55+
_priv: (),
5556
}
5657

57-
impl OutcomeProducer {
58-
fn handle_message(&self, message: TrackOutcome, config: &Config) {
59-
match self {
60-
Self::Metric(metrics) => {
61-
send_outcome_metric(&message);
62-
metrics.send(MergeBuckets {
63-
project_key: message.scoping.project_key,
64-
buckets: vec![outcome::metric::to_metric(&message, config)],
65-
})
66-
}
67-
Self::Disabled => (),
68-
}
58+
impl NullOutcomeProducerService {
59+
pub fn new() -> Self {
60+
Self { _priv: () }
6961
}
7062
}
7163

72-
/// Service implementing the [`OutcomeProducer`] interface.
64+
impl Service for NullOutcomeProducerService {
65+
type Interface = TrackOutcome;
66+
67+
async fn run(self, mut rx: relay_system::Receiver<Self::Interface>) {
68+
while rx.recv().await.is_some() {}
69+
}
70+
}
71+
72+
/// Service implementing the [`TrackOutcome`] interface.
7373
#[derive(Debug)]
7474
pub struct OutcomeProducerService {
7575
config: Arc<Config>,
76-
producer: OutcomeProducer,
76+
aggregator: Addr<Aggregator>,
7777
}
7878

7979
impl OutcomeProducerService {
80-
pub fn new(config: Arc<Config>, metric_aggregator: Addr<Aggregator>) -> Self {
81-
let producer = match config.emit_outcomes() {
82-
EmitOutcomes::AsOutcomes | EmitOutcomes::AsClientReports => {
83-
relay_log::info!("Configured to emit outcomes via metrics");
84-
OutcomeProducer::Metric(metric_aggregator)
85-
}
86-
EmitOutcomes::None => {
87-
relay_log::info!("Configured to drop all outcomes");
88-
OutcomeProducer::Disabled
89-
}
90-
};
91-
92-
Self { config, producer }
80+
pub fn new(config: Arc<Config>, aggregator: Addr<Aggregator>) -> Self {
81+
Self { config, aggregator }
82+
}
83+
84+
fn handle_message(&self, message: TrackOutcome) {
85+
send_outcome_metric(&message);
86+
self.aggregator.send(MergeBuckets {
87+
project_key: message.scoping.project_key,
88+
buckets: vec![outcome::metric::to_metric(&message, &self.config)],
89+
})
9390
}
9491
}
9592

9693
impl Service for OutcomeProducerService {
9794
type Interface = TrackOutcome;
9895

9996
async fn run(self, mut rx: relay_system::Receiver<Self::Interface>) {
100-
let Self { config, producer } = self;
101-
102-
relay_log::info!("OutcomeProducer started.");
97+
relay_log::info!("outcome producer started.");
10398
while let Some(message) = rx.recv().await {
104-
producer.handle_message(message, &config);
99+
self.handle_message(message);
105100
}
106-
relay_log::info!("OutcomeProducer stopped.");
101+
relay_log::info!("outcome producer stopped.");
107102
}
108103
}
109104

relay-server/src/utils/sleep_handle.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use std::pin::Pin;
33
use std::task::Poll;
44
use std::time::Duration;
55

6+
use futures::FutureExt;
7+
use futures::future::FusedFuture;
8+
69
/// A future wrapper around [`tokio::time::Sleep`].
710
///
811
/// When initialized with [`SleepHandle::idle`], this future is pending indefinitely every time it
@@ -28,7 +31,14 @@ impl SleepHandle {
2831
self.0 = Some(Box::pin(tokio::time::sleep(duration)));
2932
}
3033

31-
/// Checks wether the internal state is currently pending indefinite.
34+
/// Primes the internal state like [`Self::set`], only if currently [`Self::idle`].
35+
pub fn set_if_idle(&mut self, duration: Duration) {
36+
if self.is_idle() {
37+
self.set(duration)
38+
}
39+
}
40+
41+
/// Checks whether the internal state is currently pending indefinite.
3242
pub fn is_idle(&self) -> bool {
3343
self.0.is_none()
3444
}
@@ -39,7 +49,7 @@ impl Future for SleepHandle {
3949

4050
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
4151
let poll = match &mut self.0 {
42-
Some(sleep) => Pin::new(sleep).poll(cx),
52+
Some(sleep) => sleep.poll_unpin(cx),
4353
None => Poll::Pending,
4454
};
4555

@@ -50,3 +60,10 @@ impl Future for SleepHandle {
5060
poll
5161
}
5262
}
63+
64+
impl FusedFuture for SleepHandle {
65+
fn is_terminated(&self) -> bool {
66+
// The handle never terminates, it may be reset or primed at any time.
67+
false
68+
}
69+
}

0 commit comments

Comments
 (0)