fxa_client/internal/
telemetry.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use super::{commands, FirefoxAccount};
6use crate::Result;
7use serde_derive::*;
8use sync_guid::Guid;
9
10impl FirefoxAccount {
11    /// Gathers and resets telemetry for this account instance.
12    /// This should be considered a short-term solution to telemetry gathering
13    /// and should called whenever consumers expect there might be telemetry,
14    /// and it should submit the telemetry to whatever telemetry system is in
15    /// use (probably glean).
16    ///
17    /// The data is returned as a JSON string, which consumers should parse
18    /// forgivingly (eg, be tolerant of things not existing) to try and avoid
19    /// too many changes as telemetry comes and goes.
20    pub fn gather_telemetry(&mut self) -> Result<String> {
21        let telem = std::mem::replace(&mut self.telemetry, FxaTelemetry::new());
22        Ok(serde_json::to_string(&telem)?)
23    }
24}
25
26// A somewhat mixed-bag of all telemetry we want to collect. The idea is that
27// the app will "pull" telemetry via a new API whenever it thinks there might
28// be something to record.
29// It's considered a temporary solution until either we can record it directly
30// (eg, via glean) or we come up with something better.
31// Note that this means we'll lose telemetry if we crash between gathering it
32// here and the app submitting it, but that should be rare (in practice,
33// apps will submit it directly after an operation that generated telememtry)
34
35/// The reason a tab/command was received.
36#[derive(Copy, Clone, Debug, Serialize)]
37#[serde(rename_all = "kebab-case")]
38pub enum ReceivedReason {
39    /// A push notification for the command was received.
40    Push,
41    /// Discovered while handling a push notification for a later message.
42    PushMissed,
43    /// Explicit polling for missed commands.
44    Poll,
45}
46
47#[derive(Copy, Clone, Debug, Serialize)]
48pub enum Command {
49    #[serde(rename = "send_tab")]
50    SendTab,
51    #[serde(rename = "close_tabs")]
52    CloseTabs,
53}
54
55#[derive(Debug, Serialize)]
56pub struct SentCommand {
57    pub command: Command,
58    pub flow_id: String,
59    pub stream_id: String,
60}
61
62impl SentCommand {
63    pub fn for_send_tab() -> Self {
64        Self::new(Command::SendTab)
65    }
66
67    pub fn for_close_tabs() -> Self {
68        Self::new(Command::CloseTabs)
69    }
70
71    pub fn clone_with_new_stream_id(&self) -> Self {
72        Self {
73            command: self.command,
74            flow_id: self.flow_id.clone(),
75            stream_id: Guid::random().into_string(),
76        }
77    }
78
79    fn new(command: Command) -> Self {
80        Self {
81            command,
82            flow_id: Guid::random().into_string(),
83            stream_id: Guid::random().into_string(),
84        }
85    }
86}
87
88#[derive(Debug, Serialize)]
89pub struct ReceivedCommand {
90    pub command: Command,
91    pub flow_id: String,
92    pub stream_id: String,
93    pub reason: ReceivedReason,
94}
95
96impl ReceivedCommand {
97    pub fn for_send_tab(payload: &commands::SendTabPayload, reason: ReceivedReason) -> Self {
98        Self {
99            command: Command::SendTab,
100            flow_id: payload.flow_id.clone(),
101            stream_id: payload.stream_id.clone(),
102            reason,
103        }
104    }
105
106    pub fn for_close_tabs(payload: &commands::CloseTabsPayload, reason: ReceivedReason) -> Self {
107        Self {
108            command: Command::SendTab,
109            flow_id: payload.flow_id.clone(),
110            stream_id: payload.stream_id.clone(),
111            reason,
112        }
113    }
114}
115
116// We have a naive strategy to avoid unbounded memory growth - the intention
117// is that if any platform lets things grow to hit these limits, it's probably
118// never going to consume anything - so it doesn't matter what we discard (ie,
119// there's no good reason to have a smarter circular buffer etc)
120const MAX_TAB_EVENTS: usize = 200;
121
122#[derive(Debug, Default, Serialize)]
123pub struct FxaTelemetry {
124    commands_sent: Vec<SentCommand>,
125    commands_received: Vec<ReceivedCommand>,
126}
127
128impl FxaTelemetry {
129    pub fn new() -> Self {
130        FxaTelemetry {
131            ..Default::default()
132        }
133    }
134
135    pub fn record_command_sent(&mut self, sent: SentCommand) {
136        if self.commands_sent.len() < MAX_TAB_EVENTS {
137            self.commands_sent.push(sent);
138        }
139    }
140
141    pub fn record_command_received(&mut self, recd: ReceivedCommand) {
142        if self.commands_received.len() < MAX_TAB_EVENTS {
143            self.commands_received.push(recd);
144        }
145    }
146}