1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::ops::Deref;
use std::sync::atomic::{AtomicU8, Ordering};

use malloc_size_of_derive::MallocSizeOf;

use crate::error::{Error, ErrorKind};
use crate::metrics::dual_labeled_counter::validate_dynamic_key_and_or_category;
use crate::metrics::labeled::validate_dynamic_label;
use crate::Glean;
use serde::{Deserialize, Serialize};

/// The supported metrics' lifetimes.
///
/// A metric's lifetime determines when its stored data gets reset.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default, MallocSizeOf)]
#[repr(i32)] // Use i32 to be compatible with our JNA definition
#[serde(rename_all = "lowercase")]
pub enum Lifetime {
    /// The metric is reset with each sent ping
    #[default]
    Ping,
    /// The metric is reset on application restart
    Application,
    /// The metric is reset with each user profile
    User,
}

impl Lifetime {
    /// String representation of the lifetime.
    pub fn as_str(self) -> &'static str {
        match self {
            Lifetime::Ping => "ping",
            Lifetime::Application => "app",
            Lifetime::User => "user",
        }
    }
}

impl TryFrom<i32> for Lifetime {
    type Error = Error;

    fn try_from(value: i32) -> Result<Lifetime, Self::Error> {
        match value {
            0 => Ok(Lifetime::Ping),
            1 => Ok(Lifetime::Application),
            2 => Ok(Lifetime::User),
            e => Err(ErrorKind::Lifetime(e).into()),
        }
    }
}

/// The common set of data shared across all different metric types.
#[derive(Default, Debug, Clone, Deserialize, Serialize, MallocSizeOf)]
pub struct CommonMetricData {
    /// The metric's name.
    pub name: String,
    /// The metric's category.
    pub category: String,
    /// List of ping names to include this metric in.
    pub send_in_pings: Vec<String>,
    /// The metric's lifetime.
    pub lifetime: Lifetime,
    /// Whether or not the metric is disabled.
    ///
    /// Disabled metrics are never recorded.
    pub disabled: bool,
    /// Dynamic label.
    ///
    /// When a [`LabeledMetric<T>`](crate::metrics::LabeledMetric) factory creates the specific
    /// metric to be recorded to, dynamic labels are stored in the specific
    /// label so that we can validate them when the Glean singleton is
    /// available.
    pub dynamic_label: Option<DynamicLabelType>,
}

/// The type of dynamic label applied to a base metric. Used to help identify
/// the necessary validation to be performed.
#[derive(Debug, Clone, Deserialize, Serialize, MallocSizeOf, uniffi::Enum)]
pub enum DynamicLabelType {
    /// A dynamic label applied from a `LabeledMetric`
    Label(String),
    /// A label applied by a `DualLabeledCounter` that contains a dynamic key
    KeyOnly(String),
    /// A label applied by a `DualLabeledCounter` that contains a dynamic category
    CategoryOnly(String),
    /// A label applied by a `DualLabeledCounter` that contains a dynamic key and category
    KeyAndCategory(String),
}

impl Default for DynamicLabelType {
    fn default() -> Self {
        Self::Label(String::new())
    }
}

impl Deref for DynamicLabelType {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        match self {
            DynamicLabelType::Label(label) => label,
            DynamicLabelType::KeyOnly(key) => key,
            DynamicLabelType::CategoryOnly(category) => category,
            DynamicLabelType::KeyAndCategory(key_and_category) => key_and_category,
        }
    }
}

#[derive(Default, Debug, MallocSizeOf)]
pub struct CommonMetricDataInternal {
    pub inner: CommonMetricData,
    pub disabled: AtomicU8,
}

impl Clone for CommonMetricDataInternal {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.clone(),
            disabled: AtomicU8::new(self.disabled.load(Ordering::Relaxed)),
        }
    }
}

impl From<CommonMetricData> for CommonMetricDataInternal {
    fn from(input_data: CommonMetricData) -> Self {
        let disabled = input_data.disabled;
        Self {
            inner: input_data,
            disabled: AtomicU8::new(u8::from(disabled)),
        }
    }
}

impl CommonMetricDataInternal {
    /// Creates a new metadata object.
    pub fn new<A: Into<String>, B: Into<String>, C: Into<String>>(
        category: A,
        name: B,
        ping_name: C,
    ) -> CommonMetricDataInternal {
        CommonMetricDataInternal {
            inner: CommonMetricData {
                name: name.into(),
                category: category.into(),
                send_in_pings: vec![ping_name.into()],
                ..Default::default()
            },
            disabled: AtomicU8::new(0),
        }
    }

    /// The metric's base identifier, including the category and name, but not the label.
    ///
    /// If `category` is empty, it's ommitted.
    /// Otherwise, it's the combination of the metric's `category` and `name`.
    pub(crate) fn base_identifier(&self) -> String {
        if self.inner.category.is_empty() {
            self.inner.name.clone()
        } else {
            format!("{}.{}", self.inner.category, self.inner.name)
        }
    }

    /// The metric's unique identifier, including the category, name and label.
    ///
    /// If `category` is empty, it's ommitted.
    /// Otherwise, it's the combination of the metric's `category`, `name` and `label`.
    pub(crate) fn identifier(&self, glean: &Glean) -> String {
        let base_identifier = self.base_identifier();

        if let Some(label) = &self.inner.dynamic_label {
            match label {
                DynamicLabelType::Label(label) => {
                    validate_dynamic_label(glean, self, &base_identifier, label)
                }
                _ => validate_dynamic_key_and_or_category(
                    glean,
                    self,
                    &base_identifier,
                    label.clone(),
                ),
            }
        } else {
            base_identifier
        }
    }

    /// The list of storages this metric should be recorded into.
    pub fn storage_names(&self) -> &[String] {
        &self.inner.send_in_pings
    }
}