summaryrefslogtreecommitdiffstats
path: root/third_party/rust/glean/tests/upload_timing.rs
blob: ba0eee3402fe37ee1c021ee590b0f210ab79e39b (plain)
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// 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/.

//! This integration test should model how the RLB is used when embedded in another Rust application
//! (e.g. FOG/Firefox Desktop).
//!
//! We write a single test scenario per file to avoid any state keeping across runs
//! (different files run as different processes).

mod common;

use std::io::Read;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time;

use crossbeam_channel::{bounded, Sender};
use flate2::read::GzDecoder;
use serde_json::Value as JsonValue;

use glean::net;
use glean::ConfigurationBuilder;

pub mod metrics {
    #![allow(non_upper_case_globals)]

    use glean::{
        private::BooleanMetric, private::TimingDistributionMetric, CommonMetricData, Lifetime,
        TimeUnit,
    };

    pub static sample_boolean: once_cell::sync::Lazy<BooleanMetric> =
        once_cell::sync::Lazy::new(|| {
            BooleanMetric::new(CommonMetricData {
                name: "sample_boolean".into(),
                category: "test.metrics".into(),
                send_in_pings: vec!["validation".into()],
                disabled: false,
                lifetime: Lifetime::Ping,
                ..Default::default()
            })
        });

    // The following are duplicated from `glean-core/src/internal_metrics.rs`
    // so we can use the test APIs to query them.

    pub static send_success: once_cell::sync::Lazy<TimingDistributionMetric> =
        once_cell::sync::Lazy::new(|| {
            TimingDistributionMetric::new(
                CommonMetricData {
                    name: "send_success".into(),
                    category: "glean.upload".into(),
                    send_in_pings: vec!["metrics".into()],
                    lifetime: Lifetime::Ping,
                    disabled: false,
                    dynamic_label: None,
                },
                TimeUnit::Millisecond,
            )
        });

    pub static send_failure: once_cell::sync::Lazy<TimingDistributionMetric> =
        once_cell::sync::Lazy::new(|| {
            TimingDistributionMetric::new(
                CommonMetricData {
                    name: "send_failure".into(),
                    category: "glean.upload".into(),
                    send_in_pings: vec!["metrics".into()],
                    lifetime: Lifetime::Ping,
                    disabled: false,
                    dynamic_label: None,
                },
                TimeUnit::Millisecond,
            )
        });

    pub static shutdown_wait: once_cell::sync::Lazy<TimingDistributionMetric> =
        once_cell::sync::Lazy::new(|| {
            TimingDistributionMetric::new(
                CommonMetricData {
                    name: "shutdown_wait".into(),
                    category: "glean.validation".into(),
                    send_in_pings: vec!["metrics".into()],
                    lifetime: Lifetime::Ping,
                    disabled: false,
                    dynamic_label: None,
                },
                TimeUnit::Millisecond,
            )
        });
}

mod pings {
    use glean::private::PingType;
    use once_cell::sync::Lazy;

    #[allow(non_upper_case_globals)]
    pub static validation: Lazy<PingType> =
        Lazy::new(|| glean::private::PingType::new("validation", true, true, true, true, vec![]));
}

// Define a fake uploader that sleeps.
#[derive(Debug)]
struct FakeUploader {
    calls: AtomicUsize,
    sender: Sender<JsonValue>,
}

impl net::PingUploader for FakeUploader {
    fn upload(&self, upload_request: net::PingUploadRequest) -> net::UploadResult {
        let calls = self.calls.fetch_add(1, Ordering::SeqCst);
        let body = upload_request.body;
        let decode = |body: Vec<u8>| {
            let mut gzip_decoder = GzDecoder::new(&body[..]);
            let mut s = String::with_capacity(body.len());

            gzip_decoder
                .read_to_string(&mut s)
                .ok()
                .map(|_| &s[..])
                .or_else(|| std::str::from_utf8(&body).ok())
                .and_then(|payload| serde_json::from_str(payload).ok())
                .unwrap()
        };

        match calls {
            // First goes through as is.
            0 => net::UploadResult::http_status(200),
            // Second briefly sleeps
            1 => {
                thread::sleep(time::Duration::from_millis(100));
                net::UploadResult::http_status(200)
            }
            // Third one fails
            2 => net::UploadResult::http_status(404),
            // Fourth one fast again
            3 => {
                self.sender.send(decode(body)).unwrap();
                net::UploadResult::http_status(200)
            }
            // Last one is the metrics ping, a-ok.
            _ => {
                self.sender.send(decode(body)).unwrap();
                net::UploadResult::http_status(200)
            }
        }
    }
}

/// Test scenario: Different timings for upload on success and failure.
///
/// The app is initialized, in turn Glean gets initialized without problems.
/// A custom ping is submitted multiple times to trigger upload.
/// A metrics ping is submitted to get the upload timing data.
///
/// And later the whole process is shutdown.
#[test]
fn upload_timings() {
    common::enable_test_logging();

    // Create a custom configuration to use a validating uploader.
    let dir = tempfile::tempdir().unwrap();
    let tmpname = dir.path().to_path_buf();
    let (tx, rx) = bounded(1);

    let cfg = ConfigurationBuilder::new(true, tmpname.clone(), "glean-upload-timing")
        .with_server_endpoint("invalid-test-host")
        .with_use_core_mps(false)
        .with_uploader(FakeUploader {
            calls: AtomicUsize::new(0),
            sender: tx,
        })
        .build();
    common::initialize(cfg);

    // Wait for init to finish,
    // otherwise we might be to quick with calling `shutdown`.
    let _ = metrics::sample_boolean.test_get_value(None);

    // fast
    pings::validation.submit(None);
    // slow
    pings::validation.submit(None);
    // failed
    pings::validation.submit(None);
    // fast
    pings::validation.submit(None);

    // wait for the last ping
    let _body = rx.recv().unwrap();

    assert_eq!(
        3,
        metrics::send_success.test_get_value(None).unwrap().count,
        "Successful pings: two fast, one slow"
    );
    assert_eq!(
        1,
        metrics::send_failure.test_get_value(None).unwrap().count,
        "One failed ping"
    );

    // This is awkward, but it's what gets us very close to just starting a new process with a
    // fresh Glean.
    // This also calls `glean::shutdown();` internally, waiting on the uploader.
    let data_path = Some(tmpname.display().to_string());
    glean_core::glean_test_destroy_glean(false, data_path);

    let cfg = ConfigurationBuilder::new(true, tmpname, "glean-upload-timing")
        .with_server_endpoint("invalid-test-host")
        .with_use_core_mps(false)
        .build();
    common::initialize(cfg);

    assert_eq!(
        1,
        metrics::shutdown_wait.test_get_value(None).unwrap().count,
        "Measured time waiting for shutdown exactly once"
    );
}