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}