relay_cogs/
lib.rs

1//! Break down the cost of Relay by its components and individual features it handles.
2//!
3//! Relay is a one stop shop for all different kinds of events Sentry supports, Errors,
4//! Performance, Metrics, Replays, Crons and more. A single shared resource.
5//!
6//! This module intends to make it possible to give insights how much time and resources
7//! Relay spends processing individual features. The measurements collected can be later used
8//! for increased observability and accounting purposes.
9//!
10//! `relay-cogs` provides a way to give an answer to the questions:
11//!  - What portion of Relay's costs can be attributed to feature X?
12//!  - How much does feature X cost?
13//!
14//! ## Collecting COGs Measurements
15//!
16//! Measurements are collected through [`Cogs`] which attributes the measurement to either a single
17//! or to multiple different [app features](AppFeature) belonging to a [resource](ResourceId).
18//!
19//! Collected and [attributed measurements](CogsMeasurement) then are recorded by a [`CogsRecorder`].
20//!
21//! ```
22//! use relay_cogs::{AppFeature, Cogs, FeatureWeights, ResourceId};
23//!
24//! enum Message {
25//!     Span,
26//!     Transaction,
27//!     TransactionWithSpans { num_spans: usize },
28//! }
29//!
30//! struct Processor {
31//!     cogs: Cogs
32//! }
33//!
34//! impl From<&Message> for FeatureWeights {
35//!     fn from(value: &Message) -> Self {
36//!         match value {
37//!             Message::Span => FeatureWeights::new(AppFeature::Spans),
38//!             Message::Transaction => FeatureWeights::new(AppFeature::Transactions),
39//!             Message::TransactionWithSpans { num_spans } => FeatureWeights::builder()
40//!                 .weight(AppFeature::Spans, *num_spans)
41//!                 .weight(AppFeature::Transactions, 1)
42//!                 .build(),
43//!         }
44//!     }
45//! }
46//!
47//! impl Processor {
48//!     fn handle_message(&self, mut message: Message) {
49//!         let _cogs = self.cogs.timed(ResourceId::Relay, &message);
50//!
51//!         self.step1(&mut message);
52//!         self.step2(&mut message);
53//!
54//!         // Measurement automatically recorded here.
55//!     }
56//! #   fn step1(&self, _: &mut Message) {}
57//! #   fn step2(&self, _: &mut Message) {}
58//! }
59//! ```
60#![warn(missing_docs)]
61#![doc(
62    html_logo_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png",
63    html_favicon_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png"
64)]
65
66mod cogs;
67mod measurement;
68mod recorder;
69#[cfg(test)]
70mod test;
71
72pub(crate) mod time;
73
74pub use self::cogs::*;
75pub use self::recorder::*;
76#[cfg(test)]
77pub use self::test::*;
78
79pub(crate) use self::measurement::*;
80
81/// Records a categorized measurement of the passed `body`, in `category` on `token`.
82///
83/// # Example:
84///
85/// ```
86/// # use relay_cogs::{AppFeature, Cogs, ResourceId};
87/// # struct Item;
88/// # fn do_something(_: &Item) -> bool { true };
89/// # fn do_something_else(_: &Item) -> bool { true };
90///
91/// fn process(cogs: &Cogs, item: &Item) {
92///     let mut token = cogs.timed(ResourceId::Relay, AppFeature::Transactions);
93///
94///     // The entire body is categorized as `processing`.
95///     relay_cogs::with!(token, "processing", {
96///         let success = do_something(&item);
97///     });
98///
99///     // Not categorized.
100///     if success {
101///         do_something_else(&item);
102///     }
103/// }
104/// ```
105#[macro_export]
106macro_rules! with {
107    ($token:expr, $category:expr, { $($body:tt)* }) => {
108        let token = $token.start_category($category);
109        $($body)*
110        drop(token);
111    };
112}
113
114/// Resource ID as tracked in COGS.
115///
116/// Infrastructure costs are labeled with a resource id,
117/// these costs need to be broken down further by the application
118/// by [app features](AppFeature).
119#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
120pub enum ResourceId {
121    /// The Relay resource.
122    ///
123    /// This includes all computational costs required for running Relay.
124    Relay,
125}
126
127/// App feature a COGS measurement is related to.
128///
129/// App features break down the cost of a [`ResourceId`], the
130/// app features do no need to directly match a Sentry product.
131/// Multiple app features are later grouped and aggregated to determine
132/// the cost of a product.
133#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
134pub enum AppFeature {
135    /// A placeholder which should not be emitted but can be emitted in rare cases,
136    /// for example error scenarios.
137    ///
138    /// It can be useful to start a COGS measurement before it is known
139    /// what the measurement should be attributed to.
140    /// For example when parsing data, the measurement should be started
141    /// before parsing, but only after parsing it is known what to attribute
142    /// the measurement to.
143    Unattributed,
144
145    /// Metrics are attributed by their namespace, whenever this is not possible
146    /// or feasible, this app feature is emitted instead.
147    UnattributedMetrics,
148    /// When processing an envelope cannot be attributed or is not feasible to be attributed
149    /// to a more specific category, this app feature is emitted instead.
150    UnattributedEnvelope,
151
152    /// Transactions.
153    Transactions,
154    /// Errors.
155    Errors,
156    /// Logs.
157    Logs,
158    /// Spans.
159    Spans,
160    /// Sessions.
161    Sessions,
162    /// Client reports.
163    ClientReports,
164    /// Crons check ins.
165    CheckIns,
166    /// Replays.
167    Replays,
168    /// Profiles.
169    ///
170    /// This app feature is for continuous profiling.
171    Profiles,
172
173    /// Metrics in the transactions namespace.
174    MetricsTransactions,
175    /// Metrics in the spans namespace.
176    MetricsSpans,
177    /// Metrics in the sessions namespace.
178    MetricsSessions,
179    /// Metrics in the custom namespace.
180    MetricsCustom,
181    /// Metrics in the `metric_stats` namespace.
182    MetricsStats,
183    /// Metrics in the unsupported namespace.
184    ///
185    /// This is usually not emitted, since metrics in the unsupported
186    /// namespace should be dropped before any processing occurs.
187    MetricsUnsupported,
188}
189
190impl AppFeature {
191    /// Returns the string representation for this app feature.
192    pub fn as_str(&self) -> &'static str {
193        match self {
194            Self::Unattributed => "unattributed",
195            Self::UnattributedMetrics => "unattributed_metrics",
196            Self::UnattributedEnvelope => "unattributed_envelope",
197            Self::Transactions => "transactions",
198            Self::Errors => "errors",
199            Self::Spans => "spans",
200            Self::Logs => "our_logs",
201            Self::Sessions => "sessions",
202            Self::ClientReports => "client_reports",
203            Self::CheckIns => "check_ins",
204            Self::Replays => "replays",
205            Self::MetricsTransactions => "metrics_transactions",
206            Self::MetricsSpans => "metrics_spans",
207            Self::MetricsSessions => "metrics_sessions",
208            Self::MetricsCustom => "metrics_custom",
209            Self::MetricsStats => "metrics_metric_stats",
210            Self::MetricsUnsupported => "metrics_unsupported",
211            Self::Profiles => "profiles",
212        }
213    }
214}
215
216/// A COGS measurement.
217///
218/// The measurement has already been attributed to a specific feature.
219#[derive(Debug, Clone, Copy)]
220pub struct CogsMeasurement {
221    /// The measured resource.
222    pub resource: ResourceId,
223    /// The measured app feature.
224    pub feature: AppFeature,
225    /// Optional category for this measurement.
226    ///
227    /// A category further subdivides a measurement for a specific feature.
228    pub category: Option<&'static str>,
229    /// The measurement value.
230    pub value: Value,
231}
232
233/// A COGS measurement value.
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum Value {
236    /// A time measurement.
237    Time(std::time::Duration),
238}