Skip to content

Commit

Permalink
Merge branch 'feat/performance' into ref/tracing-perf
Browse files Browse the repository at this point in the history
  • Loading branch information
Swatinem committed Jan 5, 2022
2 parents 14a7238 + 3e73284 commit a5f2bcb
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 20 deletions.
34 changes: 34 additions & 0 deletions sentry-core/src/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,40 @@ impl TransactionContext {
}
}

/// Creates a new Transaction Context based on an existing Span.
///
/// This should be used when an independent computation is spawned on another
/// thread and should be connected to the calling thread via a distributed
/// tracing transaction.
pub fn continue_from_span(name: &str, op: &str, span: Option<TransactionOrSpan>) -> Self {
let span = match span {
Some(span) => span,
None => return Self::new(name, op),
};

let (trace_id, parent_span_id, sampled) = match span {
TransactionOrSpan::Transaction(transaction) => {
let inner = transaction.inner.lock().unwrap();
(
inner.context.trace_id,
inner.context.span_id,
Some(inner.sampled),
)
}
TransactionOrSpan::Span(span) => {
(span.span.trace_id, span.span.span_id, Some(span.sampled))
}
};

Self {
name: name.into(),
op: op.into(),
trace_id,
parent_span_id: Some(parent_span_id),
sampled,
}
}

/// Set the sampling decision for this Transaction.
///
/// This can be either an explicit boolean flag, or [`None`], which will fall
Expand Down
34 changes: 34 additions & 0 deletions sentry-types/src/protocol/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,40 @@ impl Envelope {
.next()
}

/// Filters the Envelope's [`EnvelopeItem`]s based on a predicate,
/// and returns a new Envelope containing only the filtered items.
///
/// Retains the [`EnvelopeItem`]s for which the predicate returns `true`.
/// Additionally, [`EnvelopeItem::Attachment`]s are only kept if the Envelope
/// contains an [`EnvelopeItem::Event`] or [`EnvelopeItem::Transaction`].
///
/// [`None`] is returned if no items remain in the Envelope after filtering.
pub fn filter<P>(self, mut predicate: P) -> Option<Self>
where
P: FnMut(&EnvelopeItem) -> bool,
{
let mut filtered = Envelope::new();
for item in self.items {
if predicate(&item) {
filtered.add_item(item);
}
}

// filter again, removing attachments which do not make any sense without
// an event/transaction
if filtered.uuid().is_none() {
filtered
.items
.retain(|item| !matches!(item, EnvelopeItem::Attachment(..)))
}

if filtered.items.is_empty() {
None
} else {
Some(filtered)
}
}

/// Serialize the Envelope into the given [`Write`].
///
/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
Expand Down
16 changes: 6 additions & 10 deletions sentry/examples/performance-demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,13 @@ fn main_span1() {
wrap_in_span("span1", "", || {
thread::sleep(Duration::from_millis(50));

let headers = match sentry::configure_scope(|scope| scope.get_span()) {
Some(span) => vec![span.iter_headers().next().unwrap()],
None => vec![],
};
let transaction_ctx = sentry::TransactionContext::continue_from_span(
"background transaction",
"root span",
sentry::configure_scope(|scope| scope.get_span()),
);
thread::spawn(move || {
let transaction =
sentry::start_transaction(sentry::TransactionContext::continue_from_headers(
"background transaction",
"root span",
headers.iter().map(|(k, v)| (*k, v.as_str())),
));
let transaction = sentry::start_transaction(transaction_ctx);
sentry::configure_scope(|scope| scope.set_span(Some(transaction.clone().into())));

thread::sleep(Duration::from_millis(50));
Expand Down
38 changes: 35 additions & 3 deletions sentry/src/transports/ratelimit.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use httpdate::parse_http_date;
use std::time::{Duration, SystemTime};

// TODO: maybe move this someplace where we can filter an `Envelope`s items.
use crate::protocol::EnvelopeItem;
use crate::Envelope;

/// A Utility that helps with rate limiting sentry requests.
#[derive(Debug, Default)]
Expand All @@ -10,6 +11,7 @@ pub struct RateLimiter {
error: Option<SystemTime>,
session: Option<SystemTime>,
transaction: Option<SystemTime>,
attachment: Option<SystemTime>,
}

impl RateLimiter {
Expand Down Expand Up @@ -55,6 +57,7 @@ impl RateLimiter {
"error" => self.error = new_time,
"session" => self.session = new_time,
"transaction" => self.transaction = new_time,
"attachment" => self.attachment = new_time,
_ => {}
}
}
Expand All @@ -66,7 +69,10 @@ impl RateLimiter {
}
}

/// Query the RateLimiter for a certain category of event.
/// Query the RateLimiter if a certain category of event is currently rate limited.
///
/// If the given category is rate limited, it will return the remaining
/// [`Duration`] for which it is.
pub fn is_disabled(&self, category: RateLimitingCategory) -> Option<Duration> {
if let Some(ts) = self.global {
let time_left = ts.duration_since(SystemTime::now()).ok();
Expand All @@ -79,14 +85,38 @@ impl RateLimiter {
RateLimitingCategory::Error => self.error,
RateLimitingCategory::Session => self.session,
RateLimitingCategory::Transaction => self.transaction,
RateLimitingCategory::Attachment => self.attachment,
}?;
time_left.duration_since(SystemTime::now()).ok()
}

/// Query the RateLimiter for a certain category of event.
///
/// Returns `true` if the category is *not* rate limited and should be sent.
pub fn is_enabled(&self, category: RateLimitingCategory) -> bool {
self.is_disabled(category).is_none()
}

/// Filters the [`Envelope`] according to the current rate limits.
///
/// Returns [`None`] if all the envelope items were filtered out.
pub fn filter_envelope(&self, envelope: Envelope) -> Option<Envelope> {
envelope.filter(|item| {
self.is_enabled(match item {
EnvelopeItem::Event(_) => RateLimitingCategory::Error,
EnvelopeItem::SessionUpdate(_) | EnvelopeItem::SessionAggregates(_) => {
RateLimitingCategory::Session
}
EnvelopeItem::Transaction(_) => RateLimitingCategory::Transaction,
EnvelopeItem::Attachment(_) => RateLimitingCategory::Attachment,
_ => RateLimitingCategory::Any,
})
})
}
}

/// The Category of payload that a Rate Limit refers to.
#[non_exhaustive]
#[allow(dead_code)]
pub enum RateLimitingCategory {
/// Rate Limit for any kind of payload.
Any,
Expand All @@ -96,6 +126,8 @@ pub enum RateLimitingCategory {
Session,
/// Rate Limit pertaining to Transactions.
Transaction,
/// Rate Limit pertaining to Attachments.
Attachment,
}

#[cfg(test)]
Expand Down
21 changes: 14 additions & 7 deletions sentry/src/transports/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,20 @@ impl TransportThread {
};

if let Some(time_left) = rl.is_disabled(RateLimitingCategory::Any) {
sentry_debug!(
"Skipping event send because we're disabled due to rate limits for {}s",
time_left.as_secs()
);
continue;
}
rl = send(envelope, rl).await;
sentry_debug!(
"Skipping event send because we're disabled due to rate limits for {}s",
time_left.as_secs()
);
continue;
}
match rl.filter_envelope(envelope) {
Some(envelope) => {
rl = send(envelope, rl).await;
},
None => {
sentry_debug!("Envelope was discarded due to per-item rate limits");
},
};
}
})
})
Expand Down

0 comments on commit a5f2bcb

Please sign in to comment.