relay_server/metrics_extraction/sessions/
types.rs

1use std::collections::BTreeMap;
2use std::fmt::{self, Display};
3
4use relay_common::time::UnixTimestamp;
5use relay_event_schema::protocol::SessionStatus;
6use relay_metrics::{
7    Bucket, BucketMetadata, BucketValue, CounterType, MetricNamespace, MetricResourceIdentifier,
8    MetricUnit,
9};
10use uuid::Uuid;
11
12use crate::metrics_extraction::IntoMetric;
13
14/// Enumerates the metrics extracted from session payloads.
15#[derive(Clone, Debug, PartialEq)]
16pub enum SessionMetric {
17    /// The number of sessions collected in a given time frame.
18    Session {
19        counter: CounterType,
20        tags: SessionSessionTags,
21    },
22    /// The number of unique session users for a given time frame.
23    User {
24        distinct_id: String,
25        tags: SessionUserTags,
26    },
27    /// The number of sessions that errored in a given time frame.
28    ///
29    /// Because multiple session updates can be received for the same session ID,
30    /// this is collected as a [`relay_metrics::MetricType::Set`] metric rather than a simple counter.
31    Error { session_id: Uuid, tags: CommonTags },
32}
33
34/// Tags that are set on the `session` counter metric.
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct SessionSessionTags {
37    pub status: String,
38    pub common_tags: CommonTags,
39}
40
41/// Tags that are set on the `user` set metric.
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct SessionUserTags {
44    pub status: Option<SessionStatus>,
45    pub abnormal_mechanism: Option<String>,
46    pub common_tags: CommonTags,
47}
48
49/// Tags that are set on all session metrics.
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct CommonTags {
52    pub release: String,
53    pub environment: Option<String>,
54    pub sdk: Option<String>,
55}
56
57impl From<CommonTags> for BTreeMap<String, String> {
58    fn from(value: CommonTags) -> Self {
59        let mut map = BTreeMap::new();
60
61        map.insert("release".to_owned(), value.release);
62
63        if let Some(environment) = value.environment {
64            map.insert("environment".into(), environment);
65        }
66
67        if let Some(sdk) = value.sdk {
68            map.insert("sdk".to_owned(), sdk);
69        }
70        map
71    }
72}
73
74impl From<SessionUserTags> for BTreeMap<String, String> {
75    fn from(value: SessionUserTags) -> Self {
76        let mut map: BTreeMap<String, String> = value.common_tags.into();
77        if let Some(status) = value.status {
78            map.insert("session.status".to_owned(), status.to_string());
79        }
80
81        if let Some(abnormal_mechanism) = value.abnormal_mechanism {
82            map.insert("abnormal_mechanism".to_owned(), abnormal_mechanism);
83        }
84
85        map
86    }
87}
88
89impl From<SessionSessionTags> for BTreeMap<String, String> {
90    fn from(value: SessionSessionTags) -> Self {
91        let mut map: BTreeMap<String, String> = value.common_tags.into();
92        map.insert("session.status".to_owned(), value.status);
93
94        map
95    }
96}
97
98impl IntoMetric for SessionMetric {
99    fn into_metric(self, timestamp: UnixTimestamp) -> Bucket {
100        let name = self.to_string();
101
102        let (value, tags) = match self {
103            SessionMetric::Error {
104                session_id: id,
105                tags,
106            } => (BucketValue::set_from_display(id), tags.into()),
107            SessionMetric::User { distinct_id, tags } => {
108                (BucketValue::set_from_display(distinct_id), tags.into())
109            }
110            SessionMetric::Session { counter, tags } => {
111                (BucketValue::Counter(counter), tags.into())
112            }
113        };
114
115        let mri = MetricResourceIdentifier {
116            ty: value.ty(),
117            namespace: MetricNamespace::Sessions,
118            name: name.into(),
119            unit: MetricUnit::None,
120        };
121
122        // For extracted metrics we assume the `received_at` timestamp is equivalent to the time
123        // in which the metric is extracted.
124        let received_at = if cfg!(not(test)) {
125            UnixTimestamp::now()
126        } else {
127            UnixTimestamp::from_secs(0)
128        };
129
130        Bucket {
131            timestamp,
132            width: 0,
133            name: mri.to_string().into(),
134            value,
135            tags,
136            metadata: BucketMetadata::new(received_at),
137        }
138    }
139}
140
141impl Display for SessionMetric {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        match self {
144            Self::Session { .. } => write!(f, "session"),
145            Self::User { .. } => write!(f, "user"),
146            Self::Error { .. } => write!(f, "error"),
147        }
148    }
149}