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
74use std::fmt;
75
76pub use self::cogs::*;
77pub use self::recorder::*;
78#[cfg(test)]
79pub use self::test::*;
80
81pub(crate) use self::measurement::*;
82
83/// Records a categorized measurement of the passed `body`, in `category` on `token`.
84///
85/// # Example:
86///
87/// ```
88/// # use relay_cogs::{AppFeature, Cogs, ResourceId};
89/// # struct Item;
90/// # fn do_something(_: &Item) -> bool { true };
91/// # fn do_something_else(_: &Item) -> bool { true };
92///
93/// fn process(cogs: &Cogs, item: &Item) {
94/// let mut token = cogs.timed(ResourceId::Relay, AppFeature::Transactions);
95///
96/// // The entire body is categorized as `processing`.
97/// relay_cogs::with!(token, "processing", {
98/// let success = do_something(&item);
99/// });
100///
101/// // Not categorized.
102/// if success {
103/// do_something_else(&item);
104/// }
105/// }
106/// ```
107#[macro_export]
108macro_rules! with {
109 ($token:expr, $category:expr, { $($body:tt)* }) => {
110 let token = $token.start_category($category);
111 $($body)*
112 drop(token);
113 };
114}
115
116/// Resource ID as tracked in COGS.
117///
118/// Infrastructure costs are labeled with a resource id,
119/// these costs need to be broken down further by the application
120/// by [app features](AppFeature).
121#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
122pub enum ResourceId {
123 /// The Relay resource.
124 ///
125 /// This includes all computational costs required for running Relay.
126 Relay,
127}
128
129/// App feature a COGS measurement is related to.
130///
131/// App features break down the cost of a [`ResourceId`], the
132/// app features do no need to directly match a Sentry product.
133/// Multiple app features are later grouped and aggregated to determine
134/// the cost of a product.
135#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
136pub enum AppFeature {
137 /// A placeholder which should not be emitted but can be emitted in rare cases,
138 /// for example error scenarios.
139 ///
140 /// It can be useful to start a COGS measurement before it is known
141 /// what the measurement should be attributed to.
142 /// For example when parsing data, the measurement should be started
143 /// before parsing, but only after parsing it is known what to attribute
144 /// the measurement to.
145 Unattributed,
146
147 /// Metrics are attributed by their namespace, whenever this is not possible
148 /// or feasible, this app feature is emitted instead.
149 UnattributedMetrics,
150 /// When processing an envelope cannot be attributed or is not feasible to be attributed
151 /// to a more specific category, this app feature is emitted instead.
152 UnattributedEnvelope,
153
154 /// Transactions.
155 Transactions,
156 /// Errors.
157 Errors,
158 /// Logs.
159 Logs,
160 /// Trace metrics.
161 TraceMetrics,
162 /// Spans.
163 Spans,
164 /// Sessions.
165 Sessions,
166 /// Client reports.
167 ClientReports,
168 /// Crons check ins.
169 CheckIns,
170 /// Replays.
171 Replays,
172 /// Profiles.
173 ///
174 /// This app feature is for continuous profiling.
175 Profiles,
176 /// User Reports
177 UserReports,
178 /// Event attachments not associated with an event.
179 StandaloneAttachments,
180
181 /// Outcomes.
182 Outcomes,
183
184 /// Metrics in the spans namespace.
185 MetricsSpans,
186 /// Metrics in the transactions namespace.
187 MetricsTransactions,
188 /// Metrics in the sessions namespace.
189 MetricsSessions,
190 /// Metrics in the custom namespace.
191 MetricsCustom,
192 /// Metrics in the unsupported namespace.
193 ///
194 /// This is usually not emitted, since metrics in the unsupported
195 /// namespace should be dropped before any processing occurs.
196 MetricsUnsupported,
197
198 /// V2 attachments that cannot be attributed to spans or logs.
199 TraceAttachments,
200}
201
202impl AppFeature {
203 /// Returns the string representation for this app feature.
204 pub fn as_str(&self) -> &'static str {
205 match self {
206 Self::Unattributed => "unattributed",
207 Self::UnattributedMetrics => "unattributed_metrics",
208 Self::UnattributedEnvelope => "unattributed_envelope",
209 Self::Transactions => "transactions",
210 Self::Errors => "errors",
211 Self::Spans => "spans",
212 Self::Logs => "our_logs",
213 Self::Sessions => "sessions",
214 Self::ClientReports => "client_reports",
215 Self::CheckIns => "check_ins",
216 Self::Replays => "replays",
217 Self::UserReports => "user_reports",
218 Self::StandaloneAttachments => "standalone_attachments",
219 Self::Outcomes => "outcomes",
220 Self::MetricsSpans => "metrics_spans",
221 Self::MetricsTransactions => "metrics_transactions",
222 Self::MetricsSessions => "metrics_sessions",
223 Self::MetricsCustom => "metrics_custom",
224 Self::MetricsUnsupported => "metrics_unsupported",
225 Self::Profiles => "profiles",
226 Self::TraceMetrics => "trace_metrics",
227 Self::TraceAttachments => "trace_attachments",
228 }
229 }
230}
231
232/// A COGS measurement.
233///
234/// The measurement has already been attributed to a specific feature.
235#[derive(Debug, Clone, Copy)]
236pub struct CogsMeasurement {
237 /// The measured resource.
238 pub resource: ResourceId,
239 /// The measured app feature.
240 pub feature: AppFeature,
241 /// Optional category for this measurement.
242 ///
243 /// A category further subdivides a measurement for a specific feature.
244 pub category: Option<&'static str>,
245 /// The measurement value.
246 pub value: Value,
247}
248
249impl fmt::Display for CogsMeasurement {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 write!(f, "{:?}@{}", self.resource, self.feature.as_str())?;
252 if let Some(category) = self.category {
253 write!(f, "[{category}]")?;
254 }
255 write!(f, "={}", self.value)
256 }
257}
258
259/// A COGS measurement value.
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261pub enum Value {
262 /// A time measurement.
263 Time(std::time::Duration),
264}
265
266impl fmt::Display for Value {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 match self {
269 Self::Time(duration) => write!(f, "{duration:?}"),
270 }
271 }
272}