diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/glean/tests | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/glean/tests')
57 files changed, 8554 insertions, 0 deletions
diff --git a/toolkit/components/glean/tests/browser/browser.ini b/toolkit/components/glean/tests/browser/browser.ini new file mode 100644 index 0000000000..eae3f38730 --- /dev/null +++ b/toolkit/components/glean/tests/browser/browser.ini @@ -0,0 +1,19 @@ +# Please keep test files lexicographically sorted, with whitespace between. +[DEFAULT] +support-files = + +[browser_event_leak.js] + +[browser_fog_gmp.js] +support-files = empty_file.html + +[browser_fog_gpu.js] + +[browser_fog_rdd.js] +support-files = small-shot.ogg + +[browser_fog_socket.js] + +[browser_fog_utility.js] + +[browser_labeled_gifft.js] diff --git a/toolkit/components/glean/tests/browser/browser_event_leak.js b/toolkit/components/glean/tests/browser/browser_event_leak.js new file mode 100644 index 0000000000..1e69f17064 --- /dev/null +++ b/toolkit/components/glean/tests/browser/browser_event_leak.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + Services.fog.testResetFOG(); // Needed for TV which reuses profiles on repeat + Assert.equal( + undefined, + Glean.testOnlyIpc.eventWithExtra.testGetValue(), + "Nothing to begin with" + ); + Glean.testOnlyIpc.eventWithExtra.record({ + extra1: "Some extra string", + extra2: 42, + extra3_longer_name: false, + }); + Assert.equal( + 1, + Glean.testOnlyIpc.eventWithExtra.testGetValue().length, + "One event? One event." + ); + + // AND NOW, FOR THE TRUE TEST: + // Will this leak memory all over the place? +}); diff --git a/toolkit/components/glean/tests/browser/browser_fog_gmp.js b/toolkit/components/glean/tests/browser/browser_fog_gmp.js new file mode 100644 index 0000000000..6e7067a5b0 --- /dev/null +++ b/toolkit/components/glean/tests/browser/browser_fog_gmp.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Return a web-based URL for a given file based on the testing directory. + * @param {String} fileName + * file that caller wants its web-based url + */ +function GetTestWebBasedURL(fileName) { + return ( + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.org" + ) + fileName + ); +} + +add_task(async () => { + await SpecialPowers.pushPrefEnv({ + set: [["media.eme.enabled", true]], + }); + + await BrowserTestUtils.withNewTab( + GetTestWebBasedURL("empty_file.html"), + async function (browser) { + await SpecialPowers.spawn(browser, [], async function () { + try { + let config = [ + { + initDataTypes: ["webm"], + videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }], + }, + ]; + let access = await content.navigator.requestMediaKeySystemAccess( + "org.w3.clearkey", + config + ); + + content.mediaKeys = await access.createMediaKeys(); + info("got media keys, which should ensure a GMP process exists"); + } catch (ex) { + ok(false, ex.toString()); + } + }); + + ok( + (await ChromeUtils.requestProcInfo()).children.some( + p => p.type == "gmpPlugin" + ), + "Found the GMP process." + ); + + Services.fog.testResetFOG(); + + is( + undefined, + Glean.testOnlyIpc.aCounter.testGetValue(), + "Ensure we begin without value." + ); + + await TestUtils.waitForCondition(async () => { + try { + await Services.fog.testTriggerMetrics( + Ci.nsIXULRuntime.PROCESS_TYPE_GMPLUGIN + ); + return true; + } catch (e) { + info(e); + return false; + } + }, "waiting until we can successfully send the TestTriggerMetrics IPC."); + + await Services.fog.testFlushAllChildren(); + + is( + Glean.testOnlyIpc.aCounter.testGetValue(), + Ci.nsIXULRuntime.PROCESS_TYPE_GMPLUGIN, + "Ensure the GMP-process-set value shows up in the parent process." + ); + } + ); +}); diff --git a/toolkit/components/glean/tests/browser/browser_fog_gpu.js b/toolkit/components/glean/tests/browser/browser_fog_gpu.js new file mode 100644 index 0000000000..da0e699c20 --- /dev/null +++ b/toolkit/components/glean/tests/browser/browser_fog_gpu.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + if ( + !(await ChromeUtils.requestProcInfo()).children.some(p => p.type == "gpu") + ) { + ok( + true, + 'No GPU process? No test. Try again with --setpref "layers.gpu-process.force-enabled=true".' + ); + return; + } + ok(true, "GPU Process found: Let's test."); + + Services.fog.testResetFOG(); + + is( + undefined, + Glean.testOnlyIpc.aCounter.testGetValue(), + "Ensure we begin without value." + ); + + await Services.fog.testTriggerMetrics(Ci.nsIXULRuntime.PROCESS_TYPE_GPU); + await Services.fog.testFlushAllChildren(); + + is( + Glean.testOnlyIpc.aCounter.testGetValue(), + Ci.nsIXULRuntime.PROCESS_TYPE_GPU, + "Ensure the GPU-process-set value shows up in the parent process." + ); +}); diff --git a/toolkit/components/glean/tests/browser/browser_fog_rdd.js b/toolkit/components/glean/tests/browser/browser_fog_rdd.js new file mode 100644 index 0000000000..caf9a6db1f --- /dev/null +++ b/toolkit/components/glean/tests/browser/browser_fog_rdd.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Return a web-based URL for a given file based on the testing directory. + * @param {String} fileName + * file that caller wants its web-based url + */ +function GetTestWebBasedURL(fileName) { + return ( + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.org" + ) + fileName + ); +} + +add_task(async () => { + await SpecialPowers.pushPrefEnv({ + set: [["media.rdd-process.enabled", true]], + }); + + let url = GetTestWebBasedURL("small-shot.ogg"); + info( + `Opening ${url} in a new tab to trigger the creation of the RDD process` + ); + let tab = BrowserTestUtils.addTab(gBrowser, url); + + await TestUtils.waitForCondition( + async () => + (await ChromeUtils.requestProcInfo()).children.some(p => p.type == "rdd"), + "waiting to find RDD process." + ); + + Services.fog.testResetFOG(); + + is( + undefined, + Glean.testOnlyIpc.aCounter.testGetValue(), + "Ensure we begin without value." + ); + + await TestUtils.waitForCondition(async () => { + try { + await Services.fog.testTriggerMetrics(Ci.nsIXULRuntime.PROCESS_TYPE_RDD); + return true; + } catch (e) { + return false; + } + }, "waiting until we can successfully send the TestTriggerMetrics IPC."); + + await Services.fog.testFlushAllChildren(); + + is( + Glean.testOnlyIpc.aCounter.testGetValue(), + Ci.nsIXULRuntime.PROCESS_TYPE_RDD, + "Ensure the RDD-process-set value shows up in the parent process." + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/components/glean/tests/browser/browser_fog_socket.js b/toolkit/components/glean/tests/browser/browser_fog_socket.js new file mode 100644 index 0000000000..4ca4f085a1 --- /dev/null +++ b/toolkit/components/glean/tests/browser/browser_fog_socket.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + if ( + !(await ChromeUtils.requestProcInfo()).children.some( + p => p.type == "socket" + ) + ) { + ok(true, "No Socket process? No test."); + return; + } + ok(true, "Socket process found: Let's test."); + + Services.fog.testResetFOG(); + + is( + undefined, + Glean.testOnlyIpc.aCounter.testGetValue(), + "Ensure we begin without value." + ); + + await Services.fog.testTriggerMetrics(Ci.nsIXULRuntime.PROCESS_TYPE_SOCKET); + await Services.fog.testFlushAllChildren(); + + is( + Glean.testOnlyIpc.aCounter.testGetValue(), + Ci.nsIXULRuntime.PROCESS_TYPE_SOCKET, + "Ensure the Socket-process-set value shows up in the parent process." + ); +}); diff --git a/toolkit/components/glean/tests/browser/browser_fog_utility.js b/toolkit/components/glean/tests/browser/browser_fog_utility.js new file mode 100644 index 0000000000..aaea9cfb42 --- /dev/null +++ b/toolkit/components/glean/tests/browser/browser_fog_utility.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + const utilityProcessTest = Cc[ + "@mozilla.org/utility-process-test;1" + ].createInstance(Ci.nsIUtilityProcessTest); + await utilityProcessTest + .startProcess() + .then(async () => { + Services.fog.testResetFOG(); + + is( + undefined, + Glean.testOnlyIpc.aCounter.testGetValue(), + "Ensure we begin without value." + ); + + await Services.fog.testTriggerMetrics( + Ci.nsIXULRuntime.PROCESS_TYPE_UTILITY + ); + await Services.fog.testFlushAllChildren(); + + is( + Glean.testOnlyIpc.aCounter.testGetValue(), + Ci.nsIXULRuntime.PROCESS_TYPE_UTILITY, + "Ensure the utility-process-set value shows up in the parent process." + ); + }) + .catch(async () => { + ok(false, "Cannot start Utility process?"); + }); + + await utilityProcessTest.stopProcess(); +}); diff --git a/toolkit/components/glean/tests/browser/browser_labeled_gifft.js b/toolkit/components/glean/tests/browser/browser_labeled_gifft.js new file mode 100644 index 0000000000..676b59453e --- /dev/null +++ b/toolkit/components/glean/tests/browser/browser_labeled_gifft.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function keyedScalarValue(aScalarName) { + let snapshot = Services.telemetry.getSnapshotForKeyedScalars(); + return "parent" in snapshot ? snapshot.parent[aScalarName] : undefined; +} + +add_task(async () => { + // Ensure we're starting with a clean slate. test-verify can be tricky. + Services.fog.testResetFOG(); + Services.telemetry.clearScalars(); + + Assert.equal( + undefined, + Glean.testOnlyIpc.aLabeledCounter.a_label.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.testOnlyIpc.aLabeledCounter.a_label.add(1); + Glean.testOnlyIpc.aLabeledCounter.another_label.add(2); + Assert.equal(1, Glean.testOnlyIpc.aLabeledCounter.a_label.testGetValue()); + Assert.equal( + 2, + Glean.testOnlyIpc.aLabeledCounter.another_label.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.testOnlyIpc.aLabeledCounter.__other__.testGetValue() + ); + Glean.testOnlyIpc.aLabeledCounter["1".repeat(72)].add(3); + Assert.throws( + () => Glean.testOnlyIpc.aLabeledCounter.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Can't get the value when you're error'd" + ); + + let value = keyedScalarValue( + "telemetry.test.another_mirror_for_labeled_counter" + ); + Assert.deepEqual( + { + a_label: 1, + another_label: 2, + ["1".repeat(72)]: 3, + }, + value + ); + + // AND NOW, FOR THE TRUE TEST: + // Will this leak memory all over the place? +}); diff --git a/toolkit/components/glean/tests/browser/empty_file.html b/toolkit/components/glean/tests/browser/empty_file.html new file mode 100644 index 0000000000..af8440ac16 --- /dev/null +++ b/toolkit/components/glean/tests/browser/empty_file.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + </head> + <body> + This page is intentionally left blank. + </body> +</html> diff --git a/toolkit/components/glean/tests/browser/small-shot.ogg b/toolkit/components/glean/tests/browser/small-shot.ogg Binary files differnew file mode 100644 index 0000000000..1a41623f81 --- /dev/null +++ b/toolkit/components/glean/tests/browser/small-shot.ogg diff --git a/toolkit/components/glean/tests/gtest/Cargo.toml b/toolkit/components/glean/tests/gtest/Cargo.toml new file mode 100644 index 0000000000..a744517e56 --- /dev/null +++ b/toolkit/components/glean/tests/gtest/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "fog-gtest" +version = "0.1.0" +authors = ["glean-team@mozilla.com"] +license = "MPL-2.0" +description = "Tests for the FOG crate" + +[dependencies] +firefox-on-glean = { path = "../../api" } +jog = { path = "../../bindings/jog" } +nsstring = { path = "../../../../../xpcom/rust/nsstring" } + +[lib] +path = "test.rs" diff --git a/toolkit/components/glean/tests/gtest/FOGFixture.h b/toolkit/components/glean/tests/gtest/FOGFixture.h new file mode 100644 index 0000000000..da3c6fc123 --- /dev/null +++ b/toolkit/components/glean/tests/gtest/FOGFixture.h @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#ifndef FOGFixture_h_ +#define FOGFixture_h_ + +#include "gtest/gtest.h" +#include "mozilla/glean/fog_ffi_generated.h" +#include "nsString.h" + +using namespace mozilla::glean::impl; + +class FOGFixture : public ::testing::Test { + protected: + FOGFixture() = default; + virtual void SetUp() { + nsCString empty; + ASSERT_EQ(NS_OK, fog_test_reset(&empty, &empty)); + } +}; + +#endif // FOGFixture_h_ diff --git a/toolkit/components/glean/tests/gtest/TestFog.cpp b/toolkit/components/glean/tests/gtest/TestFog.cpp new file mode 100644 index 0000000000..2de8f9ba4d --- /dev/null +++ b/toolkit/components/glean/tests/gtest/TestFog.cpp @@ -0,0 +1,470 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +#include "FOGFixture.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/glean/GleanPings.h" +#include "mozilla/glean/fog_ffi_generated.h" +#include "mozilla/Maybe.h" +#include "mozilla/Result.h" +#include "mozilla/ResultVariant.h" + +#include "nsTArray.h" + +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "nsString.h" +#include "prtime.h" + +using mozilla::Preferences; +using namespace mozilla::glean; +using namespace mozilla::glean::impl; + +#define DATA_PREF "datareporting.healthreport.uploadEnabled" + +extern "C" { +// This function is called by the rust code in test.rs if a non-fatal test +// failure occurs. +void GTest_FOG_ExpectFailure(const char* aMessage) { + EXPECT_STREQ(aMessage, ""); +} +} + +TEST_F(FOGFixture, BuiltinPingsRegistered) { + Preferences::SetInt("telemetry.fog.test.localhost_port", -1); + nsAutoCString metricsPingName("metrics"); + nsAutoCString baselinePingName("baseline"); + nsAutoCString eventsPingName("events"); + ASSERT_EQ(NS_OK, fog_submit_ping(&metricsPingName)); + ASSERT_EQ(NS_OK, fog_submit_ping(&baselinePingName)); + ASSERT_EQ(NS_OK, fog_submit_ping(&eventsPingName)); +} + +TEST_F(FOGFixture, TestCppCounterWorks) { + mozilla::glean::test_only::bad_code.Add(42); + + ASSERT_EQ(42, mozilla::glean::test_only::bad_code.TestGetValue("test-ping"_ns) + .unwrap() + .value()); + // And test that the ping name's optional, while you're at it: + ASSERT_EQ(42, test_only::bad_code.TestGetValue().unwrap().value()); +} + +TEST_F(FOGFixture, TestCppStringWorks) { + auto kValue = "cheez!"_ns; + mozilla::glean::test_only::cheesy_string.Set(kValue); + + ASSERT_STREQ(kValue.get(), mozilla::glean::test_only::cheesy_string + .TestGetValue("test-ping"_ns) + .unwrap() + .value() + .get()); +} + +TEST_F(FOGFixture, TestCppTimespanWorks) { + mozilla::glean::test_only::can_we_time_it.Start(); + PR_Sleep(PR_MillisecondsToInterval(10)); + mozilla::glean::test_only::can_we_time_it.Stop(); + + ASSERT_TRUE( + mozilla::glean::test_only::can_we_time_it.TestGetValue("test-ping"_ns) + .unwrap() + .value() > 0); +} + +TEST_F(FOGFixture, TestCppUuidWorks) { + nsCString kTestUuid("decafdec-afde-cafd-ecaf-decafdecafde"); + test_only::what_id_it.Set(kTestUuid); + ASSERT_STREQ(kTestUuid.get(), + test_only::what_id_it.TestGetValue("test-ping"_ns) + .unwrap() + .value() + .get()); + + test_only::what_id_it.GenerateAndSet(); + // Since we generate v4 UUIDs, and the first character of the third group + // isn't 4, this won't ever collide with kTestUuid. + ASSERT_STRNE(kTestUuid.get(), + test_only::what_id_it.TestGetValue("test-ping"_ns) + .unwrap() + .value() + .get()); +} + +TEST_F(FOGFixture, TestCppBooleanWorks) { + mozilla::glean::test_only::can_we_flag_it.Set(false); + + ASSERT_EQ(false, mozilla::glean::test_only::can_we_flag_it + .TestGetValue("test-ping"_ns) + .unwrap() + .value()); +} + +MATCHER_P(BitEq, x, "bit equal") { + static_assert(sizeof(x) == sizeof(arg)); + return std::memcmp(&arg, &x, sizeof(x)) == 0; +} + +TEST_F(FOGFixture, TestCppDatetimeWorks) { + PRExplodedTime date{0, 35, 10, 12, 6, 10, 2020, 0, 0, {5 * 60 * 60, 0}}; + test_only::what_a_date.Set(&date); + + auto received = test_only::what_a_date.TestGetValue("test-ping"_ns).unwrap(); + ASSERT_THAT(received.value(), BitEq(date)); +} + +using mozilla::Some; +using mozilla::glean::test_only_ipc::AnEventExtra; +using mozilla::glean::test_only_ipc::EventWithExtraExtra; +using std::tuple; + +TEST_F(FOGFixture, TestCppEventWorks) { + test_only_ipc::no_extra_event.Record(); + ASSERT_TRUE(test_only_ipc::no_extra_event.TestGetValue("store1"_ns) + .unwrap() + .isSome()); + + AnEventExtra extra = {.extra1 = Some("can set extras"_ns)}; + test_only_ipc::an_event.Record(Some(extra)); + auto optEvents = test_only_ipc::an_event.TestGetValue("store1"_ns).unwrap(); + ASSERT_TRUE(optEvents.isSome()); + + auto events = optEvents.extract(); + ASSERT_EQ(1UL, events.Length()); + ASSERT_STREQ("test_only.ipc", events[0].mCategory.get()); + ASSERT_STREQ("an_event", events[0].mName.get()); + ASSERT_EQ(1UL, events[0].mExtra.Length()); + ASSERT_STREQ("extra1", std::get<0>(events[0].mExtra[0]).get()); + ASSERT_STREQ("can set extras", std::get<1>(events[0].mExtra[0]).get()); +} + +TEST_F(FOGFixture, TestCppEventsWithDifferentExtraTypes) { + EventWithExtraExtra extra = {.extra1 = Some("can set extras"_ns), + .extra2 = Some(37), + .extra3LongerName = Some(false)}; + test_only_ipc::event_with_extra.Record(Some(extra)); + auto optEvents = + test_only_ipc::event_with_extra.TestGetValue("store1"_ns).unwrap(); + ASSERT_TRUE(optEvents.isSome()); + + auto events = optEvents.extract(); + ASSERT_EQ(1UL, events.Length()); + + // The list of extra key/value pairs can be in any order. + ASSERT_EQ(3UL, events[0].mExtra.Length()); + for (auto extra : events[0].mExtra) { + auto key = std::get<0>(extra); + auto value = std::get<1>(extra); + + if (key == "extra1"_ns) { + ASSERT_STREQ("can set extras", value.get()); + } else if (key == "extra2"_ns) { + ASSERT_STREQ("37", value.get()); + } else if (key == "extra3_longer_name"_ns) { + ASSERT_STREQ("false", value.get()); + } else { + ASSERT_TRUE(false) + << "Invalid extra item recorded."; + } + } +} + +TEST_F(FOGFixture, TestCppMemoryDistWorks) { + test_only::do_you_remember.Accumulate(7); + test_only::do_you_remember.Accumulate(17); + + DistributionData data = + test_only::do_you_remember.TestGetValue("test-ping"_ns).unwrap().ref(); + // Sum is in bytes, test_only::do_you_remember is in megabytes. So + // multiplication ahoy! + ASSERT_EQ(data.sum, 24UL * 1024 * 1024); + for (const auto& entry : data.values) { + const uint64_t bucket = entry.GetKey(); + const uint64_t count = entry.GetData(); + ASSERT_TRUE(count == 0 || + (count == 1 && (bucket == 17520006 || bucket == 7053950))) + << "Only two occupied buckets"; + } +} + +TEST_F(FOGFixture, TestCppCustomDistWorks) { + test_only_ipc::a_custom_dist.AccumulateSamples({7, 268435458}); + + DistributionData data = + test_only_ipc::a_custom_dist.TestGetValue("store1"_ns).unwrap().ref(); + ASSERT_EQ(data.sum, 7UL + 268435458); + for (const auto& entry : data.values) { + const uint64_t bucket = entry.GetKey(); + const uint64_t count = entry.GetData(); + ASSERT_TRUE(count == 0 || + (count == 1 && (bucket == 1 || bucket == 268435456))) + << "Only two occupied buckets"; + } +} + +TEST_F(FOGFixture, TestCppPings) { + test_only::one_ping_one_bool.Set(false); + const auto& ping = mozilla::glean_pings::OnePingOnly; + bool submitted = false; + ping.TestBeforeNextSubmit([&submitted](const nsACString& aReason) { + submitted = true; + ASSERT_EQ(false, + test_only::one_ping_one_bool.TestGetValue().unwrap().ref()); + }); + ping.Submit(); + ASSERT_TRUE(submitted) + << "Must have actually called the lambda."; +} + +TEST_F(FOGFixture, TestCppStringLists) { + auto kValue = "cheez!"_ns; + auto kValue2 = "cheezier!"_ns; + auto kValue3 = "cheeziest."_ns; + + nsTArray<nsCString> cheezList; + cheezList.EmplaceBack(kValue); + cheezList.EmplaceBack(kValue2); + + test_only::cheesy_string_list.Set(cheezList); + + auto val = test_only::cheesy_string_list.TestGetValue().unwrap().value(); + // Note: This is fragile if the order is ever not preserved. + ASSERT_STREQ(kValue.get(), val[0].get()); + ASSERT_STREQ(kValue2.get(), val[1].get()); + + test_only::cheesy_string_list.Add(kValue3); + + val = test_only::cheesy_string_list.TestGetValue().unwrap().value(); + ASSERT_STREQ(kValue3.get(), val[2].get()); +} + +TEST_F(FOGFixture, TestCppTimingDistWorks) { + auto id1 = test_only::what_time_is_it.Start(); + auto id2 = test_only::what_time_is_it.Start(); + PR_Sleep(PR_MillisecondsToInterval(5)); + auto id3 = test_only::what_time_is_it.Start(); + test_only::what_time_is_it.Cancel(std::move(id1)); + PR_Sleep(PR_MillisecondsToInterval(5)); + test_only::what_time_is_it.StopAndAccumulate(std::move(id2)); + test_only::what_time_is_it.StopAndAccumulate(std::move(id3)); + + DistributionData data = + test_only::what_time_is_it.TestGetValue().unwrap().ref(); + const uint64_t NANOS_IN_MILLIS = 1e6; + + // bug 1701847 - Sleeps don't necessarily round up as you'd expect. + // Give ourselves a 200000ns (0.2ms) window to be off on fast machines. + const uint64_t EPSILON = 200000; + + // We don't know exactly how long those sleeps took, only that it was at + // least 15ms total. + ASSERT_GT(data.sum, (uint64_t)(15 * NANOS_IN_MILLIS) - EPSILON); + + // We also can't guarantee the buckets, but we can guarantee two samples. + uint64_t sampleCount = 0; + for (const auto& value : data.values.Values()) { + sampleCount += value; + } + ASSERT_EQ(sampleCount, (uint64_t)2); +} + +TEST_F(FOGFixture, TestLabeledBooleanWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_like_balloons.Get("hot_air"_ns) + .TestGetValue() + .unwrap()); + test_only::mabels_like_balloons.Get("hot_air"_ns).Set(true); + test_only::mabels_like_balloons.Get("helium"_ns).Set(false); + ASSERT_EQ(true, test_only::mabels_like_balloons.Get("hot_air"_ns) + .TestGetValue() + .unwrap() + .ref()); + ASSERT_EQ(false, test_only::mabels_like_balloons.Get("helium"_ns) + .TestGetValue() + .unwrap() + .ref()); +} + +TEST_F(FOGFixture, TestLabeledBooleanWithLabelsWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eWater) + .TestGetValue() + .unwrap()); + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eWater) + .Set(true); + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eBirthdayParty) + .Set(false); + ASSERT_EQ(true, + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eWater) + .TestGetValue() + .unwrap() + .ref()); + ASSERT_EQ( + false, + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eBirthdayParty) + .TestGetValue() + .unwrap() + .ref()); +} + +TEST_F(FOGFixture, TestLabeledCounterWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_kitchen_counters.Get("marble"_ns) + .TestGetValue() + .unwrap()); + test_only::mabels_kitchen_counters.Get("marble"_ns).Add(1); + test_only::mabels_kitchen_counters.Get("laminate"_ns).Add(2); + ASSERT_EQ(1, test_only::mabels_kitchen_counters.Get("marble"_ns) + .TestGetValue() + .unwrap() + .ref()); + ASSERT_EQ(2, test_only::mabels_kitchen_counters.Get("laminate"_ns) + .TestGetValue() + .unwrap() + .ref()); +} + +TEST_F(FOGFixture, TestLabeledCounterWithLabelsWorks) { + ASSERT_EQ( + mozilla::Nothing(), + test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eNextToTheFridge) + .TestGetValue() + .unwrap()); + test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eNextToTheFridge) + .Add(1); + test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eClean) + .Add(2); + ASSERT_EQ( + 1, test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eNextToTheFridge) + .TestGetValue() + .unwrap() + .ref()); + ASSERT_EQ(2, test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eClean) + .TestGetValue() + .unwrap() + .ref()); +} + +TEST_F(FOGFixture, TestLabeledStringWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_balloon_strings.Get("twine"_ns) + .TestGetValue() + .unwrap()); + test_only::mabels_balloon_strings.Get("twine"_ns).Set("seems acceptable"_ns); + test_only::mabels_balloon_strings.Get("parachute_cord"_ns) + .Set("preferred"_ns); + ASSERT_STREQ("seems acceptable", + test_only::mabels_balloon_strings.Get("twine"_ns) + .TestGetValue() + .unwrap() + .ref() + .get()); + ASSERT_STREQ("preferred", + test_only::mabels_balloon_strings.Get("parachute_cord"_ns) + .TestGetValue() + .unwrap() + .ref() + .get()); +} + +TEST_F(FOGFixture, TestLabeledStringWithLabelsWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratory) + .TestGetValue() + .unwrap()); + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratory) + .Set("for birthdays, etc."_ns); + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratoryAndSnarky) + .Set("for retirements and bridal showers"_ns); + ASSERT_STREQ("for birthdays, etc.", + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratory) + .TestGetValue() + .unwrap() + .ref() + .get()); + ASSERT_STREQ( + "for retirements and bridal showers", + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratoryAndSnarky) + .TestGetValue() + .unwrap() + .ref() + .get()); +} + +TEST_F(FOGFixture, TestCppQuantityWorks) { + // This joke only works in base 13. + const uint32_t kValue = 6 * 9; + mozilla::glean::test_only::meaning_of_life.Set(kValue); + + ASSERT_EQ(kValue, mozilla::glean::test_only::meaning_of_life.TestGetValue() + .unwrap() + .value()); +} + +TEST_F(FOGFixture, TestCppRateWorks) { + // 1) Standard rate with internal denominator + const int32_t kNum = 22; + const int32_t kDen = 7; // because I like pi, even just approximately. + + test_only_ipc::irate.AddToNumerator(kNum); + test_only_ipc::irate.AddToDenominator(kDen); + auto value = test_only_ipc::irate.TestGetValue().unwrap(); + ASSERT_EQ(kNum, value.ref().first); + ASSERT_EQ(kDen, value.ref().second); + + // 2) Rate with external denominator + test_only_ipc::rate_with_external_denominator.AddToNumerator(kNum); + test_only_ipc::an_external_denominator.Add(kDen); + value = test_only_ipc::rate_with_external_denominator.TestGetValue().unwrap(); + ASSERT_EQ(kNum, value.ref().first); + ASSERT_EQ(kDen, value.ref().second); + ASSERT_EQ( + kDen, + test_only_ipc::an_external_denominator.TestGetValue().unwrap().extract()); +} + +TEST_F(FOGFixture, TestCppUrlWorks) { + auto kValue = "https://example.com/fog/gtest"_ns; + mozilla::glean::test_only_ipc::a_url.Set(kValue); + + ASSERT_STREQ(kValue.get(), + mozilla::glean::test_only_ipc::a_url.TestGetValue("store1"_ns) + .unwrap() + .value() + .get()); +} + +TEST_F(FOGFixture, TestCppTextWorks) { + auto kValue = + "République is a French-inspired bakery, café, bar and formal dining room located in the Mid-city neighborhood of Los Angeles, set in a historic 1928 space ..."_ns; + mozilla::glean::test_only_ipc::a_text.Set(kValue); + ASSERT_STREQ(kValue.get(), + mozilla::glean::test_only_ipc::a_text.TestGetValue() + .unwrap() + .value() + .get()); +} + +extern "C" void Rust_TestRustInGTest(); +TEST_F(FOGFixture, TestRustInGTest) { Rust_TestRustInGTest(); } + +extern "C" void Rust_TestJogfile(); +TEST_F(FOGFixture, TestJogfile) { Rust_TestJogfile(); } diff --git a/toolkit/components/glean/tests/gtest/moz.build b/toolkit/components/glean/tests/gtest/moz.build new file mode 100644 index 0000000000..d17ee0e0dd --- /dev/null +++ b/toolkit/components/glean/tests/gtest/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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 http://mozilla.org/MPL/2.0/. + +if not CONFIG["MOZ_GLEAN_ANDROID"]: + UNIFIED_SOURCES += [ + "TestFog.cpp", + ] + + TEST_HARNESS_FILES.gtest += ["../pytest/jogfile_output"] + +FINAL_LIBRARY = "xul-gtest" diff --git a/toolkit/components/glean/tests/gtest/test.rs b/toolkit/components/glean/tests/gtest/test.rs new file mode 100644 index 0000000000..90ad67aebb --- /dev/null +++ b/toolkit/components/glean/tests/gtest/test.rs @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +extern crate firefox_on_glean; +use firefox_on_glean::metrics; + +extern crate nsstring; +use nsstring::nsString; + +fn nonfatal_fail(msg: String) { + extern "C" { + fn GTest_FOG_ExpectFailure(message: *const ::std::os::raw::c_char); + } + unsafe { + let msg = ::std::ffi::CString::new(msg).unwrap(); + GTest_FOG_ExpectFailure(msg.as_ptr()); + } +} + +/// This macro checks if the expression evaluates to true, +/// and causes a non-fatal GTest test failure if it doesn't. +macro_rules! expect { + ($x:expr) => { + match (&$x) { + true => {} + false => nonfatal_fail(format!( + "check failed: (`{}`) at {}:{}", + stringify!($x), + file!(), + line!() + )), + } + }; +} + +#[no_mangle] +pub extern "C" fn Rust_TestRustInGTest() { + // Just a smoke test, we show here how tests might work that both + // a) Are in Rust, and + // b) Require Gecko + // This demonstration doesn't actually require Gecko. But we pretend it + // does so we remember how to do this rust-in-gtest pattern. + metrics::test_only::bad_code.add(12); + expect!(metrics::test_only::bad_code.test_get_value(None) == Some(12)); +} + +#[no_mangle] +pub extern "C" fn Rust_TestJogfile() { + // Ensure that the generated jogfile in t/c/g/tests/pytest + // (which is installed nearby using TEST_HARNESS_FILES) + // can be consumed by JOG's inner workings + // + // If it can't, that's perhaps a sign that the inner workings need to be updated. + expect!(jog::jog_load_jogfile(&nsString::from("jogfile_output"))); +} diff --git a/toolkit/components/glean/tests/moz.build b/toolkit/components/glean/tests/moz.build new file mode 100644 index 0000000000..e7de2c3b66 --- /dev/null +++ b/toolkit/components/glean/tests/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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 http://mozilla.org/MPL/2.0/. + +PYTHON_UNITTEST_MANIFESTS += ["pytest/python.ini"] + +TEST_DIRS += ["gtest"] + +XPCSHELL_TESTS_MANIFESTS += ["xpcshell/xpcshell.ini"] + +BROWSER_CHROME_MANIFESTS += ["browser/browser.ini"] + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Telemetry") diff --git a/toolkit/components/glean/tests/pytest/expect_helper.py b/toolkit/components/glean/tests/pytest/expect_helper.py new file mode 100644 index 0000000000..543ef04026 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/expect_helper.py @@ -0,0 +1,34 @@ +# 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 http://mozilla.org/MPL/2.0/. + +import inspect +import os + + +def expect(path, actual): + """ + Assert that the content of the file at `path` contains `actual`. + + If the environment variable `UPDATE_EXPECT` is set, the path content is updated to `actual`. + This allows to update the file contents easily. + """ + + callerframerecord = inspect.stack()[1] + frame = callerframerecord[0] + info = inspect.getframeinfo(frame) + msg = f""" +Unexpected content in {path} (at {info.filename}:{info.lineno}) + +If the code generation was changed, +run the test suite again with `UPDATE_EXPECT=1` set to update the test files. +""".strip() + + if "UPDATE_EXPECT" in os.environ: + with open(path, "w") as file: + file.write(actual) + + expected = None + with open(path, "r") as file: + expected = file.read() + assert actual == expected, msg diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Event b/toolkit/components/glean/tests/pytest/gifft_output_Event new file mode 100644 index 0000000000..114267fbfd --- /dev/null +++ b/toolkit/components/glean/tests/pytest/gifft_output_Event @@ -0,0 +1,69 @@ +// -*- mode: C++ -*- + +/* This file is auto-generated by run_glean_parser.py. + It is only for internal use by types in + toolkit/components/glean/bindings/private */ + +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/glean/bindings/GleanJSMetricsLookup.h" +#include "mozilla/glean/bindings/jog/JOG.h" +#include "mozilla/Maybe.h" +#include "mozilla/Telemetry.h" +#include <tuple> +#include "mozilla/DataMutex.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#ifndef mozilla_glean_EventGifftMap_h +#define mozilla_glean_EventGifftMap_h + +#define DYNAMIC_METRIC_BIT (26) +#define GLEAN_METRIC_ID(id) ((id) & ((1ULL << 27) - 1)) + +namespace mozilla::glean { + +using Telemetry::EventID; + + +static inline Maybe<EventID> EventIdForMetric(uint32_t aId) { + switch(aId) { + case 17: { // test.nested.event_metric + return Some(EventID::EventMetric_EnumNames_AreStrange); + } + case 18: { // test.nested.event_metric_with_extra + return Some(EventID::EventMetric_EnumName_WithExtra); + } + default: { + if (MOZ_UNLIKELY(aId & (1 << DYNAMIC_METRIC_BIT))) { + // Dynamic (runtime-registered) metric. Use its static (compiletime- + // registered) metric's telemetry_mirror mapping. + // ...if applicable. + + // Only JS can use dynamic (runtime-registered) metric ids. + MOZ_ASSERT(NS_IsMainThread()); + + auto metricName = JOG::GetMetricName(aId); + // All of these should have names, but the storage only lasts until + // XPCOMWillShutdown, so it might return `Nothing()`. + if (metricName.isSome()) { + auto maybeMetric = MetricByNameLookup(metricName.ref()); + if (maybeMetric.isSome()) { + uint32_t staticId = GLEAN_METRIC_ID(maybeMetric.value()); + // Let's ensure we don't infinite loop, huh. + MOZ_ASSERT(!(staticId & (1 << DYNAMIC_METRIC_BIT))); + return EventIdForMetric(staticId); + } + } + } + return Nothing(); + } + } +} + +} // namespace mozilla::glean + +#undef GLEAN_METRIC_ID +#undef DYNAMIC_METRIC_BIT + +#endif // mozilla_glean_EventGifftMaps_h diff --git a/toolkit/components/glean/tests/pytest/gifft_output_EventExtra b/toolkit/components/glean/tests/pytest/gifft_output_EventExtra new file mode 100644 index 0000000000..f5f2671d99 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/gifft_output_EventExtra @@ -0,0 +1,35 @@ +// -*- mode: C++ -*- + +/* This file is auto-generated by run_glean_parser.py. + It is only for internal use by types in + toolkit/components/glean/bindings/private */ + +#include "mozilla/glean/bindings/Event.h" +#include "mozilla/glean/GleanMetrics.h" + +namespace mozilla::glean { + +template <> +/*static*/ const nsCString impl::EventMetric<NoExtraKeys>::ExtraStringForKey(uint32_t aKey) { + MOZ_ASSERT_UNREACHABLE("What are you doing here? No extra keys!"); + return ""_ns; +} + +template <> +/*static*/ const nsCString impl::EventMetric<test_nested::EventMetricWithExtraExtra>::ExtraStringForKey(uint32_t aKey) { + using test_nested::EventMetricWithExtraExtra; + switch (aKey) { + case 0: { + return "an_extra_key"_ns; + } + case 1: { + return "another_extra_key"_ns; + } + default: { + MOZ_ASSERT_UNREACHABLE("Impossible event key reached."); + return ""_ns; + } + } +} + +}; // namespace mozilla::glean diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Histogram b/toolkit/components/glean/tests/pytest/gifft_output_Histogram new file mode 100644 index 0000000000..b13df96594 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/gifft_output_Histogram @@ -0,0 +1,144 @@ +// -*- mode: C++ -*- + +/* This file is auto-generated by run_glean_parser.py. + It is only for internal use by types in + toolkit/components/glean/bindings/private */ + +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/glean/bindings/GleanJSMetricsLookup.h" +#include "mozilla/glean/bindings/jog/JOG.h" +#include "mozilla/Maybe.h" +#include "mozilla/Telemetry.h" +#include <tuple> +#include "mozilla/DataMutex.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#ifndef mozilla_glean_HistogramGifftMap_h +#define mozilla_glean_HistogramGifftMap_h + +#define DYNAMIC_METRIC_BIT (26) +#define GLEAN_METRIC_ID(id) ((id) & ((1ULL << 27) - 1)) + +namespace mozilla::glean { + +using Telemetry::HistogramID; + + +using MetricId = uint32_t; // Same type as in api/src/private/mod.rs +using TimerId = uint64_t; // Same as in TimingDistribution.h. +using MetricTimerTuple = std::tuple<MetricId, TimerId>; +class MetricTimerTupleHashKey : public PLDHashEntryHdr { + public: + using KeyType = const MetricTimerTuple&; + using KeyTypePointer = const MetricTimerTuple*; + + explicit MetricTimerTupleHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + MetricTimerTupleHashKey(MetricTimerTupleHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), + mValue(std::move(aOther.mValue)) {} + ~MetricTimerTupleHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { + return std::get<0>(*aKey) == std::get<0>(mValue) && std::get<1>(*aKey) == std::get<1>(mValue); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + // Chosen because this is how nsIntegralHashKey does it. + return HashGeneric(std::get<0>(*aKey), std::get<1>(*aKey)); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const MetricTimerTuple mValue; +}; + +typedef StaticDataMutex<UniquePtr<nsTHashMap<MetricTimerTupleHashKey, TimeStamp>>> TimerToStampMutex; +static inline Maybe<TimerToStampMutex::AutoLock> GetTimerIdToStartsLock() { + static TimerToStampMutex sTimerIdToStarts("sTimerIdToStarts"); + auto lock = sTimerIdToStarts.Lock(); + // GIFFT will work up to the end of AppShutdownTelemetry. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) { + return Nothing(); + } + if (!*lock) { + *lock = MakeUnique<nsTHashMap<MetricTimerTupleHashKey, TimeStamp>>(); + RefPtr<nsIRunnable> cleanupFn = NS_NewRunnableFunction(__func__, [&] { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) { + auto lock = sTimerIdToStarts.Lock(); + *lock = nullptr; // deletes, see UniquePtr.h + return; + } + RunOnShutdown([&] { + auto lock = sTimerIdToStarts.Lock(); + *lock = nullptr; // deletes, see UniquePtr.h + }, ShutdownPhase::XPCOMWillShutdown); + }); + // Both getting the main thread and dispatching to it can fail. + // In that event we leak. Grab a pointer so we have something to NS_RELEASE + // in that case. + nsIRunnable* temp = cleanupFn.get(); + nsCOMPtr<nsIThread> mainThread; + if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread))) + || NS_FAILED(mainThread->Dispatch(cleanupFn.forget(), nsIThread::DISPATCH_NORMAL)) + ) { + // Failed to dispatch cleanup routine. + // First, un-leak the runnable (but only if we actually attempted dispatch) + if (!cleanupFn) { + NS_RELEASE(temp); + } + // Next, cleanup immediately, and allow metrics to try again later. + *lock = nullptr; + return Nothing(); + } + } + return Some(std::move(lock)); +} + +static Maybe<HistogramID> HistogramIdForMetric(uint32_t aId) { + switch(aId) { + case 3: { // test.custom_distribution_metric + return Some(HistogramID::SOME_LINEAR_HISTOGRAM); + } + case 10: { // test.memory_distribution_metric + return Some(HistogramID::SOME_MEM_HISTOGRAM_KB); + } + case 15: { // test.timing_distribution_metric + return Some(HistogramID::SOME_TIME_HISTOGRAM_MS); + } + default: { + if (MOZ_UNLIKELY(aId & (1 << DYNAMIC_METRIC_BIT))) { + // Dynamic (runtime-registered) metric. Use its static (compiletime- + // registered) metric's telemetry_mirror mapping. + // ...if applicable. + + // Only JS can use dynamic (runtime-registered) metric ids. + MOZ_ASSERT(NS_IsMainThread()); + + auto metricName = JOG::GetMetricName(aId); + // All of these should have names, but the storage only lasts until + // XPCOMWillShutdown, so it might return `Nothing()`. + if (metricName.isSome()) { + auto maybeMetric = MetricByNameLookup(metricName.ref()); + if (maybeMetric.isSome()) { + uint32_t staticId = GLEAN_METRIC_ID(maybeMetric.value()); + // Let's ensure we don't infinite loop, huh. + MOZ_ASSERT(!(staticId & (1 << DYNAMIC_METRIC_BIT))); + return HistogramIdForMetric(staticId); + } + } + } + return Nothing(); + } + } +} + +} // namespace mozilla::glean + +#undef GLEAN_METRIC_ID +#undef DYNAMIC_METRIC_BIT + +#endif // mozilla_glean_HistogramGifftMaps_h diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Scalar b/toolkit/components/glean/tests/pytest/gifft_output_Scalar new file mode 100644 index 0000000000..66d25a2629 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/gifft_output_Scalar @@ -0,0 +1,218 @@ +// -*- mode: C++ -*- + +/* This file is auto-generated by run_glean_parser.py. + It is only for internal use by types in + toolkit/components/glean/bindings/private */ + +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/glean/bindings/GleanJSMetricsLookup.h" +#include "mozilla/glean/bindings/jog/JOG.h" +#include "mozilla/Maybe.h" +#include "mozilla/Telemetry.h" +#include <tuple> +#include "mozilla/DataMutex.h" +#include "nsClassHashtable.h" +#include "nsTHashMap.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#ifndef mozilla_glean_ScalarGifftMap_h +#define mozilla_glean_ScalarGifftMap_h + +#define DYNAMIC_METRIC_BIT (26) +#define GLEAN_METRIC_ID(id) ((id) & ((1ULL << 27) - 1)) + +namespace mozilla::glean { + +using Telemetry::ScalarID; + +typedef nsUint32HashKey SubmetricIdHashKey; +typedef nsTHashMap<SubmetricIdHashKey, std::tuple<ScalarID, nsString>> + SubmetricToLabeledMirrorMapType; +typedef StaticDataMutex<UniquePtr<SubmetricToLabeledMirrorMapType>> + SubmetricToMirrorMutex; +static inline Maybe<SubmetricToMirrorMutex::AutoLock> GetLabeledMirrorLock() { + static SubmetricToMirrorMutex sLabeledMirrors("sLabeledMirrors"); + auto lock = sLabeledMirrors.Lock(); + // GIFFT will work up to the end of AppShutdownTelemetry. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) { + return Nothing(); + } + if (!*lock) { + *lock = MakeUnique<SubmetricToLabeledMirrorMapType>(); + RefPtr<nsIRunnable> cleanupFn = NS_NewRunnableFunction(__func__, [&] { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) { + auto lock = sLabeledMirrors.Lock(); + *lock = nullptr; // deletes, see UniquePtr.h + return; + } + RunOnShutdown([&] { + auto lock = sLabeledMirrors.Lock(); + *lock = nullptr; // deletes, see UniquePtr.h + }, ShutdownPhase::XPCOMWillShutdown); + }); + // Both getting the main thread and dispatching to it can fail. + // In that event we leak. Grab a pointer so we have something to NS_RELEASE + // in that case. + nsIRunnable* temp = cleanupFn.get(); + nsCOMPtr<nsIThread> mainThread; + if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread))) + || NS_FAILED(mainThread->Dispatch(cleanupFn.forget(), nsIThread::DISPATCH_NORMAL)) + ) { + // Failed to dispatch cleanup routine. + // First, un-leak the runnable (but only if we actually attempted dispatch) + if (!cleanupFn) { + NS_RELEASE(temp); + } + // Next, cleanup immediately, and allow metrics to try again later. + *lock = nullptr; + return Nothing(); + } + } + return Some(std::move(lock)); +} + +namespace { +class ScalarIDHashKey : public PLDHashEntryHdr { + public: + typedef const ScalarID& KeyType; + typedef const ScalarID* KeyTypePointer; + + explicit ScalarIDHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + ScalarIDHashKey(ScalarIDHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mValue(std::move(aOther.mValue)) {} + ~ScalarIDHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return static_cast<std::underlying_type<ScalarID>::type>(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const ScalarID mValue; +}; +} // namespace +typedef StaticDataMutex<UniquePtr<nsTHashMap<ScalarIDHashKey, TimeStamp>>> TimesToStartsMutex; +static inline Maybe<TimesToStartsMutex::AutoLock> GetTimesToStartsLock() { + static TimesToStartsMutex sTimespanStarts("sTimespanStarts"); + auto lock = sTimespanStarts.Lock(); + // GIFFT will work up to the end of AppShutdownTelemetry. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) { + return Nothing(); + } + if (!*lock) { + *lock = MakeUnique<nsTHashMap<ScalarIDHashKey, TimeStamp>>(); + RefPtr<nsIRunnable> cleanupFn = NS_NewRunnableFunction(__func__, [&] { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) { + auto lock = sTimespanStarts.Lock(); + *lock = nullptr; // deletes, see UniquePtr.h + return; + } + RunOnShutdown([&] { + auto lock = sTimespanStarts.Lock(); + *lock = nullptr; // deletes, see UniquePtr.h + }, ShutdownPhase::XPCOMWillShutdown); + }); + // Both getting the main thread and dispatching to it can fail. + // In that event we leak. Grab a pointer so we have something to NS_RELEASE + // in that case. + nsIRunnable* temp = cleanupFn.get(); + nsCOMPtr<nsIThread> mainThread; + if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread))) + || NS_FAILED(mainThread->Dispatch(cleanupFn.forget(), nsIThread::DISPATCH_NORMAL)) + ) { + // Failed to dispatch cleanup routine. + // First, un-leak the runnable (but only if we actually attempted dispatch) + if (!cleanupFn) { + NS_RELEASE(temp); + } + // Next, cleanup immediately, and allow metrics to try again later. + *lock = nullptr; + return Nothing(); + } + } + return Some(std::move(lock)); +} + +static inline bool IsSubmetricId(uint32_t aId) { + // Submetrics have the 2^25 bit set. + // (ID_BITS - ID_SIGNAL_BITS, keep it in sync with js.py). + return (aId & (1 << 25)) > 0; +} + +static inline Maybe<ScalarID> ScalarIdForMetric(uint32_t aId) { + switch(aId) { + case 1: { // test.boolean_metric + return Some(ScalarID::SOME_BOOL_SCALAR); + } + case 2: { // test.counter_metric + return Some(ScalarID::SOME_UINT_SCALAR); + } + case 4: { // test.labeled_boolean_metric + return Some(ScalarID::SOME_KEYED_BOOL_SCALAR); + } + case 5: { // test.labeled_boolean_metric_labels + return Some(ScalarID::SOME_OTHER_KEYED_BOOL_SCALAR); + } + case 6: { // test.labeled_counter_metric + return Some(ScalarID::SOME_KEYED_UINT_SCALAR); + } + case 7: { // test.labeled_counter_metric_labels + return Some(ScalarID::SOME_OTHER_KEYED_UINT_SCALAR); + } + case 11: { // test.string_list_metric + return Some(ScalarID::YET_ANOTHER_KEYED_BOOL_SCALAR); + } + case 12: { // test.string_metric + return Some(ScalarID::SOME_STRING_SCALAR); + } + case 14: { // test.timespan_metric + return Some(ScalarID::SOME_OTHER_UINT_SCALAR); + } + case 16: { // test.nested.datetime_metric + return Some(ScalarID::SOME_STILL_OTHER_STRING_SCALAR); + } + case 20: { // test.nested.quantity_metric + return Some(ScalarID::TELEMETRY_TEST_MIRROR_FOR_QUANTITY); + } + case 23: { // test.nested.uuid_metric + return Some(ScalarID::SOME_OTHER_STRING_SCALAR); + } + default: { + if (MOZ_UNLIKELY(aId & (1 << DYNAMIC_METRIC_BIT))) { + // Dynamic (runtime-registered) metric. Use its static (compiletime- + // registered) metric's telemetry_mirror mapping. + // ...if applicable. + + // Only JS can use dynamic (runtime-registered) metric ids. + MOZ_ASSERT(NS_IsMainThread()); + + auto metricName = JOG::GetMetricName(aId); + // All of these should have names, but the storage only lasts until + // XPCOMWillShutdown, so it might return `Nothing()`. + if (metricName.isSome()) { + auto maybeMetric = MetricByNameLookup(metricName.ref()); + if (maybeMetric.isSome()) { + uint32_t staticId = GLEAN_METRIC_ID(maybeMetric.value()); + // Let's ensure we don't infinite loop, huh. + MOZ_ASSERT(!(staticId & (1 << DYNAMIC_METRIC_BIT))); + return ScalarIdForMetric(staticId); + } + } + } + return Nothing(); + } + } +} + +} // namespace mozilla::glean + +#undef GLEAN_METRIC_ID +#undef DYNAMIC_METRIC_BIT + +#endif // mozilla_glean_ScalarGifftMaps_h diff --git a/toolkit/components/glean/tests/pytest/jogfile_output b/toolkit/components/glean/tests/pytest/jogfile_output new file mode 100644 index 0000000000..dd6a0869f0 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/jogfile_output @@ -0,0 +1,331 @@ +{ + "metrics": { + "test": [ + [ + "boolean", + "boolean_metric", + [ + "metrics" + ], + "application", + false + ], + [ + "counter", + "counter_metric", + [ + "metrics" + ], + "application", + false + ], + [ + "custom_distribution", + "custom_distribution_metric", + [ + "metrics" + ], + "application", + false, + { + "bucket_count": 100, + "histogram_type": "linear", + "range_max": 100, + "range_min": 0 + } + ], + [ + "labeled_boolean", + "labeled_boolean_metric", + [ + "metrics" + ], + "application", + false, + { + "ordered_labels": null + } + ], + [ + "labeled_boolean", + "labeled_boolean_metric_labels", + [ + "metrics" + ], + "application", + false, + { + "ordered_labels": [ + "one_label", + "two_labels", + "three_labels", + "four_labels", + "five_labels", + "six_labels", + "seven_labels", + "eight_labels", + "nine_labels", + "ten_labels" + ] + } + ], + [ + "labeled_counter", + "labeled_counter_metric", + [ + "metrics" + ], + "application", + false, + { + "ordered_labels": null + } + ], + [ + "labeled_counter", + "labeled_counter_metric_labels", + [ + "metrics" + ], + "application", + false, + { + "ordered_labels": [ + "one_label", + "two_labels" + ] + } + ], + [ + "labeled_string", + "labeled_string_metric", + [ + "metrics" + ], + "application", + false, + { + "ordered_labels": null + } + ], + [ + "labeled_string", + "labeled_string_metric_labels", + [ + "metrics" + ], + "application", + false, + { + "ordered_labels": [ + "one_label", + "two_labels" + ] + } + ], + [ + "memory_distribution", + "memory_distribution_metric", + [ + "metrics" + ], + "application", + false, + { + "memory_unit": "kilobyte" + } + ], + [ + "string_list", + "string_list_metric", + [ + "metrics" + ], + "application", + false + ], + [ + "string", + "string_metric", + [ + "metrics" + ], + "application", + false + ], + [ + "text", + "text_metric", + [ + "metrics" + ], + "application", + false + ], + [ + "timespan", + "timespan_metric", + [ + "metrics" + ], + "application", + false, + { + "time_unit": "millisecond" + } + ], + [ + "timing_distribution", + "timing_distribution_metric", + [ + "metrics" + ], + "application", + false, + { + "time_unit": "nanosecond" + } + ] + ], + "test.nested": [ + [ + "datetime", + "datetime_metric", + [ + "metrics" + ], + "application", + false, + { + "time_unit": "millisecond" + } + ], + [ + "event", + "event_metric", + [ + "events" + ], + "ping", + false, + { + "allowed_extra_keys": [] + } + ], + [ + "event", + "event_metric_with_extra", + [ + "events" + ], + "ping", + false, + { + "allowed_extra_keys": [ + "an_extra_key", + "another_extra_key" + ] + } + ], + [ + "denominator", + "external_denominator", + [ + "metrics" + ], + "ping", + false, + { + "numerators": [ + [ + "rate_with_external_denominator", + "test.nested", + [ + "metrics" + ], + "ping", + false, + null + ] + ] + } + ], + [ + "quantity", + "quantity_metric", + [ + "metrics" + ], + "ping", + false + ], + [ + "rate", + "rate_metric", + [ + "metrics" + ], + "ping", + false + ], + [ + "rate", + "rate_with_external_denominator", + [ + "metrics" + ], + "ping", + false + ], + [ + "uuid", + "uuid_metric", + [ + "metrics" + ], + "application", + false + ] + ] + }, + "pings": [ + [ + "not-baseline", + true, + false, + [ + "background", + "dirty_startup", + "foreground" + ] + ], + [ + "not-deletion-request", + true, + true, + [] + ], + [ + "not-events", + true, + false, + [ + "background", + "max_capacity", + "startup" + ] + ], + [ + "not-metrics", + true, + false, + [ + "overdue", + "reschedule", + "today", + "tomorrow", + "upgrade" + ] + ] + ] +}
\ No newline at end of file diff --git a/toolkit/components/glean/tests/pytest/metrics_expires_versions_test.yaml b/toolkit/components/glean/tests/pytest/metrics_expires_versions_test.yaml new file mode 100644 index 0000000000..ea73a6465e --- /dev/null +++ b/toolkit/components/glean/tests/pytest/metrics_expires_versions_test.yaml @@ -0,0 +1,84 @@ +# 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 http://mozilla.org/MPL/2.0/. + +# This file is FOR TESTING PURPOSES ONLY. + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 + +test: + expired1: + type: boolean + expires: 41 + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1664306 + data_reviews: + - https://example.com + no_lint: + - EXPIRED + + expired2: + type: labeled_boolean + expires: 42 + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1664306 + data_reviews: + - https://example.com + no_lint: + - EXPIRED + + unexpired: + type: labeled_boolean + expires: 100 + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1664306 + data_reviews: + - https://example.com + labels: + - one_label + - two_labels + + never: + type: string + expires: never + description: A never-expiring metric + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1664306 + data_reviews: + - https://example.com + + always: + type: string + expires: expired + description: An already-expired metric + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1664306 + data_reviews: + - https://example.com + no_lint: + - EXPIRED diff --git a/toolkit/components/glean/tests/pytest/metrics_test.yaml b/toolkit/components/glean/tests/pytest/metrics_test.yaml new file mode 100644 index 0000000000..fe2b973ae0 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/metrics_test.yaml @@ -0,0 +1,384 @@ +# 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 http://mozilla.org/MPL/2.0/. + +# This file defines the metrics that are recorded by the Glean SDK. They are +# automatically converted to platform-specific code at build time using the +# `glean_parser` PyPI package. + +# This file is presently for Internal FOG Use Only. +# You should not add metrics here until probably about January of 2021. +# If you're looking for the metrics.yaml for Geckoveiw Streaming Telemetry, +# you can find that one in toolkit/components/telemetry/geckoview/streaming. + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 + +test: + boolean_metric: + type: boolean + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_BOOL_SCALAR + + labeled_boolean_metric: + type: labeled_boolean + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_KEYED_BOOL_SCALAR + + labeled_boolean_metric_labels: + type: labeled_boolean + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + labels: + - one_label + - two_labels + - three_labels + - four_labels + - five_labels + - six_labels + - seven_labels + - eight_labels + - nine_labels + - ten_labels + telemetry_mirror: SOME_OTHER_KEYED_BOOL_SCALAR + + counter_metric: + type: counter + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_UINT_SCALAR + + labeled_counter_metric: + type: labeled_counter + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_KEYED_UINT_SCALAR + + labeled_counter_metric_labels: + type: labeled_counter + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + labels: + - one_label + - two_labels + telemetry_mirror: SOME_OTHER_KEYED_UINT_SCALAR + + string_metric: + type: string + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_STRING_SCALAR + + labeled_string_metric: + type: labeled_string + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + + labeled_string_metric_labels: + type: labeled_string + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + labels: + - one_label + - two_labels + + string_list_metric: + type: string_list + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: YET_ANOTHER_KEYED_BOOL_SCALAR + + text_metric: + type: text + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1828528 + data_reviews: + - https://example.com + + + timespan_metric: + type: timespan + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_OTHER_UINT_SCALAR + + timing_distribution_metric: + type: timing_distribution + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_TIME_HISTOGRAM_MS + + memory_distribution_metric: + type: memory_distribution + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + memory_unit: kilobyte + telemetry_mirror: SOME_MEM_HISTOGRAM_KB + + custom_distribution_metric: + type: custom_distribution + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + range_min: 0 + range_max: 100 + bucket_count: 100 + histogram_type: linear + telemetry_mirror: SOME_LINEAR_HISTOGRAM + +test.nested: + uuid_metric: + type: uuid + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_OTHER_STRING_SCALAR + + datetime_metric: + type: datetime + expires: never + description: | + A multi-line + description + lifetime: application + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: SOME_STILL_OTHER_STRING_SCALAR + + event_metric: + type: event + expires: never + description: | + A multi-line + description + lifetime: ping + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + telemetry_mirror: EventMetric_EnumNames_AreStrange + + event_metric_with_extra: + type: event + expires: never + description: | + A multi-line + description + lifetime: ping + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1635260/ + data_reviews: + - https://example.com + extra_keys: + an_extra_key: + type: string + description: An extra key description + another_extra_key: + type: string + description: Another extra key description + telemetry_mirror: EventMetric_EnumName_WithExtra + + quantity_metric: + type: quantity + unit: someunit + expires: never + description: | + A multi-line + description + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1704846/ + data_reviews: + - https://example.com + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_QUANTITY + + rate_metric: + type: rate + expires: never + description: | + A multi-line + description + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1704846/ + data_reviews: + - https://example.com + + rate_with_external_denominator: + type: rate + denominator_metric: test.nested.external_denominator + expires: never + description: | + A multi-line + description + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1704846/ + data_reviews: + - https://example.com + + external_denominator: + type: counter + expires: never + description: | + A multi-line + description + notification_emails: + - glean-team@mozilla.com + bugs: + - https://bugzilla.mozilla.org/1704846/ + data_reviews: + - https://example.com diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output b/toolkit/components/glean/tests/pytest/metrics_test_output new file mode 100644 index 0000000000..6301597709 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/metrics_test_output @@ -0,0 +1,901 @@ +// -*- mode: Rust -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. + +/* 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 http://mozilla.org/MPL/2.0/. */ + +pub enum DynamicLabel { } + +pub mod test { + use crate::private::*; + use glean::CommonMetricData; + #[allow(unused_imports)] // HistogramType might be unusued, let's avoid warnings + use glean::HistogramType; + use once_cell::sync::Lazy; + + #[allow(non_upper_case_globals)] + /// generated from test.boolean_metric + /// + /// A multi-line + /// description + pub static boolean_metric: Lazy<BooleanMetric> = Lazy::new(|| { + BooleanMetric::new(1.into(), CommonMetricData { + name: "boolean_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.counter_metric + /// + /// A multi-line + /// description + pub static counter_metric: Lazy<CounterMetric> = Lazy::new(|| { + CounterMetric::new(2.into(), CommonMetricData { + name: "counter_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.custom_distribution_metric + /// + /// A multi-line + /// description + pub static custom_distribution_metric: Lazy<CustomDistributionMetric> = Lazy::new(|| { + CustomDistributionMetric::new(3.into(), CommonMetricData { + name: "custom_distribution_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, 0, 100, 100, HistogramType::Linear) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.labeled_boolean_metric + /// + /// A multi-line + /// description + pub static labeled_boolean_metric: Lazy<LabeledMetric<LabeledBooleanMetric, super::DynamicLabel>> = Lazy::new(|| { + LabeledMetric::new(4.into(), CommonMetricData { + name: "labeled_boolean_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, None) + }); + + #[repr(u16)] + pub enum LabeledBooleanMetricLabelsLabel { + OneLabel = 0, + TwoLabels = 1, + ThreeLabels = 2, + FourLabels = 3, + FiveLabels = 4, + SixLabels = 5, + SevenLabels = 6, + EightLabels = 7, + NineLabels = 8, + TenLabels = 9, + __Other__, + } + impl From<u16> for LabeledBooleanMetricLabelsLabel { + fn from(v: u16) -> Self { + match v { + 0 => Self::OneLabel, + 1 => Self::TwoLabels, + 2 => Self::ThreeLabels, + 3 => Self::FourLabels, + 4 => Self::FiveLabels, + 5 => Self::SixLabels, + 6 => Self::SevenLabels, + 7 => Self::EightLabels, + 8 => Self::NineLabels, + 9 => Self::TenLabels, + _ => Self::__Other__, + } + } + } + impl Into<&'static str> for LabeledBooleanMetricLabelsLabel { + fn into(self) -> &'static str { + match self { + Self::OneLabel => "one_label", + Self::TwoLabels => "two_labels", + Self::ThreeLabels => "three_labels", + Self::FourLabels => "four_labels", + Self::FiveLabels => "five_labels", + Self::SixLabels => "six_labels", + Self::SevenLabels => "seven_labels", + Self::EightLabels => "eight_labels", + Self::NineLabels => "nine_labels", + Self::TenLabels => "ten_labels", + Self::__Other__ => "__other__", + } + } + } + #[allow(non_upper_case_globals)] + /// generated from test.labeled_boolean_metric_labels + /// + /// A multi-line + /// description + pub static labeled_boolean_metric_labels: Lazy<LabeledMetric<LabeledBooleanMetric, LabeledBooleanMetricLabelsLabel>> = Lazy::new(|| { + LabeledMetric::new(5.into(), CommonMetricData { + name: "labeled_boolean_metric_labels".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, Some(vec![::std::borrow::Cow::from("eight_labels"), ::std::borrow::Cow::from("five_labels"), ::std::borrow::Cow::from("four_labels"), ::std::borrow::Cow::from("nine_labels"), ::std::borrow::Cow::from("one_label"), ::std::borrow::Cow::from("seven_labels"), ::std::borrow::Cow::from("six_labels"), ::std::borrow::Cow::from("ten_labels"), ::std::borrow::Cow::from("three_labels"), ::std::borrow::Cow::from("two_labels")])) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.labeled_counter_metric + /// + /// A multi-line + /// description + pub static labeled_counter_metric: Lazy<LabeledMetric<LabeledCounterMetric, super::DynamicLabel>> = Lazy::new(|| { + LabeledMetric::new(6.into(), CommonMetricData { + name: "labeled_counter_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, None) + }); + + #[repr(u16)] + pub enum LabeledCounterMetricLabelsLabel { + OneLabel = 0, + TwoLabels = 1, + __Other__, + } + impl From<u16> for LabeledCounterMetricLabelsLabel { + fn from(v: u16) -> Self { + match v { + 0 => Self::OneLabel, + 1 => Self::TwoLabels, + _ => Self::__Other__, + } + } + } + impl Into<&'static str> for LabeledCounterMetricLabelsLabel { + fn into(self) -> &'static str { + match self { + Self::OneLabel => "one_label", + Self::TwoLabels => "two_labels", + Self::__Other__ => "__other__", + } + } + } + #[allow(non_upper_case_globals)] + /// generated from test.labeled_counter_metric_labels + /// + /// A multi-line + /// description + pub static labeled_counter_metric_labels: Lazy<LabeledMetric<LabeledCounterMetric, LabeledCounterMetricLabelsLabel>> = Lazy::new(|| { + LabeledMetric::new(7.into(), CommonMetricData { + name: "labeled_counter_metric_labels".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, Some(vec![::std::borrow::Cow::from("one_label"), ::std::borrow::Cow::from("two_labels")])) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.labeled_string_metric + /// + /// A multi-line + /// description + pub static labeled_string_metric: Lazy<LabeledMetric<LabeledStringMetric, super::DynamicLabel>> = Lazy::new(|| { + LabeledMetric::new(8.into(), CommonMetricData { + name: "labeled_string_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, None) + }); + + #[repr(u16)] + pub enum LabeledStringMetricLabelsLabel { + OneLabel = 0, + TwoLabels = 1, + __Other__, + } + impl From<u16> for LabeledStringMetricLabelsLabel { + fn from(v: u16) -> Self { + match v { + 0 => Self::OneLabel, + 1 => Self::TwoLabels, + _ => Self::__Other__, + } + } + } + impl Into<&'static str> for LabeledStringMetricLabelsLabel { + fn into(self) -> &'static str { + match self { + Self::OneLabel => "one_label", + Self::TwoLabels => "two_labels", + Self::__Other__ => "__other__", + } + } + } + #[allow(non_upper_case_globals)] + /// generated from test.labeled_string_metric_labels + /// + /// A multi-line + /// description + pub static labeled_string_metric_labels: Lazy<LabeledMetric<LabeledStringMetric, LabeledStringMetricLabelsLabel>> = Lazy::new(|| { + LabeledMetric::new(9.into(), CommonMetricData { + name: "labeled_string_metric_labels".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, Some(vec![::std::borrow::Cow::from("one_label"), ::std::borrow::Cow::from("two_labels")])) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.memory_distribution_metric + /// + /// A multi-line + /// description + pub static memory_distribution_metric: Lazy<MemoryDistributionMetric> = Lazy::new(|| { + MemoryDistributionMetric::new(10.into(), CommonMetricData { + name: "memory_distribution_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, MemoryUnit::Kilobyte) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.string_list_metric + /// + /// A multi-line + /// description + pub static string_list_metric: Lazy<StringListMetric> = Lazy::new(|| { + StringListMetric::new(11.into(), CommonMetricData { + name: "string_list_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.string_metric + /// + /// A multi-line + /// description + pub static string_metric: Lazy<StringMetric> = Lazy::new(|| { + StringMetric::new(12.into(), CommonMetricData { + name: "string_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.text_metric + /// + /// A multi-line + /// description + pub static text_metric: Lazy<TextMetric> = Lazy::new(|| { + TextMetric::new(13.into(), CommonMetricData { + name: "text_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.timespan_metric + /// + /// A multi-line + /// description + pub static timespan_metric: Lazy<TimespanMetric> = Lazy::new(|| { + TimespanMetric::new(14.into(), CommonMetricData { + name: "timespan_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, TimeUnit::Millisecond) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.timing_distribution_metric + /// + /// A multi-line + /// description + pub static timing_distribution_metric: Lazy<TimingDistributionMetric> = Lazy::new(|| { + TimingDistributionMetric::new(15.into(), CommonMetricData { + name: "timing_distribution_metric".into(), + category: "test".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, TimeUnit::Nanosecond) + }); + +} +pub mod test_nested { + use crate::private::*; + use glean::CommonMetricData; + #[allow(unused_imports)] // HistogramType might be unusued, let's avoid warnings + use glean::HistogramType; + use once_cell::sync::Lazy; + + #[allow(non_upper_case_globals)] + /// generated from test.nested.datetime_metric + /// + /// A multi-line + /// description + pub static datetime_metric: Lazy<DatetimeMetric> = Lazy::new(|| { + DatetimeMetric::new(16.into(), CommonMetricData { + name: "datetime_metric".into(), + category: "test.nested".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }, TimeUnit::Millisecond) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.nested.event_metric + /// + /// A multi-line + /// description + pub static event_metric: Lazy<EventMetric<NoExtraKeys>> = Lazy::new(|| { + EventMetric::new(17.into(), CommonMetricData { + name: "event_metric".into(), + category: "test.nested".into(), + send_in_pings: vec!["events".into()], + lifetime: Lifetime::Ping, + disabled: false, + ..Default::default() + }) + }); + + #[derive(Default, Debug, Clone, Hash, Eq, PartialEq)] + pub struct EventMetricWithExtraExtra { + pub an_extra_key: Option<String>, + pub another_extra_key: Option<String>, + } + + impl ExtraKeys for EventMetricWithExtraExtra { + const ALLOWED_KEYS: &'static [&'static str] = &["an_extra_key", "another_extra_key"]; + + fn into_ffi_extra(self) -> ::std::collections::HashMap<String, String> { + let mut map = ::std::collections::HashMap::new(); + self.an_extra_key.and_then(|val| map.insert("an_extra_key".into(), val.to_string())); + self.another_extra_key.and_then(|val| map.insert("another_extra_key".into(), val.to_string())); + map + } + } + #[allow(non_upper_case_globals)] + /// generated from test.nested.event_metric_with_extra + /// + /// A multi-line + /// description + pub static event_metric_with_extra: Lazy<EventMetric<EventMetricWithExtraExtra>> = Lazy::new(|| { + EventMetric::new(18.into(), CommonMetricData { + name: "event_metric_with_extra".into(), + category: "test.nested".into(), + send_in_pings: vec!["events".into()], + lifetime: Lifetime::Ping, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.nested.external_denominator + /// + /// A multi-line + /// description + pub static external_denominator: Lazy<DenominatorMetric> = Lazy::new(|| { + DenominatorMetric::new(19.into(), CommonMetricData { + name: "external_denominator".into(), + category: "test.nested".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Ping, + disabled: false, + ..Default::default() + }, vec![CommonMetricData {name: "rate_with_external_denominator".into(), category: "test.nested".into(), send_in_pings: vec!["metrics".into()], lifetime: Lifetime::Ping, disabled: false, ..Default::default()}]) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.nested.quantity_metric + /// + /// A multi-line + /// description + pub static quantity_metric: Lazy<QuantityMetric> = Lazy::new(|| { + QuantityMetric::new(20.into(), CommonMetricData { + name: "quantity_metric".into(), + category: "test.nested".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Ping, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.nested.rate_metric + /// + /// A multi-line + /// description + pub static rate_metric: Lazy<RateMetric> = Lazy::new(|| { + RateMetric::new(21.into(), CommonMetricData { + name: "rate_metric".into(), + category: "test.nested".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Ping, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.nested.rate_with_external_denominator + /// + /// A multi-line + /// description + pub static rate_with_external_denominator: Lazy<NumeratorMetric> = Lazy::new(|| { + NumeratorMetric::new(22.into(), CommonMetricData { + name: "rate_with_external_denominator".into(), + category: "test.nested".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Ping, + disabled: false, + ..Default::default() + }) + }); + + #[allow(non_upper_case_globals)] + /// generated from test.nested.uuid_metric + /// + /// A multi-line + /// description + pub static uuid_metric: Lazy<UuidMetric> = Lazy::new(|| { + UuidMetric::new(23.into(), CommonMetricData { + name: "uuid_metric".into(), + category: "test.nested".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Application, + disabled: false, + ..Default::default() + }) + }); + +} + +#[allow(dead_code)] +pub(crate) mod __glean_metric_maps { + use std::collections::HashMap; + + use crate::metrics::extra_keys_len; + use crate::private::*; + use once_cell::sync::Lazy; + + pub static BOOLEAN_MAP: Lazy<HashMap<MetricId, &Lazy<BooleanMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(1.into(), &super::test::boolean_metric); + map + }); + + pub static COUNTER_MAP: Lazy<HashMap<MetricId, &Lazy<CounterMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(2.into(), &super::test::counter_metric); + map + }); + + pub static CUSTOM_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<CustomDistributionMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(3.into(), &super::test::custom_distribution_metric); + map + }); + + pub static MEMORY_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<MemoryDistributionMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(10.into(), &super::test::memory_distribution_metric); + map + }); + + pub static STRING_LIST_MAP: Lazy<HashMap<MetricId, &Lazy<StringListMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(11.into(), &super::test::string_list_metric); + map + }); + + pub static STRING_MAP: Lazy<HashMap<MetricId, &Lazy<StringMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(12.into(), &super::test::string_metric); + map + }); + + pub static TEXT_MAP: Lazy<HashMap<MetricId, &Lazy<TextMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(13.into(), &super::test::text_metric); + map + }); + + pub static TIMESPAN_MAP: Lazy<HashMap<MetricId, &Lazy<TimespanMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(14.into(), &super::test::timespan_metric); + map + }); + + pub static TIMING_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<TimingDistributionMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(15.into(), &super::test::timing_distribution_metric); + map + }); + + pub static DATETIME_MAP: Lazy<HashMap<MetricId, &Lazy<DatetimeMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(16.into(), &super::test_nested::datetime_metric); + map + }); + + pub static DENOMINATOR_MAP: Lazy<HashMap<MetricId, &Lazy<DenominatorMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(19.into(), &super::test_nested::external_denominator); + map + }); + + pub static QUANTITY_MAP: Lazy<HashMap<MetricId, &Lazy<QuantityMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(20.into(), &super::test_nested::quantity_metric); + map + }); + + pub static RATE_MAP: Lazy<HashMap<MetricId, &Lazy<RateMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(21.into(), &super::test_nested::rate_metric); + map + }); + + pub static NUMERATOR_MAP: Lazy<HashMap<MetricId, &Lazy<NumeratorMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(22.into(), &super::test_nested::rate_with_external_denominator); + map + }); + + pub static UUID_MAP: Lazy<HashMap<MetricId, &Lazy<UuidMetric>>> = Lazy::new(|| { + let mut map = HashMap::with_capacity(1); + map.insert(23.into(), &super::test_nested::uuid_metric); + map + }); + + + /// Wrapper to record an event based on its metric ID. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `extra` - An map of (extra key id, string) pairs. + /// The map will be decoded into the appropriate `ExtraKeys` type. + /// # Returns + /// + /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`, + /// or an `EventRecordingError::InvalidId` if no event by that ID exists + /// or an `EventRecordingError::InvalidExtraKey` if the `extra` map could not be deserialized. + pub(crate) fn record_event_by_id(metric_id: u32, extra: HashMap<String, String>) -> Result<(), EventRecordingError> { + match metric_id { + 17 => { + assert!( + extra_keys_len(&super::test_nested::event_metric) != 0 || extra.is_empty(), + "No extra keys allowed, but some were passed" + ); + + super::test_nested::event_metric.record_raw(extra); + Ok(()) + } + 18 => { + assert!( + extra_keys_len(&super::test_nested::event_metric_with_extra) != 0 || extra.is_empty(), + "No extra keys allowed, but some were passed" + ); + + super::test_nested::event_metric_with_extra.record_raw(extra); + Ok(()) + } + _ => Err(EventRecordingError::InvalidId), + } + } + + /// Wrapper to record an event based on its metric ID, with a provided timestamp. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `timestamp` - The time at which this event was recorded. + /// * `extra` - An map of (extra key id, string) pairs. + /// The map will be decoded into the appropriate `ExtraKeys` type. + /// # Returns + /// + /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`, + /// or an `EventRecordingError::InvalidId` if no event by that ID exists + /// or an `EventRecordingError::InvalidExtraKey` if the event doesn't take extra pairs, + /// but some are passed in. + pub(crate) fn record_event_by_id_with_time(metric_id: MetricId, timestamp: u64, extra: HashMap<String, String>) -> Result<(), EventRecordingError> { + match metric_id { + MetricId(17) => { + if extra_keys_len(&super::test_nested::event_metric) == 0 && !extra.is_empty() { + return Err(EventRecordingError::InvalidExtraKey); + } + + super::test_nested::event_metric.record_with_time(timestamp, extra); + Ok(()) + } + MetricId(18) => { + if extra_keys_len(&super::test_nested::event_metric_with_extra) == 0 && !extra.is_empty() { + return Err(EventRecordingError::InvalidExtraKey); + } + + super::test_nested::event_metric_with_extra.record_with_time(timestamp, extra); + Ok(()) + } + _ => Err(EventRecordingError::InvalidId), + } + } + + /// Wrapper to get the currently stored events for event metric. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `ping_name` - (Optional) The ping name to look into. + /// Defaults to the first value in `send_in_pings`. + /// + /// # Returns + /// + /// Returns the recorded events or `None` if nothing stored. + /// + /// # Panics + /// + /// Panics if no event by the given metric ID could be found. + pub(crate) fn event_test_get_value_wrapper(metric_id: u32, ping_name: Option<String>) -> Option<Vec<RecordedEvent>> { + match metric_id { + 17 => super::test_nested::event_metric.test_get_value(ping_name.as_deref()), + 18 => super::test_nested::event_metric_with_extra.test_get_value(ping_name.as_deref()), + _ => panic!("No event for metric id {}", metric_id), + } + } + + /// Check the provided event for errors. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `ping_name` - (Optional) The ping name to look into. + /// Defaults to the first value in `send_in_pings`. + /// + /// # Returns + /// + /// Returns a string for the recorded error or `None`. + /// + /// # Panics + /// + /// Panics if no event by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn event_test_get_error(metric_id: u32) -> Option<String> { + #[cfg(feature = "with_gecko")] + match metric_id { + 17 => test_get_errors!(super::test_nested::event_metric), + 18 => test_get_errors!(super::test_nested::event_metric_with_extra), + _ => panic!("No event for metric id {}", metric_id), + } + + #[cfg(not(feature = "with_gecko"))] + { + return None; + } + } + + /// Gets the submetric from the specified labeled_boolean metric. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `label` - The label identifying the boolean submetric. + /// + /// # Returns + /// + /// Returns the boolean submetric. + /// + /// # Panics + /// + /// Panics if no labeled_boolean by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn labeled_boolean_get(metric_id: u32, label: &str) -> LabeledBooleanMetric { + match metric_id { + 4 => super::test::labeled_boolean_metric.get(label), + 5 => super::test::labeled_boolean_metric_labels.get(label), + _ => panic!("No labeled_boolean for metric id {}", metric_id), + } + } + + /// Gets the submetric from the specified labeled_boolean metric, by enum. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `label_enum` - The label enum identifying the boolean submetric. + /// + /// # Returns + /// + /// Returns the boolean submetric. + /// + /// # Panics + /// + /// Panics if no labeled_boolean by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn labeled_boolean_enum_get(metric_id: u32, label_enum: u16) -> LabeledBooleanMetric { + match metric_id { + 5 => super::test::labeled_boolean_metric_labels.get(labeled_enum_to_str(metric_id, label_enum)), + _ => panic!("No labeled_boolean for metric id {}", metric_id), + } + } + /// Gets the submetric from the specified labeled_counter metric. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `label` - The label identifying the counter submetric. + /// + /// # Returns + /// + /// Returns the counter submetric. + /// + /// # Panics + /// + /// Panics if no labeled_counter by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn labeled_counter_get(metric_id: u32, label: &str) -> LabeledCounterMetric { + match metric_id { + 6 => super::test::labeled_counter_metric.get(label), + 7 => super::test::labeled_counter_metric_labels.get(label), + _ => panic!("No labeled_counter for metric id {}", metric_id), + } + } + + /// Gets the submetric from the specified labeled_counter metric, by enum. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `label_enum` - The label enum identifying the counter submetric. + /// + /// # Returns + /// + /// Returns the counter submetric. + /// + /// # Panics + /// + /// Panics if no labeled_counter by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn labeled_counter_enum_get(metric_id: u32, label_enum: u16) -> LabeledCounterMetric { + match metric_id { + 7 => super::test::labeled_counter_metric_labels.get(labeled_enum_to_str(metric_id, label_enum)), + _ => panic!("No labeled_counter for metric id {}", metric_id), + } + } + /// Gets the submetric from the specified labeled_string metric. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `label` - The label identifying the string submetric. + /// + /// # Returns + /// + /// Returns the string submetric. + /// + /// # Panics + /// + /// Panics if no labeled_string by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn labeled_string_get(metric_id: u32, label: &str) -> LabeledStringMetric { + match metric_id { + 8 => super::test::labeled_string_metric.get(label), + 9 => super::test::labeled_string_metric_labels.get(label), + _ => panic!("No labeled_string for metric id {}", metric_id), + } + } + + /// Gets the submetric from the specified labeled_string metric, by enum. + /// + /// # Arguments + /// + /// * `metric_id` - The metric's ID to look up + /// * `label_enum` - The label enum identifying the string submetric. + /// + /// # Returns + /// + /// Returns the string submetric. + /// + /// # Panics + /// + /// Panics if no labeled_string by the given metric ID could be found. + #[allow(unused_variables)] + pub(crate) fn labeled_string_enum_get(metric_id: u32, label_enum: u16) -> LabeledStringMetric { + match metric_id { + 9 => super::test::labeled_string_metric_labels.get(labeled_enum_to_str(metric_id, label_enum)), + _ => panic!("No labeled_string for metric id {}", metric_id), + } + } + + pub(crate) fn labeled_enum_to_str(metric_id: u32, label: u16) -> &'static str { + match metric_id { + 5 => super::test::LabeledBooleanMetricLabelsLabel::from(label).into(), + 7 => super::test::LabeledCounterMetricLabelsLabel::from(label).into(), + 9 => super::test::LabeledStringMetricLabelsLabel::from(label).into(), + _ => panic!("Can't turn label enum to string for metric {} which isn't a labeled metric with static labels", metric_id), + } + } + + pub(crate) mod submetric_maps { + use std::sync::{ + atomic::AtomicU32, + RwLock, + }; + use super::*; + + pub(crate) const SUBMETRIC_BIT: u32 = 25; + pub(crate) static NEXT_LABELED_SUBMETRIC_ID: AtomicU32 = AtomicU32::new((1 << SUBMETRIC_BIT) + 1); + pub(crate) static LABELED_METRICS_TO_IDS: Lazy<RwLock<HashMap<(u32, String), u32>>> = Lazy::new(|| + RwLock::new(HashMap::new()) + ); + pub(crate) static LABELED_ENUMS_TO_IDS: Lazy<RwLock<HashMap<(u32, u16), u32>>> = Lazy::new(|| + RwLock::new(HashMap::new()) + ); + + pub static BOOLEAN_MAP: Lazy<RwLock<HashMap<MetricId, LabeledBooleanMetric>>> = Lazy::new(|| + RwLock::new(HashMap::new()) + ); + pub static COUNTER_MAP: Lazy<RwLock<HashMap<MetricId, LabeledCounterMetric>>> = Lazy::new(|| + RwLock::new(HashMap::new()) + ); + pub static STRING_MAP: Lazy<RwLock<HashMap<MetricId, LabeledStringMetric>>> = Lazy::new(|| + RwLock::new(HashMap::new()) + ); + } +} + diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_cpp b/toolkit/components/glean/tests/pytest/metrics_test_output_cpp new file mode 100644 index 0000000000..5e57bc8914 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/metrics_test_output_cpp @@ -0,0 +1,278 @@ +// -*- mode: C++ -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. + +/* 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_Metrics_h +#define mozilla_Metrics_h + +#include "mozilla/glean/bindings/MetricTypes.h" +#include "mozilla/Maybe.h" +#include "nsTArray.h" +#include "nsPrintfCString.h" + +#include <tuple> + +namespace mozilla::glean { +struct NoExtraKeys; +enum class DynamicLabel: uint16_t { }; + +namespace test { + /** + * generated from test.boolean_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::BooleanMetric boolean_metric(1); + + /** + * generated from test.counter_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::CounterMetric counter_metric(2); + + /** + * generated from test.custom_distribution_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::CustomDistributionMetric custom_distribution_metric(3); + + /** + * generated from test.labeled_boolean_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::Labeled<impl::BooleanMetric, DynamicLabel> labeled_boolean_metric(4); + + /** + * generated from test.labeled_boolean_metric_labels + */ + enum class LabeledBooleanMetricLabelsLabel: uint16_t { + eOneLabel = 0, + eTwoLabels = 1, + eThreeLabels = 2, + eFourLabels = 3, + eFiveLabels = 4, + eSixLabels = 5, + eSevenLabels = 6, + eEightLabels = 7, + eNineLabels = 8, + eTenLabels = 9, + e__Other__, + }; + /** + * A multi-line + * description + */ + constexpr impl::Labeled<impl::BooleanMetric, LabeledBooleanMetricLabelsLabel> labeled_boolean_metric_labels(5); + + /** + * generated from test.labeled_counter_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::Labeled<impl::CounterMetric, DynamicLabel> labeled_counter_metric(6); + + /** + * generated from test.labeled_counter_metric_labels + */ + enum class LabeledCounterMetricLabelsLabel: uint16_t { + eOneLabel = 0, + eTwoLabels = 1, + e__Other__, + }; + /** + * A multi-line + * description + */ + constexpr impl::Labeled<impl::CounterMetric, LabeledCounterMetricLabelsLabel> labeled_counter_metric_labels(7); + + /** + * generated from test.labeled_string_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::Labeled<impl::StringMetric, DynamicLabel> labeled_string_metric(8); + + /** + * generated from test.labeled_string_metric_labels + */ + enum class LabeledStringMetricLabelsLabel: uint16_t { + eOneLabel = 0, + eTwoLabels = 1, + e__Other__, + }; + /** + * A multi-line + * description + */ + constexpr impl::Labeled<impl::StringMetric, LabeledStringMetricLabelsLabel> labeled_string_metric_labels(9); + + /** + * generated from test.memory_distribution_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::MemoryDistributionMetric memory_distribution_metric(10); + + /** + * generated from test.string_list_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::StringListMetric string_list_metric(11); + + /** + * generated from test.string_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::StringMetric string_metric(12); + + /** + * generated from test.text_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::TextMetric text_metric(13); + + /** + * generated from test.timespan_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::TimespanMetric timespan_metric(14); + + /** + * generated from test.timing_distribution_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::TimingDistributionMetric timing_distribution_metric(15); + +} +namespace test_nested { + /** + * generated from test.nested.datetime_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::DatetimeMetric datetime_metric(16); + + /** + * generated from test.nested.event_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::EventMetric<NoExtraKeys> event_metric(17); + + /** + * generated from test.nested.event_metric_with_extra + */ + struct EventMetricWithExtraExtra { + mozilla::Maybe<nsCString> anExtraKey; + mozilla::Maybe<nsCString> anotherExtraKey; + + std::tuple<nsTArray<nsCString>, nsTArray<nsCString>> ToFfiExtra() const { + nsTArray<nsCString> extraKeys; + nsTArray<nsCString> extraValues; + if (anExtraKey) { + extraKeys.AppendElement()->AssignASCII("an_extra_key"); + extraValues.EmplaceBack(anExtraKey.value()); + } + if (anotherExtraKey) { + extraKeys.AppendElement()->AssignASCII("another_extra_key"); + extraValues.EmplaceBack(anotherExtraKey.value()); + } + return std::make_tuple(std::move(extraKeys), std::move(extraValues)); + } + }; + /** + * A multi-line + * description + */ + constexpr impl::EventMetric<EventMetricWithExtraExtra> event_metric_with_extra(18); + + /** + * generated from test.nested.external_denominator + */ + /** + * A multi-line + * description + */ + constexpr impl::DenominatorMetric external_denominator(19); + + /** + * generated from test.nested.quantity_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::QuantityMetric quantity_metric(20); + + /** + * generated from test.nested.rate_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::RateMetric rate_metric(21); + + /** + * generated from test.nested.rate_with_external_denominator + */ + /** + * A multi-line + * description + */ + constexpr impl::NumeratorMetric rate_with_external_denominator(22); + + /** + * generated from test.nested.uuid_metric + */ + /** + * A multi-line + * description + */ + constexpr impl::UuidMetric uuid_metric(23); + +} + +} // namespace mozilla::glean + +#endif // mozilla_Metrics_h diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp b/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp new file mode 100644 index 0000000000..b7db68b9e9 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp @@ -0,0 +1,403 @@ +// -*- mode: C++ -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. + +/* 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 http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/glean/bindings/GleanJSMetricsLookup.h" + +#include "mozilla/PerfectHash.h" +#include "mozilla/Maybe.h" +#include "mozilla/glean/bindings/MetricTypes.h" +#include "mozilla/glean/fog_ffi_generated.h" +#include "nsString.h" + +#define GLEAN_INDEX_BITS (32) +#define GLEAN_TYPE_BITS (5) +#define GLEAN_ID_BITS (27) +#define GLEAN_TYPE_ID(id) ((id) >> GLEAN_ID_BITS) +#define GLEAN_METRIC_ID(id) ((id) & ((1ULL << GLEAN_ID_BITS) - 1)) +#define GLEAN_OFFSET(entry) (entry & ((1ULL << GLEAN_INDEX_BITS) - 1)) + +namespace mozilla::glean { + +// The category lookup table's entry type +using category_entry_t = uint32_t; +// The metric lookup table's entry type +// This is a bitpacked type with 32 bits available to index into +// the string table, 5 bits available to signify the metric type, +// and the remaining 27 bits devoted to 2 "signal" +// bits to signify important characteristics (metric's a labeled metric's +// submetric, metric's been registered at runtime) and 25 bits +// for built-in metric ids. +// Gives room for 33554432 of each combination of +// characteristics (which hopefully will prove to be enough). +using metric_entry_t = uint64_t; + +static_assert(GLEAN_INDEX_BITS + GLEAN_TYPE_BITS + GLEAN_ID_BITS == sizeof(metric_entry_t) * 8, "Index, Type, and ID bits need to fit into a metric_entry_t"); +static_assert(GLEAN_TYPE_BITS + GLEAN_ID_BITS <= sizeof(uint32_t) * 8, "Metric Types and IDs need to fit into at most 32 bits"); +static_assert(2 < UINT32_MAX, "Too many metric categories generated."); +static_assert(23 < 33554432, "Too many metrics generated. Need room for 2 signal bits."); +static_assert(19 < 32, "Too many different metric types."); + +already_AddRefed<nsISupports> NewMetricFromId(uint32_t id) { + uint32_t typeId = GLEAN_TYPE_ID(id); + uint32_t metricId = GLEAN_METRIC_ID(id); + + switch (typeId) { + case 1: /* boolean */ + { + return MakeAndAddRef<GleanBoolean>(metricId); + } + case 2: /* counter */ + { + return MakeAndAddRef<GleanCounter>(metricId); + } + case 3: /* custom_distribution */ + { + return MakeAndAddRef<GleanCustomDistribution>(metricId); + } + case 4: /* labeled_boolean */ + { + return MakeAndAddRef<GleanLabeled>(metricId, 4); + } + case 5: /* labeled_counter */ + { + return MakeAndAddRef<GleanLabeled>(metricId, 5); + } + case 6: /* labeled_string */ + { + return MakeAndAddRef<GleanLabeled>(metricId, 6); + } + case 7: /* memory_distribution */ + { + return MakeAndAddRef<GleanMemoryDistribution>(metricId); + } + case 8: /* string_list */ + { + return MakeAndAddRef<GleanStringList>(metricId); + } + case 9: /* string */ + { + return MakeAndAddRef<GleanString>(metricId); + } + case 10: /* text */ + { + return MakeAndAddRef<GleanText>(metricId); + } + case 11: /* timespan */ + { + return MakeAndAddRef<GleanTimespan>(metricId); + } + case 12: /* timing_distribution */ + { + return MakeAndAddRef<GleanTimingDistribution>(metricId); + } + case 13: /* datetime */ + { + return MakeAndAddRef<GleanDatetime>(metricId); + } + case 14: /* event */ + { + return MakeAndAddRef<GleanEvent>(metricId); + } + case 15: /* denominator */ + { + return MakeAndAddRef<GleanDenominator>(metricId); + } + case 16: /* quantity */ + { + return MakeAndAddRef<GleanQuantity>(metricId); + } + case 17: /* rate */ + { + return MakeAndAddRef<GleanRate>(metricId); + } + case 18: /* numerator */ + { + return MakeAndAddRef<GleanNumerator>(metricId); + } + case 19: /* uuid */ + { + return MakeAndAddRef<GleanUuid>(metricId); + } + default: + MOZ_ASSERT_UNREACHABLE("Invalid type ID reached when trying to instantiate a new metric"); + return nullptr; + } +} + +/** + * Create a submetric instance for a labeled metric of the provided type and id for the given label. + * Assigns or retrieves an id for the submetric from the SDK. + * + * @param aParentTypeId - The type of the parent labeled metric identified as a number generated during codegen. + * Only used to identify which X of LabeledX you are so that X can be created here. + * @param aParentMetricId - The metric id for the parent labeled metric. + * @param aLabel - The label for the submetric. Might not adhere to the SDK label format. + * @param aSubmetricId - an outparam which is assigned the submetric's SDK-generated submetric id. + * Used only by GIFFT. + */ +already_AddRefed<nsISupports> NewSubMetricFromIds(uint32_t aParentTypeId, uint32_t aParentMetricId, const nsACString& aLabel, uint32_t* aSubmetricId) { + switch (aParentTypeId) { + case 4: { /* labeled_boolean */ + auto id = impl::fog_labeled_boolean_get(aParentMetricId, &aLabel); + *aSubmetricId = id; + return MakeAndAddRef<GleanBoolean>(id); + } + case 5: { /* labeled_counter */ + auto id = impl::fog_labeled_counter_get(aParentMetricId, &aLabel); + *aSubmetricId = id; + return MakeAndAddRef<GleanCounter>(id); + } + case 6: { /* labeled_string */ + auto id = impl::fog_labeled_string_get(aParentMetricId, &aLabel); + *aSubmetricId = id; + return MakeAndAddRef<GleanString>(id); + } + default: { + MOZ_ASSERT_UNREACHABLE("Invalid type ID for submetric."); + return nullptr; + } + } +} + +static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry); +static Maybe<uint32_t> metric_result_check(const nsACString& aKey, metric_entry_t entry); + +#if defined(_MSC_VER) && !defined(__clang__) +const char gCategoryStringTable[] = { +#else +constexpr char gCategoryStringTable[] = { +#endif + /* 0 - "test" */ 't', 'e', 's', 't', '\0', + /* 5 - "testNested" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '\0', +}; + + +static_assert(sizeof(gCategoryStringTable) < UINT32_MAX, "Category string table is too large."); + +const category_entry_t sCategoryByNameLookupEntries[] = { + 5ul, + 0ul +}; + + + +Maybe<uint32_t> +CategoryByNameLookup(const nsACString& aKey) +{ + static const uint8_t BASES[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + + const char* bytes = aKey.BeginReading(); + size_t length = aKey.Length(); + auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES, + sCategoryByNameLookupEntries); + return category_result_check(aKey, entry); +} + + +#if defined(_MSC_VER) && !defined(__clang__) +const char gMetricStringTable[] = { +#else +constexpr char gMetricStringTable[] = { +#endif + /* 0 - "test.booleanMetric" */ 't', 'e', 's', 't', '.', 'b', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 19 - "test.counterMetric" */ 't', 'e', 's', 't', '.', 'c', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 38 - "test.customDistributionMetric" */ 't', 'e', 's', 't', '.', 'c', 'u', 's', 't', 'o', 'm', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 68 - "test.labeledBooleanMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 94 - "test.labeledBooleanMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0', + /* 126 - "test.labeledCounterMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 152 - "test.labeledCounterMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0', + /* 184 - "test.labeledStringMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'S', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 209 - "test.labeledStringMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'S', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0', + /* 240 - "test.memoryDistributionMetric" */ 't', 'e', 's', 't', '.', 'm', 'e', 'm', 'o', 'r', 'y', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 270 - "test.stringListMetric" */ 't', 'e', 's', 't', '.', 's', 't', 'r', 'i', 'n', 'g', 'L', 'i', 's', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 292 - "test.stringMetric" */ 't', 'e', 's', 't', '.', 's', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 310 - "test.textMetric" */ 't', 'e', 's', 't', '.', 't', 'e', 'x', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 326 - "test.timespanMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'e', 's', 'p', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 346 - "test.timingDistributionMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'i', 'n', 'g', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 376 - "testNested.datetimeMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'd', 'a', 't', 'e', 't', 'i', 'm', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 402 - "testNested.eventMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 425 - "testNested.eventMetricWithExtra" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', 'W', 'i', 't', 'h', 'E', 'x', 't', 'r', 'a', '\0', + /* 457 - "testNested.externalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0', + /* 488 - "testNested.quantityMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'q', 'u', 'a', 'n', 't', 'i', 't', 'y', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 514 - "testNested.rateMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0', + /* 536 - "testNested.rateWithExternalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'W', 'i', 't', 'h', 'E', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0', + /* 575 - "testNested.uuidMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'u', 'u', 'i', 'd', 'M', 'e', 't', 'r', 'i', 'c', '\0', +}; + + +static_assert(sizeof(gMetricStringTable) < 4294967296, "Metric string table is too large."); + +const metric_entry_t sMetricByNameLookupEntries[] = { + 9223372122754122216ull, + 3458764548180279480ull, + 9799832879352513026ull, + 6341068335467200838ull, + 2305843026393563204ull, + 4035225309073637616ull, + 2305843030688530526ull, + 4611686065672028430ull, + 576460756598390784ull, + 7493989848663982456ull, + 8070450605262373266ull, + 8646911366155731401ull, + 3458764552475246801ull, + 5188146822270419236ull, + 1152921513196781587ull, + 10952754392549294655ull, + 1729382269795172390ull, + 2882303787286921342ull, + 10376293635950903832ull, + 2882303791581888664ull, + 6917529092065591642ull, + 5764607578868810038ull, + 8070450609557340585ull +}; + + + +Maybe<uint32_t> +MetricByNameLookup(const nsACString& aKey) +{ + static const uint8_t BASES[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 2, 0, 0, 21, 0, 0, 0, 0, 34, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + + const char* bytes = aKey.BeginReading(); + size_t length = aKey.Length(); + auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES, + sMetricByNameLookupEntries); + return metric_result_check(aKey, entry); +} + + +/** + * Get a category's name from the string table. + */ +const char* GetCategoryName(category_entry_t entry) { + MOZ_ASSERT(entry < sizeof(gCategoryStringTable), "Entry identifier offset larger than string table"); + return &gCategoryStringTable[entry]; +} + +/** + * Get a metric's identifier from the string table. + */ +const char* GetMetricIdentifier(metric_entry_t entry) { + uint32_t offset = GLEAN_OFFSET(entry); + MOZ_ASSERT(offset < sizeof(gMetricStringTable), "Entry identifier offset larger than string table"); + return &gMetricStringTable[offset]; +} + +/** + * Check that the found entry is pointing to the right key + * and return it. + * Or return `Nothing()` if the entry was not found. + */ +static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry) { + if (MOZ_UNLIKELY(entry > sizeof(gCategoryStringTable))) { + return Nothing(); + } + if (aKey.EqualsASCII(gCategoryStringTable + entry)) { + return Some(entry); + } + return Nothing(); +} + +/** + * Check if the found entry index is pointing to the right key + * and return the corresponding metric ID. + * Or return `Nothing()` if the entry was not found. + */ +static Maybe<uint32_t> metric_result_check(const nsACString& aKey, uint64_t entry) { + uint32_t metricId = entry >> GLEAN_INDEX_BITS; + uint32_t offset = GLEAN_OFFSET(entry); + + if (offset > sizeof(gMetricStringTable)) { + return Nothing(); + } + + if (aKey.EqualsASCII(gMetricStringTable + offset)) { + return Some(metricId); + } + + return Nothing(); +} + + +#undef GLEAN_INDEX_BITS +#undef GLEAN_ID_BITS +#undef GLEAN_TYPE_ID +#undef GLEAN_METRIC_ID +#undef GLEAN_OFFSET + +} // namespace mozilla::glean diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_js_h b/toolkit/components/glean/tests/pytest/metrics_test_output_js_h new file mode 100644 index 0000000000..50569781f8 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/metrics_test_output_js_h @@ -0,0 +1,74 @@ +// -*- mode: C++ -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. + +/* 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_GleanJSMetricsLookup_h +#define mozilla_GleanJSMetricsLookup_h + +#include <cstdint> + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Maybe.h" +#include "nsStringFwd.h" + +class nsISupports; + +namespace mozilla::glean { + +// The category lookup table's entry type +using category_entry_t = uint32_t; +// The metric lookup table's entry type +// This is a bitpacked type with 32 bits available to index into +// the string table, 5 bits available to signify the metric type, +// and the remaining 27 bits devoted to 2 "signal" +// bits to signify important characteristics (metric's a labeled metric's +// submetric, metric's been registered at runtime) and 25 bits +// for built-in metric ids. +// Gives room for 33554432 of each combination of +// characteristics (which hopefully will prove to be enough). +using metric_entry_t = uint64_t; + +already_AddRefed<nsISupports> NewMetricFromId(uint32_t id); + +/** + * Create a submetric instance for a labeled metric of the provided type and id for the given label. + * Assigns or retrieves an id for the submetric from the SDK. + * + * @param aParentTypeId - The type of the parent labeled metric identified as a number generated during codegen. + * Only used to identify which X of LabeledX you are so that X can be created here. + * @param aParentMetricId - The metric id for the parent labeled metric. + * @param aLabel - The label for the submetric. Might not adhere to the SDK label format. + * @param aSubmetricId - an outparam which is assigned the submetric's SDK-generated submetric id. + * Used only by GIFFT. + */ +already_AddRefed<nsISupports> NewSubMetricFromIds(uint32_t aParentTypeId, uint32_t aParentMetricId, const nsACString& aLabel, uint32_t* aSubmetricId); + +/** + * Get a category's name from the string table. + */ +const char* GetCategoryName(category_entry_t entry); + +/** + * Get a metric's identifier from the string table. + */ +const char* GetMetricIdentifier(metric_entry_t entry); + +/** + * Get a metric's id given its name. + */ +Maybe<uint32_t> MetricByNameLookup(const nsACString&); + +/** + * Get a category's id given its name. + */ +Maybe<uint32_t> CategoryByNameLookup(const nsACString&); + +extern const category_entry_t sCategoryByNameLookupEntries[2]; +extern const metric_entry_t sMetricByNameLookupEntries[23]; + +} // namespace mozilla::glean +#endif // mozilla_GleanJSMetricsLookup_h diff --git a/toolkit/components/glean/tests/pytest/pings_test.yaml b/toolkit/components/glean/tests/pytest/pings_test.yaml new file mode 100644 index 0000000000..efa6ba9e0a --- /dev/null +++ b/toolkit/components/glean/tests/pytest/pings_test.yaml @@ -0,0 +1,116 @@ +# 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 http://mozilla.org/MPL/2.0/. + +# This file defines the built-in pings that are recorded by the Glean SDK. They +# are automatically converted to Kotlin code at build time using the +# `glean_parser` PyPI package. + +--- +$schema: moz://mozilla.org/schemas/glean/pings/1-0-0 + +not-baseline: + description: > + This ping is intended to provide metrics that are managed by the library + itself, and not explicitly set by the application or included in the + application's `metrics.yaml` file. + The `baseline` ping is automatically sent when the application is moved to + the background. + include_client_id: true + bugs: + - https://bugzilla.mozilla.org/1512938 + - https://bugzilla.mozilla.org/1599877 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1599877#c25 + notification_emails: + - glean-team@mozilla.com + reasons: + dirty_startup: | + The ping was submitted at startup, because the application process was + killed before the Glean SDK had the chance to generate this ping, when + going to background, in the last session. + + *Note*: this ping will not contain the `glean.baseline.duration` metric. + background: | + The ping was submitted before going to background. + foreground: | + The ping was submitted when the application went to foreground, which + includes when the application starts. + + *Note*: this ping will not contain the `glean.baseline.duration` metric. + +not-metrics: + description: > + The `metrics` ping is intended for all of the metrics that are explicitly + set by the application or are included in the application's `metrics.yaml` + file (except events). + The reported data is tied to the ping's *measurement window*, which is the + time between the collection of two `metrics` ping. Ideally, this window is + expected to be about 24 hours, given that the collection is scheduled daily + at 4AM. Data in the `ping_info` section of the ping can be used to infer the + length of this window. + include_client_id: true + bugs: + - https://bugzilla.mozilla.org/1512938 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1557048#c13 + notification_emails: + - glean-team@mozilla.com + reasons: + overdue: | + The last ping wasn't submitted on the current calendar day, but it's after + 4am, so this ping submitted immediately + today: | + The last ping wasn't submitted on the current calendar day, but it is + still before 4am, so schedule to send this ping on the current calendar + day at 4am. + tomorrow: | + The last ping was already submitted on the current calendar day, so + schedule this ping for the next calendar day at 4am. + upgrade: | + This ping was submitted at startup because the application was just + upgraded. + reschedule: | + A ping was just submitted. This ping was rescheduled for the next calendar + day at 4am. + +not-events: + description: > + The events ping's purpose is to transport all of the event metric + information. The `events` ping is automatically sent when the application is + moved to the background. + include_client_id: true + bugs: + - https://bugzilla.mozilla.org/1512938 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3 + notification_emails: + - glean-team@mozilla.com + reasons: + startup: | + The ping was submitted at startup. The events ping is always sent if there + are any pending events at startup, because event timestamps can not be + mixed across runs of the application. + background: | + The ping was submitted before going to background. + max_capacity: | + The maximum number of events was reached (default 500 events). + +not-deletion-request: + description: > + This ping is submitted when a user opts out of + sending technical and interaction data to Mozilla. + This ping is intended to communicate to the Data Pipeline + that the user wishes to have their reported Telemetry data deleted. + As such it attempts to send itself at the moment the user + opts out of data collection. + include_client_id: true + send_if_empty: true + bugs: + - https://bugzilla.mozilla.org/1587095 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1587095#c6 + notification_emails: + - glean-team@mozilla.com diff --git a/toolkit/components/glean/tests/pytest/pings_test_output b/toolkit/components/glean/tests/pytest/pings_test_output new file mode 100644 index 0000000000..03a5921f90 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/pings_test_output @@ -0,0 +1,114 @@ +// -*- mode: Rust -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. + +/* 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 http://mozilla.org/MPL/2.0/. */ + +use crate::private::Ping; +use once_cell::sync::Lazy; + +#[allow(non_upper_case_globals)] +/// This ping is intended to provide metrics that are managed by the library +/// itself, and not explicitly set by the application or included in the +/// application's `metrics.yaml` file. The `baseline` ping is automatically sent +/// when the application is moved to the background. +pub static not_baseline: Lazy<Ping> = Lazy::new(|| { + Ping::new( + "not-baseline", + true, + false, + vec!["background".into(), "dirty_startup".into(), "foreground".into()], + ) +}); + +#[allow(non_upper_case_globals)] +/// This ping is submitted when a user opts out of sending technical and +/// interaction data to Mozilla. This ping is intended to communicate to the Data +/// Pipeline that the user wishes to have their reported Telemetry data deleted. As +/// such it attempts to send itself at the moment the user opts out of data +/// collection. +pub static not_deletion_request: Lazy<Ping> = Lazy::new(|| { + Ping::new( + "not-deletion-request", + true, + true, + vec![], + ) +}); + +#[allow(non_upper_case_globals)] +/// The events ping's purpose is to transport all of the event metric information. +/// The `events` ping is automatically sent when the application is moved to the +/// background. +pub static not_events: Lazy<Ping> = Lazy::new(|| { + Ping::new( + "not-events", + true, + false, + vec!["background".into(), "max_capacity".into(), "startup".into()], + ) +}); + +#[allow(non_upper_case_globals)] +/// The `metrics` ping is intended for all of the metrics that are explicitly set +/// by the application or are included in the application's `metrics.yaml` file +/// (except events). The reported data is tied to the ping's *measurement window*, +/// which is the time between the collection of two `metrics` ping. Ideally, this +/// window is expected to be about 24 hours, given that the collection is scheduled +/// daily at 4AM. Data in the `ping_info` section of the ping can be used to infer +/// the length of this window. +pub static not_metrics: Lazy<Ping> = Lazy::new(|| { + Ping::new( + "not-metrics", + true, + false, + vec!["overdue".into(), "reschedule".into(), "today".into(), "tomorrow".into(), "upgrade".into()], + ) +}); + + +/// Instantiate custom pings once to trigger registration. +/// +/// # Arguments +/// +/// application_id: If present, limit to only registering custom pings +/// assigned to the identified application. +#[doc(hidden)] +pub fn register_pings(application_id: Option<&str>) { + match application_id { + _ => { + let _ = &*not_baseline; + let _ = &*not_deletion_request; + let _ = &*not_events; + let _ = &*not_metrics; + } + } +} + +#[cfg(feature = "with_gecko")] +pub(crate) fn submit_ping_by_id(id: u32, reason: Option<&str>) { + if id & (1 << crate::factory::DYNAMIC_PING_BIT) > 0 { + let map = crate::factory::__jog_metric_maps::PING_MAP + .read() + .expect("Read lock for dynamic ping map was poisoned!"); + if let Some(ping) = map.get(&id) { + ping.submit(reason); + } else { + // TODO: instrument this error. + log::error!("Cannot submit unknown dynamic ping {} by id.", id); + } + return; + } + match id { + 1 => not_baseline.submit(reason), + 2 => not_deletion_request.submit(reason), + 3 => not_events.submit(reason), + 4 => not_metrics.submit(reason), + _ => { + // TODO: instrument this error. + log::error!("Cannot submit unknown ping {} by id.", id); + } + } +} diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_cpp b/toolkit/components/glean/tests/pytest/pings_test_output_cpp new file mode 100644 index 0000000000..289529b118 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/pings_test_output_cpp @@ -0,0 +1,62 @@ +// -*- mode: C++ -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. + +/* 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_glean_Pings_h +#define mozilla_glean_Pings_h + +#include "mozilla/glean/bindings/Ping.h" + +namespace mozilla::glean_pings { + +/* + * Generated from not-baseline. + * + * This ping is intended to provide metrics that are managed by the library + * itself, and not explicitly set by the application or included in the + * application's `metrics.yaml` file. The `baseline` ping is automatically sent + * when the application is moved to the background. + */ +constexpr glean::impl::Ping NotBaseline(1); + +/* + * Generated from not-deletion-request. + * + * This ping is submitted when a user opts out of sending technical and + * interaction data to Mozilla. This ping is intended to communicate to the Data + * Pipeline that the user wishes to have their reported Telemetry data deleted. As + * such it attempts to send itself at the moment the user opts out of data + * collection. + */ +constexpr glean::impl::Ping NotDeletionRequest(2); + +/* + * Generated from not-events. + * + * The events ping's purpose is to transport all of the event metric information. + * The `events` ping is automatically sent when the application is moved to the + * background. + */ +constexpr glean::impl::Ping NotEvents(3); + +/* + * Generated from not-metrics. + * + * The `metrics` ping is intended for all of the metrics that are explicitly set + * by the application or are included in the application's `metrics.yaml` file + * (except events). The reported data is tied to the ping's *measurement window*, + * which is the time between the collection of two `metrics` ping. Ideally, this + * window is expected to be about 24 hours, given that the collection is scheduled + * daily at 4AM. Data in the `ping_info` section of the ping can be used to infer + * the length of this window. + */ +constexpr glean::impl::Ping NotMetrics(4); + + +} // namespace mozilla::glean_pings + +#endif // mozilla_glean_Pings_h diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp b/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp new file mode 100644 index 0000000000..4a2cc4e808 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp @@ -0,0 +1,130 @@ +// -*- mode: C++ -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. + +/* 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 http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/glean/bindings/GleanJSPingsLookup.h" + +#include "mozilla/PerfectHash.h" +#include "nsString.h" + +#include "mozilla/PerfectHash.h" + +#define GLEAN_PING_INDEX_BITS (16) +#define GLEAN_PING_ID(entry) ((entry) >> GLEAN_PING_INDEX_BITS) +#define GLEAN_PING_INDEX(entry) ((entry) & ((1UL << GLEAN_PING_INDEX_BITS) - 1)) + +namespace mozilla::glean { + +// Contains the ping id and the index into the ping string table. +using ping_entry_t = uint32_t; + +Maybe<uint32_t> ping_result_check(const nsACString& aKey, ping_entry_t aEntry); + +#if defined(_MSC_VER) && !defined(__clang__) +const char gPingStringTable[] = { +#else +constexpr char gPingStringTable[] = { +#endif + /* 0 - "notBaseline" */ 'n', 'o', 't', 'B', 'a', 's', 'e', 'l', 'i', 'n', 'e', '\0', + /* 12 - "notDeletionRequest" */ 'n', 'o', 't', 'D', 'e', 'l', 'e', 't', 'i', 'o', 'n', 'R', 'e', 'q', 'u', 'e', 's', 't', '\0', + /* 31 - "notEvents" */ 'n', 'o', 't', 'E', 'v', 'e', 'n', 't', 's', '\0', + /* 41 - "notMetrics" */ 'n', 'o', 't', 'M', 'e', 't', 'r', 'i', 'c', 's', '\0', +}; + + + +const ping_entry_t sPingByNameLookupEntries[] = { + 262185, + 131084, + 65536, + 196639 +}; + + + +Maybe<uint32_t> +PingByNameLookup(const nsACString& aKey) +{ + static const uint8_t BASES[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + + const char* bytes = aKey.BeginReading(); + size_t length = aKey.Length(); + auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES, + sPingByNameLookupEntries); + return ping_result_check(aKey, entry); +} + + +/** + * Get a ping's name given its entry from the PHF. + */ +const char* GetPingName(ping_entry_t aEntry) { + uint32_t idx = GLEAN_PING_INDEX(aEntry); + MOZ_ASSERT(idx < sizeof(gPingStringTable), "Ping index larger than string table"); + return &gPingStringTable[idx]; +} + +/** + * Check if the found entry is pointing at the correct ping. + * PHF can false-positive a result when the key isn't present, so we check + * for a string match. If it fails, return Nothing(). If we found it, + * return the ping's id. + */ +Maybe<uint32_t> ping_result_check(const nsACString& aKey, ping_entry_t aEntry) { + uint32_t idx = GLEAN_PING_INDEX(aEntry); + uint32_t id = GLEAN_PING_ID(aEntry); + + if (MOZ_UNLIKELY(idx > sizeof(gPingStringTable))) { + return Nothing(); + } + + if (aKey.EqualsASCII(&gPingStringTable[idx])) { + return Some(id); + } + + return Nothing(); +} + +#undef GLEAN_PING_INDEX_BITS +#undef GLEAN_PING_ID +#undef GLEAN_PING_INDEX + +} // namespace mozilla::glean diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_js_h b/toolkit/components/glean/tests/pytest/pings_test_output_js_h new file mode 100644 index 0000000000..0c89de93ae --- /dev/null +++ b/toolkit/components/glean/tests/pytest/pings_test_output_js_h @@ -0,0 +1,33 @@ +// -*- mode: C++ -*- + +// AUTOGENERATED BY glean_parser. DO NOT EDIT. + +/* 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_GleanJSPingsLookup_h +#define mozilla_GleanJSPingsLookup_h + +#include <cstdint> +#include "mozilla/Maybe.h" +#include "nsStringFwd.h" + +namespace mozilla::glean { + +// Contains the ping id and the index into the ping string table. +using ping_entry_t = uint32_t; + +/** + * Get a ping's name given its entry in the PHF. + */ +const char* GetPingName(ping_entry_t aEntry); + +/** + * Get a ping's id given its name. + */ +Maybe<uint32_t> PingByNameLookup(const nsACString&); + +extern const ping_entry_t sPingByNameLookupEntries[4]; +} // namespace mozilla::glean +#endif // mozilla_GleanJSPingsLookup_h diff --git a/toolkit/components/glean/tests/pytest/python.ini b/toolkit/components/glean/tests/pytest/python.ini new file mode 100644 index 0000000000..87573cf519 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/python.ini @@ -0,0 +1,10 @@ +[DEFAULT] +subsuite = fog + +[test_gifft.py] +[test_glean_parser_rust.py] +[test_glean_parser_cpp.py] +[test_glean_parser_js.py] +[test_jogfile_output.py] +[test_no_expired_metrics.py] +[test_yaml_indices.py] diff --git a/toolkit/components/glean/tests/pytest/test_gifft.py b/toolkit/components/glean/tests/pytest/test_gifft.py new file mode 100644 index 0000000000..5de0640bca --- /dev/null +++ b/toolkit/components/glean/tests/pytest/test_gifft.py @@ -0,0 +1,49 @@ +# 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 http://mozilla.org/MPL/2.0/. + +import io +import sys +from os import path +from pathlib import Path + +import mozunit +from expect_helper import expect + +# Shenanigans to import the FOG glean_parser runner +FOG_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext")) +import run_glean_parser + + +def test_gifft_codegen(): + """ + A regression test. Very fragile. + It generates C++ for GIFFT for metrics_test.yaml and compares it + byte-for-byte with expected output C++ files. + To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite: + + UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest + """ + + options = {"allow_reserved": False} + here_path = Path(path.dirname(__file__)) + input_files = [here_path / "metrics_test.yaml"] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + for probe_type in ("Event", "Histogram", "Scalar"): + output_fd = io.StringIO() + cpp_fd = io.StringIO() + run_glean_parser.output_gifft_map(output_fd, probe_type, all_objs, cpp_fd) + + expect(here_path / f"gifft_output_{probe_type}", output_fd.getvalue()) + + if probe_type == "Event": + expect(here_path / "gifft_output_EventExtra", cpp_fd.getvalue()) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/glean/tests/pytest/test_glean_parser_cpp.py b/toolkit/components/glean/tests/pytest/test_glean_parser_cpp.py new file mode 100644 index 0000000000..05fd691782 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/test_glean_parser_cpp.py @@ -0,0 +1,70 @@ +# 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 http://mozilla.org/MPL/2.0/. + +import io +import sys +from os import path +from pathlib import Path + +import mozunit +from expect_helper import expect + +# Shenanigans to import the cpp outputter extension +FOG_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext")) +import cpp +import run_glean_parser + + +def test_all_metric_types(): + """Honestly, this is a pretty bad test. + It generates C++ for a given test metrics.yaml and compares it byte-for-byte + with an expected output C++ file. + Expect it to be fragile. + To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite: + + UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest + """ + + options = {"allow_reserved": False} + input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + output_fd = io.StringIO() + cpp.output_cpp(all_objs, output_fd, options) + + expect( + path.join(path.dirname(__file__), "metrics_test_output_cpp"), + output_fd.getvalue(), + ) + + +def test_fake_pings(): + """Another similarly-fragile test. + It generates C++ for pings_test.yaml, comparing it byte-for-byte + with an expected output C++ file `pings_test_output_cpp`. + Expect it to be fragile. + To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite: + + UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest + """ + + options = {"allow_reserved": False} + input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + output_fd = io.StringIO() + cpp.output_cpp(all_objs, output_fd, options) + + expect( + path.join(path.dirname(__file__), "pings_test_output_cpp"), output_fd.getvalue() + ) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/glean/tests/pytest/test_glean_parser_js.py b/toolkit/components/glean/tests/pytest/test_glean_parser_js.py new file mode 100644 index 0000000000..3f4a37e797 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/test_glean_parser_js.py @@ -0,0 +1,84 @@ +# 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 http://mozilla.org/MPL/2.0/. + +import io +import sys +from os import path +from pathlib import Path + +import mozunit +from expect_helper import expect + +# Shenanigans to import the js outputter extension +FOG_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext")) +import run_glean_parser + +import js + + +def test_all_metric_types(): + """Honestly, this is a pretty bad test. + It generates C++ for a given test metrics.yaml and compares it byte-for-byte + with an expected output C++ file. + Expect it to be fragile. + To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite: + + UPDATE_EXPECT=1 mach test toolkit/components/glean/tests/pytest + """ + + options = {"allow_reserved": False} + input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + output_fd_h = io.StringIO() + output_fd_cpp = io.StringIO() + js.output_js(all_objs, output_fd_h, output_fd_cpp, options) + + expect( + path.join(path.dirname(__file__), "metrics_test_output_js_h"), + output_fd_h.getvalue(), + ) + + expect( + path.join(path.dirname(__file__), "metrics_test_output_js_cpp"), + output_fd_cpp.getvalue(), + ) + + +def test_fake_pings(): + """Another similarly-fragile test. + It generates C++ for pings_test.yaml, comparing it byte-for-byte + with an expected output C++ file `pings_test_output_js`. + Expect it to be fragile. + To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite: + + UPDATE_EXPECT=1 mach test toolkit/components/glean/tests/pytest + """ + + options = {"allow_reserved": False} + input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + output_fd_h = io.StringIO() + output_fd_cpp = io.StringIO() + js.output_js(all_objs, output_fd_h, output_fd_cpp, options) + + expect( + path.join(path.dirname(__file__), "pings_test_output_js_h"), + output_fd_h.getvalue(), + ) + + expect( + path.join(path.dirname(__file__), "pings_test_output_js_cpp"), + output_fd_cpp.getvalue(), + ) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/glean/tests/pytest/test_glean_parser_rust.py b/toolkit/components/glean/tests/pytest/test_glean_parser_rust.py new file mode 100644 index 0000000000..329c7ed348 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/test_glean_parser_rust.py @@ -0,0 +1,89 @@ +# 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 http://mozilla.org/MPL/2.0/. + +import io +import sys +from os import path +from pathlib import Path + +import mozunit +from expect_helper import expect + +# Shenanigans to import the rust outputter extension +FOG_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext")) +import run_glean_parser +import rust + +# Shenanigans to import the in-tree glean_parser +GECKO_PATH = path.join(FOG_ROOT_PATH, path.pardir, path.pardir, path.pardir) +sys.path.append(path.join(GECKO_PATH, "third_party", "python", "glean_parser")) + + +def test_all_metric_types(): + """Honestly, this is a pretty bad test. + It generates Rust for a given test metrics.yaml and compares it byte-for-byte + with an expected output Rust file. + Expect it to be fragile. + To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite: + + UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest + """ + + options = {"allow_reserved": False} + input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + output_fd = io.StringIO() + rust.output_rust(all_objs, output_fd, {}, options) + + expect( + path.join(path.dirname(__file__), "metrics_test_output"), output_fd.getvalue() + ) + + +def test_fake_pings(): + """Another similarly-bad test. + It generates Rust for pings_test.yaml, comparing it byte-for-byte + with an expected output Rust file. + Expect it to be fragile. + To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite: + + UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest + """ + + options = {"allow_reserved": False} + input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + output_fd = io.StringIO() + rust.output_rust(all_objs, output_fd, {}, options) + + expect(path.join(path.dirname(__file__), "pings_test_output"), output_fd.getvalue()) + + +def test_expires_version(): + """This test relies on the intermediary object format output by glean_parser. + Expect it to be fragile on glean_parser updates that change that format. + """ + + # The test file has 41, 42, 100. Use 42.0a1 here to ensure "expires == version" means expired. + options = run_glean_parser.get_parser_options("42.0a1") + input_files = [ + Path(path.join(path.dirname(__file__), "metrics_expires_versions_test.yaml")) + ] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + assert all_objs["test"]["expired1"].disabled is True + assert all_objs["test"]["expired2"].disabled is True + assert all_objs["test"]["unexpired"].disabled is False + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/glean/tests/pytest/test_jogfile_output.py b/toolkit/components/glean/tests/pytest/test_jogfile_output.py new file mode 100644 index 0000000000..801c0967ff --- /dev/null +++ b/toolkit/components/glean/tests/pytest/test_jogfile_output.py @@ -0,0 +1,50 @@ +# 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 http://mozilla.org/MPL/2.0/. + +import io +import sys +from os import path +from pathlib import Path + +import mozunit +from expect_helper import expect + +# Shenanigans to import the FOG glean_parser runner +FOG_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext")) +import jog +import run_glean_parser + + +def test_jogfile_output(): + """ + A regression test. Very fragile. + It generates a jogfile for metrics_test.yaml and compares it + byte-for-byte with an expected output file. + + Also, part one of a two-part test. + The generated jogfile is consumed in Rust_TestJogfile in t/c/g/tests/gtest/test.rs + This is to ensure that the jogfile we generate in Python can be consumed in Rust. + + To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite: + + UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest + """ + + options = {"allow_reserved": False} + here_path = Path(path.dirname(__file__)) + input_files = [here_path / "metrics_test.yaml", here_path / "pings_test.yaml"] + + all_objs, options = run_glean_parser.parse_with_options(input_files, options) + + output_fd = io.StringIO() + jog.output_file(all_objs, output_fd, options) + + expect(here_path / "jogfile_output", output_fd.getvalue()) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/glean/tests/pytest/test_no_expired_metrics.py b/toolkit/components/glean/tests/pytest/test_no_expired_metrics.py new file mode 100644 index 0000000000..9783acaf0a --- /dev/null +++ b/toolkit/components/glean/tests/pytest/test_no_expired_metrics.py @@ -0,0 +1,46 @@ +# 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 http://mozilla.org/MPL/2.0/. + +import sys +from os import path +from pathlib import Path + +import mozunit + +# Shenanigans to import the metrics index's list of metrics.yamls +FOG_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(FOG_ROOT_PATH) +from metrics_index import metrics_yamls, tags_yamls + +# Shenanigans to import run_glean_parser +sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext")) +import run_glean_parser + +# Shenanigans to import the in-tree glean_parser +GECKO_PATH = path.join(FOG_ROOT_PATH, path.pardir, path.pardir, path.pardir) +sys.path.append(path.join(GECKO_PATH, "third_party", "python", "glean_parser")) +from glean_parser import lint, parser, util + + +def test_no_metrics_expired(): + """ + Of all the metrics included in this build, are any expired? + If so, they must be removed or renewed. + + (This also checks other lints, as a treat.) + """ + with open("browser/config/version.txt", "r") as version_file: + app_version = version_file.read().strip() + + options = run_glean_parser.get_parser_options(app_version) + paths = [Path(x) for x in metrics_yamls] + [Path(x) for x in tags_yamls] + all_objs = parser.parse_objects(paths, options) + assert not util.report_validation_errors(all_objs) + assert not lint.lint_metrics(all_objs.value, options) + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/glean/tests/pytest/test_yaml_indices.py b/toolkit/components/glean/tests/pytest/test_yaml_indices.py new file mode 100644 index 0000000000..4edb402e03 --- /dev/null +++ b/toolkit/components/glean/tests/pytest/test_yaml_indices.py @@ -0,0 +1,42 @@ +# 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 http://mozilla.org/MPL/2.0/. + +import sys +from os import path + +import mozunit + +# Shenanigans to import the metrics index's lists of yamls +FOG_ROOT_PATH = path.abspath( + path.join(path.dirname(__file__), path.pardir, path.pardir) +) +sys.path.append(FOG_ROOT_PATH) +import metrics_index + + +def test_yamls_sorted(): + """ + Ensure the yamls indices are sorted lexicographically. + """ + # Ignore lists that are the concatenation of others. + to_ignore = ["metrics_yamls", "pings_yamls"] + + # Fetch names of all variables defined in the `metrics_index` module. + yaml_lists = [ + item + for item in dir(metrics_index) + if isinstance(item, list) and not item.startswith("__") + ] + for name in yaml_lists: + if name in to_ignore: + continue + + yamls_to_test = metrics_index.__dict__[name] + assert ( + sorted(yamls_to_test) == yamls_to_test + ), f"{name} must be be lexicographically sorted." + + +if __name__ == "__main__": + mozunit.main() diff --git a/toolkit/components/glean/tests/test_metrics.yaml b/toolkit/components/glean/tests/test_metrics.yaml new file mode 100644 index 0000000000..bc290d470f --- /dev/null +++ b/toolkit/components/glean/tests/test_metrics.yaml @@ -0,0 +1,859 @@ +# 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 http://mozilla.org/MPL/2.0/. + +# This file is for Internal FOG Test Use Only. + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 + +test_only: + bad_code: + type: counter + description: | + Number of times we encountered bad code. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + + can_we_time_it: + type: timespan + time_unit: nanosecond + description: | + Test metric for a timespan. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + + cheesy_string: + type: string + description: | + Only the cheesiest of strings. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673662 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673662#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + + cheesy_string_list: + type: string_list + description: | + Only the cheesiest of strings. In list form! + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1682960 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1682960#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + + what_a_date: + type: datetime + time_unit: second + description: > + ...To be writing FOG code. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + + what_id_it: # Using a different metrics yaml style for fun. + type: uuid + description: | + Just a UUID. + This is a test-only metric. + bugs: ["https://bugzilla.mozilla.org/show_bug.cgi?id=1673664"] + data_reviews: ["https://bugzilla.mozilla.org/show_bug.cgi?id=1673664#c1"] + data_sensitivity: ["technical"] + notification_emails: ["glean-team@mozilla.com"] + expires: never + send_in_pings: ["test-ping"] + + can_we_flag_it: + type: boolean + description: | + Test metric for a boolean. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + + do_you_remember: + type: memory_distribution + memory_unit: megabyte + description: | + They say it's the second thing to go. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673648 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673648#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + + what_time_is_it: + type: timing_distribution + time_unit: microsecond + description: | + Adheres to at least two of the top ten fallacies programmers believe + about time. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673663 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673663#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_TIMING + + mabels_kitchen_counters: + type: labeled_counter + description: | + Counts Mabels labeled by their kitchen counters. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + + mabels_labeled_counters: + type: labeled_counter + description: | + Counts Mabels labeled by their kitchen counters. + Now with static labels. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1672273 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1672273 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + labels: + - next_to_the_fridge + - clean + + mabels_bathroom_counters: + type: labeled_counter + description: | + Counts Mabels labeled by their bathroom counters. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1683171 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1683171#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + + mabels_like_balloons: + type: labeled_boolean + description: | + Does the labeled Mabel like balloons? + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + + mabels_like_labeled_balloons: + type: labeled_boolean + description: | + Does the labeled Mabel like balloons? + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1672273 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1672273 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + labels: + - water + - birthday_party + + mabels_balloon_strings: + type: labeled_string + description: | + What do the labeled Mabel's liked balloons' strings say? + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + + mabels_balloon_labels: + type: labeled_string + description: | + How does Mabel label her balloons? + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1672273 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1672273 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + labels: + - celebratory + - celebratory_and_snarky + + mabels_label_maker: + type: labeled_string + description: | + Mabel just got a label maker and wants to party like it's + 1999. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1696388 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1696388 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + + mirror_time: + type: timespan + time_unit: nanosecond + description: | + Mirrored metric for a timespan. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_TIMESPAN + + mirror_time_nanos: + type: timespan + time_unit: nanosecond + description: | + Mirrored metric for a timespan. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1704106 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1704106#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_TIMESPAN_NANOS + + mirrors_for_labeled_bools: + type: labeled_boolean + description: | + Mirrored metric. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_LABELED_BOOL + + one_ping_one_bool: + type: boolean + description: | + One bool for one ping only. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685402 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685402#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - one-ping-only + + meaning_of_life: + type: quantity + unit: unfathomable + description: | + Measures the one true answer to the Ultimate Question of Life, + the Universe, and Everything. + Approximately. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1704846 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1704846#c1 + data_sensitivity: + - technical + expires: never + notification_emails: + - glean-team@mozilla.com + send_in_pings: + - test-ping + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_QUANTITY + + +test_only.ipc: + a_counter: + type: counter + description: | + This is a test-only metric. + Just counting things. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_COUNTER + a_bool: + type: boolean + description: | + This is a test-only metric. + Just flagging things. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_BOOLEAN_KIND + a_date: + type: datetime + time_unit: second + description: | + This is a test-only metric. + Just putting things on the calendar. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_DATE + a_string: + type: string + description: | + This is a test-only metric. + Just setting some strings. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_MULTIPLE_STORES_STRING + a_text: + type: text + description: | + This is a test-only metric. + For holding Text data. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1828528 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281#c1 + data_sensitivity: + - web_activity + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + a_memory_dist: + type: memory_distribution + memory_unit: kilobyte + description: | + This is a test-only metric. + Just measuring memory. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_LINEAR + a_timing_dist: + type: timing_distribution + description: | + This is a test-only metric. + Just measuring time. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_EXPONENTIAL + a_custom_dist: + type: custom_distribution + description: | + This is a test-only metric. + Just measuring samples. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1713398 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1713398#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + range_min: 1 + range_max: 2147483646 + bucket_count: 10 + histogram_type: linear + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_CUSTOM + a_string_list: + type: string_list + description: | + This is a test-only metric. + Just appending some strings. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_KEYED_BOOLEAN_KIND + an_event: + type: event + extra_keys: + extra1: + type: string + description: "Some extra data" + extra2: + type: string + description: "Some extra data again" + description: | + This is a test-only metric. + Just recording some events. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TelemetryTest_MirrorWithExtra_Object1 + event_with_extra: + type: event + extra_keys: + extra1: + type: string + description: "Some extra data" + extra2: + type: quantity + description: "Some extra data again" + extra3_longer_name: + type: boolean + description: "Some extra data again. Also tests extras with underscores" + description: | + This is a test-only metric. + Just recording some events. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + no_extra_event: + type: event + description: | + This is a test-only metric. + Just recording some events without the extra fuss. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + telemetry_mirror: TelemetryTest_NotExpiredOptout_Object1 + a_uuid: + type: uuid + description: | + This is a test-only metric. + Just recording some unique identifiers. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_STRING_KIND + a_labeled_counter: + type: labeled_counter + description: | + This is a test-only metric. + Just counting labeled things. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_ANOTHER_MIRROR_FOR_LABELED_COUNTER + another_labeled_counter: + type: labeled_counter + description: | + This is a test-only metric. + Just another metric counting labeled things. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1758795 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_LABELED_COUNTER + a_quantity: + type: quantity + unit: squad + description: | + This is a test-only metric. + Just quantifying things. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1704846 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1704846#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + irate: + type: rate + description: | + This is a test-only metric. + A rate that isn't happy about it. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_RATE + rate_with_external_denominator: + type: rate + denominator_metric: test_only.ipc.an_external_denominator + description: | + This is a test-only metric. + A rate with a denominator that is Out There. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + an_external_denominator: + type: counter + description: | + This is a test-only metric. + A denominator not from around here. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + a_url: + type: url + description: | + This is a test-only metric. + Just setting some Urls. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1766980 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + no_lint: + - COMMON_PREFIX + telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_URL + +test_only.jog: + a_counter: + type: counter + description: | + This is a test-only metric. + Just counting things. + Tied closely to test_jog_name_collision. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1767039 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1767039 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 + + an_event: + type: event + extra_keys: + extra1: + type: string + description: "Some extra data" + extra2: + type: string + description: "Some extra data again" + description: | + This is a test-only metric. + Just recording some events. + Tied closely to test_jog_name_collision. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1767039 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1767039 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - store1 diff --git a/toolkit/components/glean/tests/test_pings.yaml b/toolkit/components/glean/tests/test_pings.yaml new file mode 100644 index 0000000000..d62f682109 --- /dev/null +++ b/toolkit/components/glean/tests/test_pings.yaml @@ -0,0 +1,42 @@ +# 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 http://mozilla.org/MPL/2.0/. + +# This file defines the pings that are recorded by the Glean SDK. They are +# automatically converted to platform-specific code at build time using the +# `glean_parser` PyPI package. + +# This file is presently for Internal FOG Test Use Only. + +--- +$schema: moz://mozilla.org/schemas/glean/pings/2-0-0 + +one-ping-only: + description: | + This ping is for tests only. + include_client_id: false + send_if_empty: true + bugs: + - https://bugzilla.mozilla.org/1673660 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673660#c1 + notification_emails: + - chutten@mozilla.com + - glean-team@mozilla.com + no_lint: + - REDUNDANT_PING + +test-ping: + description: | + This ping is for tests only. + include_client_id: false + send_if_empty: true + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1737157 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1737157#c1 + notification_emails: + - chutten@mozilla.com + - glean-team@mozilla.com + no_lint: + - REDUNDANT_PING diff --git a/toolkit/components/glean/tests/xpcshell/head.js b/toolkit/components/glean/tests/xpcshell/head.js new file mode 100644 index 0000000000..f42bd02822 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/head.js @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); diff --git a/toolkit/components/glean/tests/xpcshell/test_FOGIPCLimit.js b/toolkit/components/glean/tests/xpcshell/test_FOGIPCLimit.js new file mode 100644 index 0000000000..10ab9f5bc3 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_FOGIPCLimit.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const { ContentTaskUtils } = ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" +); + +add_setup( + /* on Android FOG is set up through head.js */ + { skip_if: () => !runningInParent || AppConstants.platform == "android" }, + function test_setup() { + // Give FOG a temp profile to init within. + do_get_profile(); + + // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. + Services.fog.initializeFOG(); + } +); + +// Keep in sync with ipc.rs. +// "Why no -1?" Because the limit's 100k. The -1 is because of atomic ops. +const FOG_IPC_PAYLOAD_ACCESS_LIMIT = 100000; + +add_task({ skip_if: () => runningInParent }, async function run_child_stuff() { + for (let i = 0; i < FOG_IPC_PAYLOAD_ACCESS_LIMIT + 1; i++) { + Glean.testOnly.badCode.add(1); + } +}); + +add_task( + { skip_if: () => !runningInParent }, + async function test_fog_ipc_limit() { + await run_test_in_child("test_FOGIPCLimit.js"); + + await ContentTaskUtils.waitForCondition(() => { + return !!Glean.testOnly.badCode.testGetValue(); + }, "Waiting for IPC."); + + // The child exceeded the number of accesses to trigger an IPC flush. + Assert.greater( + Glean.testOnly.badCode.testGetValue(), + FOG_IPC_PAYLOAD_ACCESS_LIMIT + ); + } +); diff --git a/toolkit/components/glean/tests/xpcshell/test_FOGInit.js b/toolkit/components/glean/tests/xpcshell/test_FOGInit.js new file mode 100644 index 0000000000..f8c4a410ad --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_FOGInit.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +add_setup( + /* on Android FOG is set up through head.js */ + { skip_if: () => AppConstants.platform == "android" }, + function test_setup() { + // FOG needs a profile directory to put its data in. + do_get_profile(); + + // Glean init (via `chrono`) gets the timezone via unprotected write. + // This is being worked around: + // https://github.com/chronotope/chrono/pull/677 + // Until that reaches a release and we update to it (bug 1780401), ensure + // local time has been loaded by JS before we kick of Glean init. + new Date().getHours(); // used for its side effect. + + // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. + Services.fog.initializeFOG(); + } +); + +add_task(function test_fog_init_works() { + if (new Date().getHours() >= 3 && new Date().getHours() <= 4) { + // We skip this test if it's too close to 4AM, when we might send a + // "metrics" ping between init and this test being run. + Assert.ok(true, "Too close to 'metrics' ping send window. Skipping test."); + return; + } + Assert.greater( + Glean.fog.initialization.testGetValue(), + 0, + "FOG init happened, and its time was measured." + ); +}); diff --git a/toolkit/components/glean/tests/xpcshell/test_FOGPrefs.js b/toolkit/components/glean/tests/xpcshell/test_FOGPrefs.js new file mode 100644 index 0000000000..943ffb3186 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_FOGPrefs.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TELEMETRY_SERVER_PREF = "toolkit.telemetry.server"; +const UPLOAD_PREF = "datareporting.healthreport.uploadEnabled"; +const LOCALHOST_PREF = "telemetry.fog.test.localhost_port"; + +// FOG needs a profile directory to put its data in. +do_get_profile(); + +// We want Glean to use a localhost server so we can be SURE not to send data to the outside world. +// Yes, the port spells GLEAN on a T9 keyboard, why do you ask? +Services.prefs.setIntPref(LOCALHOST_PREF, 45326); +// We need to initialize it once, otherwise operations will be stuck in the pre-init queue. +Services.fog.initializeFOG(); + +add_task(function test_fog_upload_only() { + // Don't forget to point the telemetry server to localhost, or Telemetry + // might make a non-local connection during the test run. + Services.prefs.setStringPref( + TELEMETRY_SERVER_PREF, + "http://localhost/telemetry-fake/" + ); + // Be sure to set port=-1 for faking success _before_ enabling upload. + // Or else there's a short window where we might send something. + Services.prefs.setIntPref(LOCALHOST_PREF, -1); + Services.prefs.setBoolPref(UPLOAD_PREF, true); + + const value = 42; + Glean.testOnly.meaningOfLife.set(value); + // We specifically look at the custom ping's value because we know it + // won't be reset by being sent. + Assert.equal(value, Glean.testOnly.meaningOfLife.testGetValue("test-ping")); + + // Despite upload being disabled, we keep the old values around. + Services.prefs.setBoolPref(UPLOAD_PREF, false); + Assert.equal(value, Glean.testOnly.meaningOfLife.testGetValue("test-ping")); + + // Now, when we turn the fake upload off, we clear the stores + Services.prefs.setIntPref(LOCALHOST_PREF, 0); + Assert.equal( + undefined, + Glean.testOnly.meaningOfLife.testGetValue("test-ping") + ); +}); diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js new file mode 100644 index 0000000000..cad1bc31c4 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js @@ -0,0 +1,538 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const Telemetry = Services.telemetry; + +function sleep(ms) { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function scalarValue(aScalarName) { + let snapshot = Telemetry.getSnapshotForScalars(); + return "parent" in snapshot ? snapshot.parent[aScalarName] : undefined; +} + +function keyedScalarValue(aScalarName) { + let snapshot = Telemetry.getSnapshotForKeyedScalars(); + return "parent" in snapshot ? snapshot.parent[aScalarName] : undefined; +} + +add_setup(function test_setup() { + // FOG needs a profile directory to put its data in. + do_get_profile(); + + Services.prefs.setBoolPref( + "toolkit.telemetry.testing.overrideProductsCheck", + true + ); + + // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. + // On Android FOG is set up through head.js. + if (AppConstants.platform != "android") { + Services.fog.initializeFOG(); + } +}); + +add_task(function test_gifft_counter() { + Glean.testOnlyIpc.aCounter.add(20); + Assert.equal(20, Glean.testOnlyIpc.aCounter.testGetValue()); + Assert.equal(20, scalarValue("telemetry.test.mirror_for_counter")); +}); + +add_task(function test_gifft_boolean() { + Glean.testOnlyIpc.aBool.set(false); + Assert.equal(false, Glean.testOnlyIpc.aBool.testGetValue()); + Assert.equal(false, scalarValue("telemetry.test.boolean_kind")); +}); + +add_task(function test_gifft_datetime() { + const dateStr = "2021-03-22T16:06:00"; + const value = new Date(dateStr); + Glean.testOnlyIpc.aDate.set(value.getTime() * 1000); + + let received = Glean.testOnlyIpc.aDate.testGetValue(); + Assert.equal(value.getTime(), received.getTime()); + Assert.ok(scalarValue("telemetry.test.mirror_for_date").startsWith(dateStr)); +}); + +add_task(function test_gifft_string() { + const value = "a string!"; + Glean.testOnlyIpc.aString.set(value); + + Assert.equal(value, Glean.testOnlyIpc.aString.testGetValue()); + Assert.equal(value, scalarValue("telemetry.test.multiple_stores_string")); +}); + +add_task(function test_gifft_memory_dist() { + Glean.testOnlyIpc.aMemoryDist.accumulate(7); + Glean.testOnlyIpc.aMemoryDist.accumulate(17); + + let data = Glean.testOnlyIpc.aMemoryDist.testGetValue(); + // `data.sum` is in bytes, but the metric is in KB. + Assert.equal(24 * 1024, data.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(data.values)) { + Assert.ok( + count == 0 || (count == 1 && (bucket == 6888 || bucket == 17109)), + `Only two buckets have a sample ${bucket} ${count}` + ); + } + + data = Telemetry.getHistogramById("TELEMETRY_TEST_LINEAR").snapshot(); + Telemetry.getHistogramById("TELEMETRY_TEST_LINEAR").clear(); + Assert.equal(24, data.sum, "Histogram's in `memory_unit` units"); + Assert.equal(2, data.values["1"], "Both samples in a low bucket"); + + // MemoryDistribution's Accumulate method to takes + // a platform specific type (size_t). + // Glean's, however, is i64, and, glean_memory_dist is uint64_t + // What happens when we give accumulate dubious values? + // This may occur on some uncommon platforms. + // Note: there are issues in JS with numbers above 2**53 + Glean.testOnlyIpc.aMemoryDist.accumulate(36893488147419103232); + let dubiousValue = Object.entries( + Glean.testOnlyIpc.aMemoryDist.testGetValue().values + )[0][1]; + Assert.equal( + dubiousValue, + 1, + "Greater than 64-Byte number did not accumulate correctly" + ); + + // Values lower than the out-of-range value are not clamped + // resulting in an exception being thrown from the glean side + // when the value exceeds the glean maximum allowed value + Glean.testOnlyIpc.aMemoryDist.accumulate(Math.pow(2, 31)); + Assert.throws( + () => Glean.testOnlyIpc.aMemoryDist.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Did not accumulate correctly" + ); +}); + +add_task(function test_gifft_custom_dist() { + Glean.testOnlyIpc.aCustomDist.accumulateSamples([7, 268435458]); + + let data = Glean.testOnlyIpc.aCustomDist.testGetValue(); + Assert.equal(7 + 268435458, data.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(data.values)) { + Assert.ok( + count == 0 || (count == 1 && (bucket == 1 || bucket == 268435456)), + `Only two buckets have a sample ${bucket} ${count}` + ); + } + + data = Telemetry.getHistogramById( + "TELEMETRY_TEST_MIRROR_FOR_CUSTOM" + ).snapshot(); + Telemetry.getHistogramById("TELEMETRY_TEST_MIRROR_FOR_CUSTOM").clear(); + Assert.equal(7 + 268435458, data.sum, "Sum in histogram is correct"); + Assert.equal(1, data.values["1"], "One sample in the low bucket"); + // Yes, the bucket is off-by-one compared to Glean. + Assert.equal(1, data.values["268435457"], "One sample in the next bucket"); +}); + +add_task(async function test_gifft_timing_dist() { + let t1 = Glean.testOnlyIpc.aTimingDist.start(); + // Interleave some other metric's samples. bug 1768636. + let ot1 = Glean.testOnly.whatTimeIsIt.start(); + let t2 = Glean.testOnlyIpc.aTimingDist.start(); + let ot2 = Glean.testOnly.whatTimeIsIt.start(); + Glean.testOnly.whatTimeIsIt.cancel(ot1); + Glean.testOnly.whatTimeIsIt.cancel(ot2); + + await sleep(5); + + let t3 = Glean.testOnlyIpc.aTimingDist.start(); + Glean.testOnlyIpc.aTimingDist.cancel(t1); + + await sleep(5); + + Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t2); // 10ms + Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t3); // 5ms + + let data = Glean.testOnlyIpc.aTimingDist.testGetValue(); + const NANOS_IN_MILLIS = 1e6; + // bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough. + const EPSILON = 40000; + + // Variance in timing makes getting the sum impossible to know. + // 10 and 5 input value can be trunacted to 4. + 9. >= 13. from cast + Assert.greater(data.sum, 13 * NANOS_IN_MILLIS - EPSILON); + + // No guarantees from timers means no guarantees on buckets. + // But we can guarantee it's only two samples. + Assert.equal( + 2, + Object.entries(data.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ), + "Only two buckets with samples" + ); + + data = Telemetry.getHistogramById("TELEMETRY_TEST_EXPONENTIAL").snapshot(); + // Suffers from same cast truncation issue of 9.... and 4.... values + Assert.greaterOrEqual(data.sum, 13, "Histogram's in milliseconds"); + Assert.equal( + 2, + Object.entries(data.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ), + "Only two samples" + ); +}); + +add_task(function test_gifft_string_list_works() { + const value = "a string!"; + const value2 = "another string!"; + const value3 = "yet another string."; + + // `set` doesn't work in the mirror, so use `add` + Glean.testOnlyIpc.aStringList.add(value); + Glean.testOnlyIpc.aStringList.add(value2); + Glean.testOnlyIpc.aStringList.add(value3); + + let val = Glean.testOnlyIpc.aStringList.testGetValue(); + // Note: This is incredibly fragile and will break if we ever rearrange items + // in the string list. + Assert.deepEqual([value, value2, value3], val); + + val = keyedScalarValue("telemetry.test.keyed_boolean_kind"); + // This too may be fragile. + Assert.deepEqual( + { + [value]: true, + [value2]: true, + [value3]: true, + }, + val + ); +}); + +add_task(function test_gifft_events() { + Telemetry.setEventRecordingEnabled("telemetry.test", true); + + Glean.testOnlyIpc.noExtraEvent.record(); + var events = Glean.testOnlyIpc.noExtraEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("no_extra_event", events[0].name); + + let extra = { extra1: "can set extras", extra2: "passing more data" }; + Glean.testOnlyIpc.anEvent.record(extra); + events = Glean.testOnlyIpc.anEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("an_event", events[0].name); + Assert.deepEqual(extra, events[0].extra); + + TelemetryTestUtils.assertEvents( + [ + ["telemetry.test", "not_expired_optout", "object1", undefined, undefined], + ["telemetry.test", "mirror_with_extra", "object1", null, extra], + ], + { category: "telemetry.test" } + ); +}); + +add_task(function test_gifft_uuid() { + const kTestUuid = "decafdec-afde-cafd-ecaf-decafdecafde"; + Glean.testOnlyIpc.aUuid.set(kTestUuid); + Assert.equal(kTestUuid, Glean.testOnlyIpc.aUuid.testGetValue()); + Assert.equal(kTestUuid, scalarValue("telemetry.test.string_kind")); +}); + +add_task(function test_gifft_labeled_counter() { + Assert.equal( + undefined, + Glean.testOnlyIpc.aLabeledCounter.a_label.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.testOnlyIpc.aLabeledCounter.a_label.add(1); + Glean.testOnlyIpc.aLabeledCounter.another_label.add(2); + Glean.testOnlyIpc.aLabeledCounter.a_label.add(3); + Assert.equal(4, Glean.testOnlyIpc.aLabeledCounter.a_label.testGetValue()); + Assert.equal( + 2, + Glean.testOnlyIpc.aLabeledCounter.another_label.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.testOnlyIpc.aLabeledCounter.__other__.testGetValue() + ); + Glean.testOnlyIpc.aLabeledCounter["1".repeat(72)].add(3); + Assert.throws( + () => Glean.testOnlyIpc.aLabeledCounter.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Can't get the value when you're error'd" + ); + + let value = keyedScalarValue( + "telemetry.test.another_mirror_for_labeled_counter" + ); + Assert.deepEqual( + { + a_label: 4, + another_label: 2, + ["1".repeat(72)]: 3, + }, + value + ); +}); + +add_task(async function test_gifft_timespan() { + // We start, briefly sleep and then stop. + // That guarantees some time to measure. + Glean.testOnly.mirrorTime.start(); + await sleep(10); + Glean.testOnly.mirrorTime.stop(); + + const NANOS_IN_MILLIS = 1e6; + // bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough. + const EPSILON = 40000; + Assert.greater( + Glean.testOnly.mirrorTime.testGetValue(), + 10 * NANOS_IN_MILLIS - EPSILON + ); + // Mirrored to milliseconds. + Assert.greaterOrEqual(scalarValue("telemetry.test.mirror_for_timespan"), 9); +}); + +add_task(async function test_gifft_timespan_raw() { + Glean.testOnly.mirrorTimeNanos.setRaw(15 /*ns*/); + + Assert.equal(15, Glean.testOnly.mirrorTimeNanos.testGetValue()); + // setRaw, unlike start/stop, mirrors the raw value directly. + Assert.equal(scalarValue("telemetry.test.mirror_for_timespan_nanos"), 15); +}); + +add_task(async function test_gifft_labeled_boolean() { + Assert.equal( + undefined, + Glean.testOnly.mirrorsForLabeledBools.a_label.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.testOnly.mirrorsForLabeledBools.a_label.set(true); + Glean.testOnly.mirrorsForLabeledBools.another_label.set(false); + Assert.equal( + true, + Glean.testOnly.mirrorsForLabeledBools.a_label.testGetValue() + ); + Assert.equal( + false, + Glean.testOnly.mirrorsForLabeledBools.another_label.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.testOnly.mirrorsForLabeledBools.__other__.testGetValue() + ); + Glean.testOnly.mirrorsForLabeledBools["1".repeat(72)].set(true); + Assert.throws( + () => Glean.testOnly.mirrorsForLabeledBools.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of a recording error." + ); + + // In Telemetry there is no invalid label + let value = keyedScalarValue("telemetry.test.mirror_for_labeled_bool"); + Assert.deepEqual( + { + a_label: true, + another_label: false, + ["1".repeat(72)]: true, + }, + value + ); +}); + +add_task(function test_gifft_boolean() { + Glean.testOnly.meaningOfLife.set(42); + Assert.equal(42, Glean.testOnly.meaningOfLife.testGetValue()); + Assert.equal(42, scalarValue("telemetry.test.mirror_for_quantity")); +}); + +add_task(function test_gifft_rate() { + Glean.testOnlyIpc.irate.addToNumerator(22); + Glean.testOnlyIpc.irate.addToDenominator(7); + Assert.deepEqual( + { numerator: 22, denominator: 7 }, + Glean.testOnlyIpc.irate.testGetValue() + ); + Assert.deepEqual( + { numerator: 22, denominator: 7 }, + keyedScalarValue("telemetry.test.mirror_for_rate") + ); +}); + +add_task( + { + // bug 1670259 to see if we can implement `testResetFOG` on Android. + skip_if: () => AppConstants.platform == "android", + }, + function test_gifft_numeric_limits() { + // Glean and Telemetry don't share the same storage sizes or signedness. + // Check the edges. + + // 0) Reset everything + Services.fog.testResetFOG(); + Services.telemetry.getSnapshotForHistograms("main", true /* aClearStore */); + Services.telemetry.getSnapshotForScalars("main", true /* aClearStore */); + Services.telemetry.getSnapshotForKeyedScalars( + "main", + true /* aClearStore */ + ); + + // 1) Counter: i32 (saturates), mirrored to uint Scalar: u32 (overflows) + // 1.1) Negative parameters refused. + Glean.testOnlyIpc.aCounter.add(-20); + // Unfortunately we can't check what the error was, due to API design. + // (chutten blames chutten for his shortsightedness) + Assert.throws( + () => Glean.testOnlyIpc.aCounter.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Can't get the value when you're error'd" + ); + Assert.equal(undefined, scalarValue("telemetry.test.mirror_for_counter")); + // Clear the error state + Services.fog.testResetFOG(); + + // 1.2) Values that sum larger than u32::max saturate (counter) and overflow (Scalar) + // Sums to 2^32 + 1 + Glean.testOnlyIpc.aCounter.add(Math.pow(2, 31) - 1); + Glean.testOnlyIpc.aCounter.add(1); + Glean.testOnlyIpc.aCounter.add(Math.pow(2, 31) - 1); + Glean.testOnlyIpc.aCounter.add(2); + // Glean doesn't actually throw on saturation (bug 1751469), + // so we can just check the saturation value. + Assert.equal( + Math.pow(2, 31) - 1, + Glean.testOnlyIpc.aCounter.testGetValue() + ); + // Telemetry will have wrapped around to 1 + Assert.equal(1, scalarValue("telemetry.test.mirror_for_counter")); + + // 2) Quantity: i64 (saturates), mirrored to uint Scalar: u32 (overflows) + // 2.1) Negative parameters refused. + Glean.testOnly.meaningOfLife.set(-42); + // Glean will error on this. + Assert.throws( + () => Glean.testOnly.meaningOfLife.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Can't get the value when you're error'd" + ); + // GIFFT doesn't tell Telemetry about the weird value at all. + Assert.equal(undefined, scalarValue("telemetry.test.mirror_for_quantity")); + // Clear the error state + Services.fog.testResetFOG(); + + // 2.2) A parameter larger than u32::max is passed to Glean unchanged, + // but is clamped to u32::max before being passed to Telemetry. + Glean.testOnly.meaningOfLife.set(Math.pow(2, 32)); + Assert.equal(Math.pow(2, 32), Glean.testOnly.meaningOfLife.testGetValue()); + Assert.equal( + Math.pow(2, 32) - 1, + scalarValue("telemetry.test.mirror_for_quantity") + ); + + // 3) Rate: two i32 (saturates), mirrored to keyed uint Scalar: u32s (overflow) + // 3.1) Negative parameters refused. + Glean.testOnlyIpc.irate.addToNumerator(-22); + Glean.testOnlyIpc.irate.addToDenominator(7); + Assert.throws( + () => Glean.testOnlyIpc.irate.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Can't get the value when you're error'd" + ); + Assert.deepEqual( + { denominator: 7 }, + keyedScalarValue("telemetry.test.mirror_for_rate") + ); + // Clear the error state + Services.fog.testResetFOG(); + // Clear the partial Telemetry value + Services.telemetry.getSnapshotForKeyedScalars( + "main", + true /* aClearStore */ + ); + + // Now the denominator: + Glean.testOnlyIpc.irate.addToNumerator(22); + Glean.testOnlyIpc.irate.addToDenominator(-7); + Assert.throws( + () => Glean.testOnlyIpc.irate.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Can't get the value when you're error'd" + ); + Assert.deepEqual( + { numerator: 22 }, + keyedScalarValue("telemetry.test.mirror_for_rate") + ); + + // 4) Timespan + // ( Can't overflow time without finding a way to get TimeStamp to think + // we're 2^32 milliseconds later without waiting a month ) + + // 5) TimingDistribution + // ( Can't overflow time with start() and stopAndAccumulate() without + // waiting for ages. But we _do_ have a test-only raw API...) + // The max sample for timing_distribution is 600000000000. + // The type for timing_distribution samples is i64. + // This means when we explore the edges of GIFFT's limits, we're well past + // Glean's limits. All we can get out of Glean is errors. + // (Which is good for data, difficult for tests.) + // But GIFFT should properly saturate in Telemetry at i32::max, + // so we shall test that. + Glean.testOnlyIpc.aTimingDist.testAccumulateRawMillis(Math.pow(2, 31) + 1); + Glean.testOnlyIpc.aTimingDist.testAccumulateRawMillis(Math.pow(2, 32) + 1); + Assert.throws( + () => Glean.testOnlyIpc.aTimingDist.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Can't get the value when you're error'd" + ); + let snapshot = Telemetry.getHistogramById( + "TELEMETRY_TEST_EXPONENTIAL" + ).snapshot(); + Assert.equal( + snapshot.values["2147483646"], + 2, + "samples > i32::max should end up in the top bucket" + ); + } +); + +add_task(function test_gifft_url() { + const value = "https://www.example.com"; + Glean.testOnlyIpc.aUrl.set(value); + + Assert.equal(value, Glean.testOnlyIpc.aUrl.testGetValue()); + Assert.equal(value, scalarValue("telemetry.test.mirror_for_url")); +}); + +add_task(function test_gifft_url_cropped() { + const value = `https://example.com${"/test".repeat(47)}`; + Glean.testOnlyIpc.aUrl.set(value); + + Assert.equal(value, Glean.testOnlyIpc.aUrl.testGetValue()); + // We expect the mirrored URL to be truncated at the maximum + // length supported by string scalars. + Assert.equal( + value.substring(0, 50), + scalarValue("telemetry.test.mirror_for_url") + ); +}); diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js new file mode 100644 index 0000000000..bac716e764 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js @@ -0,0 +1,315 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { ContentTaskUtils } = ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); +const Telemetry = Services.telemetry; +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +function sleep(ms) { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function scalarValue(aScalarName, aProcessName) { + let snapshot = Telemetry.getSnapshotForScalars(); + return aProcessName in snapshot + ? snapshot[aProcessName][aScalarName] + : undefined; +} + +function keyedScalarValue(aScalarName, aProcessName) { + let snapshot = Telemetry.getSnapshotForKeyedScalars(); + return aProcessName in snapshot + ? snapshot[aProcessName][aScalarName] + : undefined; +} + +add_setup({ skip_if: () => !runningInParent }, function test_setup() { + // Give FOG a temp profile to init within. + do_get_profile(); + + // Allows these tests to properly run on e.g. Thunderbird + Services.prefs.setBoolPref( + "toolkit.telemetry.testing.overrideProductsCheck", + true + ); + + // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. + // on Android FOG is set up through head.js + if (AppConstants.platform != "android") { + Services.fog.initializeFOG(); + } +}); + +const COUNT = 42; +const CHEESY_STRING = "a very cheesy string!"; +const CHEESIER_STRING = "a much cheesier string!"; +const CUSTOM_SAMPLES = [3, 4]; +const EVENT_EXTRA = { extra1: "so very extra" }; +const MEMORIES = [13, 31]; +const MEMORY_BUCKETS = ["13193", "31378"]; // buckets are strings : | +const A_LABEL_COUNT = 3; +const ANOTHER_LABEL_COUNT = 5; +const INVALID_COUNTERS = 7; +const IRATE_NUMERATOR = 44; +const IRATE_DENOMINATOR = 14; + +add_task({ skip_if: () => runningInParent }, async function run_child_stuff() { + let oldCanRecordBase = Telemetry.canRecordBase; + Telemetry.canRecordBase = true; // Ensure we're able to record things. + + Glean.testOnlyIpc.aCounter.add(COUNT); + Glean.testOnlyIpc.aStringList.add(CHEESY_STRING); + Glean.testOnlyIpc.aStringList.add(CHEESIER_STRING); + + Glean.testOnlyIpc.noExtraEvent.record(); + Glean.testOnlyIpc.anEvent.record(EVENT_EXTRA); + + for (let memory of MEMORIES) { + Glean.testOnlyIpc.aMemoryDist.accumulate(memory); + } + + let t1 = Glean.testOnlyIpc.aTimingDist.start(); + let t2 = Glean.testOnlyIpc.aTimingDist.start(); + + await sleep(5); + + let t3 = Glean.testOnlyIpc.aTimingDist.start(); + Glean.testOnlyIpc.aTimingDist.cancel(t1); + + await sleep(5); + + Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t2); // 10ms + Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t3); // 5ms + + Glean.testOnlyIpc.aCustomDist.accumulateSamples(CUSTOM_SAMPLES); + + Glean.testOnlyIpc.aLabeledCounter.a_label.add(A_LABEL_COUNT); + Glean.testOnlyIpc.aLabeledCounter.another_label.add(ANOTHER_LABEL_COUNT); + + // Has to be different from aLabeledCounter so the error we record doesn't + // get in the way. + Glean.testOnlyIpc.anotherLabeledCounter["1".repeat(72)].add(INVALID_COUNTERS); + + Glean.testOnlyIpc.irate.addToNumerator(IRATE_NUMERATOR); + Glean.testOnlyIpc.irate.addToDenominator(IRATE_DENOMINATOR); + Telemetry.canRecordBase = oldCanRecordBase; +}); + +add_task( + { skip_if: () => !runningInParent }, + async function test_child_metrics() { + Telemetry.setEventRecordingEnabled("telemetry.test", true); + + // Clear any stray Telemetry data + Telemetry.clearScalars(); + Telemetry.getSnapshotForHistograms("main", true); + Telemetry.clearEvents(); + + await run_test_in_child("test_GIFFTIPC.js"); + + // Wait for both IPC mechanisms to flush. + await Services.fog.testFlushAllChildren(); + await ContentTaskUtils.waitForCondition(() => { + let snapshot = Telemetry.getSnapshotForKeyedScalars(); + return ( + "content" in snapshot && + "telemetry.test.mirror_for_rate" in snapshot.content + ); + }, "failed to find content telemetry in parent"); + + // boolean + // Doesn't work over IPC + + // counter + Assert.equal(Glean.testOnlyIpc.aCounter.testGetValue(), COUNT); + Assert.equal( + scalarValue("telemetry.test.mirror_for_counter", "content"), + COUNT, + "content-process Scalar has expected count" + ); + + // custom_distribution + const customSampleSum = CUSTOM_SAMPLES.reduce((acc, a) => acc + a, 0); + const customData = Glean.testOnlyIpc.aCustomDist.testGetValue("store1"); + Assert.equal(customSampleSum, customData.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(customData.values)) { + Assert.ok( + count == 0 || (count == CUSTOM_SAMPLES.length && bucket == 1), // both values in the low bucket + `Only two buckets have a sample ${bucket} ${count}` + ); + } + const histSnapshot = Telemetry.getSnapshotForHistograms( + "main", + false, + false + ); + const histData = histSnapshot.content.TELEMETRY_TEST_MIRROR_FOR_CUSTOM; + Assert.equal(customSampleSum, histData.sum, "Sum in histogram's correct"); + Assert.equal(2, histData.values["1"], "Two samples in the first bucket"); + + // datetime + // Doesn't work over IPC + + // event + var events = Glean.testOnlyIpc.noExtraEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("no_extra_event", events[0].name); + + events = Glean.testOnlyIpc.anEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("an_event", events[0].name); + Assert.deepEqual(EVENT_EXTRA, events[0].extra); + + TelemetryTestUtils.assertEvents( + [ + [ + "telemetry.test", + "not_expired_optout", + "object1", + undefined, + undefined, + ], + ["telemetry.test", "mirror_with_extra", "object1", null, EVENT_EXTRA], + ], + { category: "telemetry.test" }, + { process: "content" } + ); + + // labeled_boolean + // Doesn't work over IPC + + // labeled_counter + const counters = Glean.testOnlyIpc.aLabeledCounter; + Assert.equal(counters.a_label.testGetValue(), A_LABEL_COUNT); + Assert.equal(counters.another_label.testGetValue(), ANOTHER_LABEL_COUNT); + + Assert.throws( + () => Glean.testOnlyIpc.anotherLabeledCounter.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Invalid labels record errors, which throw" + ); + + let value = keyedScalarValue( + "telemetry.test.another_mirror_for_labeled_counter", + "content" + ); + Assert.deepEqual( + { + a_label: A_LABEL_COUNT, + another_label: ANOTHER_LABEL_COUNT, + }, + value + ); + value = keyedScalarValue( + "telemetry.test.mirror_for_labeled_counter", + "content" + ); + Assert.deepEqual( + { + ["1".repeat(72)]: INVALID_COUNTERS, + }, + value + ); + + // labeled_string + // Doesn't work over IPC + + // memory_distribution + const memoryData = Glean.testOnlyIpc.aMemoryDist.testGetValue(); + const memorySum = MEMORIES.reduce((acc, a) => acc + a, 0); + // The sum's in bytes, but the metric's in KB + Assert.equal(memorySum * 1024, memoryData.sum); + for (let [bucket, count] of Object.entries(memoryData.values)) { + // We could assert instead, but let's skip to save the logspam. + if (count == 0) { + continue; + } + Assert.ok(count == 1 && MEMORY_BUCKETS.includes(bucket)); + } + + const memoryHist = histSnapshot.content.TELEMETRY_TEST_LINEAR; + Assert.equal( + memorySum, + memoryHist.sum, + "Histogram's in `memory_unit` units" + ); + Assert.equal(2, memoryHist.values["1"], "Samples are in the right bucket"); + + // quantity + // Doesn't work over IPC + + // rate + Assert.deepEqual( + { numerator: IRATE_NUMERATOR, denominator: IRATE_DENOMINATOR }, + Glean.testOnlyIpc.irate.testGetValue() + ); + Assert.deepEqual( + { numerator: IRATE_NUMERATOR, denominator: IRATE_DENOMINATOR }, + keyedScalarValue("telemetry.test.mirror_for_rate", "content") + ); + + // string + // Doesn't work over IPC + + // string_list + // Note: this will break if string list ever rearranges its items. + const cheesyStrings = Glean.testOnlyIpc.aStringList.testGetValue(); + Assert.deepEqual(cheesyStrings, [CHEESY_STRING, CHEESIER_STRING]); + // Note: this will break if keyed scalars rearrange their items. + Assert.deepEqual( + { + [CHEESY_STRING]: true, + [CHEESIER_STRING]: true, + }, + keyedScalarValue("telemetry.test.keyed_boolean_kind", "content") + ); + + // timespan + // Doesn't work over IPC + + // timing_distribution + const NANOS_IN_MILLIS = 1e6; + const EPSILON = 40000; // bug 1701949 + const times = Glean.testOnlyIpc.aTimingDist.testGetValue(); + Assert.greater(times.sum, 15 * NANOS_IN_MILLIS - EPSILON); + // We can't guarantee any specific time values (thank you clocks), + // but we can assert there are only two samples. + Assert.equal( + 2, + Object.entries(times.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ) + ); + const timingHist = histSnapshot.content.TELEMETRY_TEST_EXPONENTIAL; + Assert.greaterOrEqual(timingHist.sum, 13, "Histogram's in milliseconds."); + // Both values, 10 and 5, are truncated by a cast in AccumulateTimeDelta + // Minimally downcast 9. + 4. could realistically result in 13. + Assert.equal( + 2, + Object.entries(timingHist.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ), + "Only two samples" + ); + + // uuid + // Doesn't work over IPC + } +); diff --git a/toolkit/components/glean/tests/xpcshell/test_Glean.js b/toolkit/components/glean/tests/xpcshell/test_Glean.js new file mode 100644 index 0000000000..369635b2a0 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_Glean.js @@ -0,0 +1,438 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +function sleep(ms) { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + return new Promise(resolve => setTimeout(resolve, ms)); +} + +add_setup( + /* on Android FOG is set up through head.js */ + { skip_if: () => AppConstants.platform == "android" }, + function test_setup() { + // FOG needs a profile directory to put its data in. + do_get_profile(); + + // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. + Services.fog.initializeFOG(); + } +); + +add_task(function test_fog_counter_works() { + Glean.testOnly.badCode.add(31); + Assert.equal(31, Glean.testOnly.badCode.testGetValue("test-ping")); +}); + +add_task(async function test_fog_string_works() { + const value = "a cheesy string!"; + Glean.testOnly.cheesyString.set(value); + + Assert.equal(value, Glean.testOnly.cheesyString.testGetValue("test-ping")); +}); + +add_task(async function test_fog_string_list_works() { + const value = "a cheesy string!"; + const value2 = "a cheesier string!"; + const value3 = "the cheeziest of strings."; + + const cheeseList = [value, value2]; + Glean.testOnly.cheesyStringList.set(cheeseList); + + let val = Glean.testOnly.cheesyStringList.testGetValue(); + // Note: This is incredibly fragile and will break if we ever rearrange items + // in the string list. + Assert.deepEqual(cheeseList, val); + + Glean.testOnly.cheesyStringList.add(value3); + Assert.ok(Glean.testOnly.cheesyStringList.testGetValue().includes(value3)); +}); + +add_task(async function test_fog_timespan_works() { + Glean.testOnly.canWeTimeIt.start(); + Glean.testOnly.canWeTimeIt.cancel(); + Assert.equal(undefined, Glean.testOnly.canWeTimeIt.testGetValue()); + + // We start, briefly sleep and then stop. + // That guarantees some time to measure. + Glean.testOnly.canWeTimeIt.start(); + await sleep(10); + Glean.testOnly.canWeTimeIt.stop(); + + Assert.ok(Glean.testOnly.canWeTimeIt.testGetValue("test-ping") > 0); +}); + +add_task(async function test_fog_timespan_throws_on_stop_wout_start() { + Glean.testOnly.canWeTimeIt.stop(); + Assert.throws( + () => Glean.testOnly.canWeTimeIt.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because stop was called without start." + ); +}); + +add_task(async function test_fog_uuid_works() { + const kTestUuid = "decafdec-afde-cafd-ecaf-decafdecafde"; + Glean.testOnly.whatIdIt.set(kTestUuid); + Assert.equal(kTestUuid, Glean.testOnly.whatIdIt.testGetValue("test-ping")); + + Glean.testOnly.whatIdIt.generateAndSet(); + // Since we generate v4 UUIDs, and the first character of the third group + // isn't 4, this won't ever collide with kTestUuid. + Assert.notEqual(kTestUuid, Glean.testOnly.whatIdIt.testGetValue("test-ping")); +}); + +add_task(function test_fog_datetime_works() { + const value = new Date("2020-06-11T12:00:00"); + + Glean.testOnly.whatADate.set(value.getTime() * 1000); + + const received = Glean.testOnly.whatADate.testGetValue("test-ping"); + Assert.equal(received.getTime(), value.getTime()); +}); + +add_task(function test_fog_boolean_works() { + Glean.testOnly.canWeFlagIt.set(false); + Assert.equal(false, Glean.testOnly.canWeFlagIt.testGetValue("test-ping")); + // While you're here, might as well test that the ping name's optional. + Assert.equal(false, Glean.testOnly.canWeFlagIt.testGetValue()); +}); + +add_task(async function test_fog_event_works() { + Glean.testOnlyIpc.noExtraEvent.record(); + var events = Glean.testOnlyIpc.noExtraEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("no_extra_event", events[0].name); + + let extra = { extra1: "can set extras", extra2: "passing more data" }; + Glean.testOnlyIpc.anEvent.record(extra); + events = Glean.testOnlyIpc.anEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("an_event", events[0].name); + Assert.deepEqual(extra, events[0].extra); + + let extra2 = { + extra1: "can set extras", + extra2: 37, + extra3_longer_name: false, + }; + Glean.testOnlyIpc.eventWithExtra.record(extra2); + events = Glean.testOnlyIpc.eventWithExtra.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("event_with_extra", events[0].name); + let expectedExtra = { + extra1: "can set extras", + extra2: "37", + extra3_longer_name: "false", + }; + Assert.deepEqual(expectedExtra, events[0].extra); + + // Quantities need to be non-negative. + // This does not record a Glean error. + let extra4 = { + extra2: -1, + }; + Glean.testOnlyIpc.eventWithExtra.record(extra4); + events = Glean.testOnlyIpc.eventWithExtra.testGetValue(); + // Unchanged number of events + Assert.equal(1, events.length, "Recorded one event too many."); + + // camelCase extras work. + let extra5 = { + extra3LongerName: false, + }; + Glean.testOnlyIpc.eventWithExtra.record(extra5); + events = Glean.testOnlyIpc.eventWithExtra.testGetValue(); + Assert.equal(2, events.length, "Recorded one event too many."); + expectedExtra = { + extra3_longer_name: "false", + }; + Assert.deepEqual(expectedExtra, events[1].extra); + + // Invalid extra keys don't crash, the event is not recorded, + // but an error is recorded. + let extra3 = { + extra1_nonexistent_extra: "this does not crash", + }; + Glean.testOnlyIpc.eventWithExtra.record(extra3); + Assert.throws( + () => Glean.testOnlyIpc.eventWithExtra.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of a recording error." + ); +}); + +add_task(async function test_fog_memory_distribution_works() { + Glean.testOnly.doYouRemember.accumulate(7); + Glean.testOnly.doYouRemember.accumulate(17); + + let data = Glean.testOnly.doYouRemember.testGetValue("test-ping"); + // `data.sum` is in bytes, but the metric is in MB. + Assert.equal(24 * 1024 * 1024, data.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(data.values)) { + Assert.ok( + count == 0 || (count == 1 && (bucket == 17520006 || bucket == 7053950)), + "Only two buckets have a sample" + ); + } +}); + +add_task(async function test_fog_custom_distribution_works() { + Glean.testOnlyIpc.aCustomDist.accumulateSamples([7, 268435458]); + + let data = Glean.testOnlyIpc.aCustomDist.testGetValue("store1"); + Assert.equal(7 + 268435458, data.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(data.values)) { + Assert.ok( + count == 0 || (count == 1 && (bucket == 1 || bucket == 268435456)), + `Only two buckets have a sample ${bucket} ${count}` + ); + } + + // Negative values will not be recorded, instead an error is recorded. + Glean.testOnlyIpc.aCustomDist.accumulateSamples([-7]); + Assert.throws( + () => Glean.testOnlyIpc.aCustomDist.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/ + ); +}); + +add_task(function test_fog_custom_pings() { + Assert.ok("onePingOnly" in GleanPings); + let submitted = false; + Glean.testOnly.onePingOneBool.set(false); + GleanPings.onePingOnly.testBeforeNextSubmit(reason => { + submitted = true; + Assert.equal(false, Glean.testOnly.onePingOneBool.testGetValue()); + }); + GleanPings.onePingOnly.submit(); + Assert.ok(submitted, "Ping was submitted, callback was called."); +}); + +add_task(async function test_fog_timing_distribution_works() { + let t1 = Glean.testOnly.whatTimeIsIt.start(); + let t2 = Glean.testOnly.whatTimeIsIt.start(); + + await sleep(5); + + let t3 = Glean.testOnly.whatTimeIsIt.start(); + Glean.testOnly.whatTimeIsIt.cancel(t1); + + await sleep(5); + + Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t2); // 10ms + Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t3); // 5ms + + let data = Glean.testOnly.whatTimeIsIt.testGetValue(); + const NANOS_IN_MILLIS = 1e6; + // bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough. + const EPSILON = 40000; + + // Variance in timing makes getting the sum impossible to know. + Assert.greater(data.sum, 15 * NANOS_IN_MILLIS - EPSILON); + + // No guarantees from timers means no guarantees on buckets. + // But we can guarantee it's only two samples. + Assert.equal( + 2, + Object.entries(data.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ), + "Only two buckets with samples" + ); +}); + +add_task(async function test_fog_labels_conform() { + Glean.testOnly.mabelsLabelMaker.singleword.set("portmanteau"); + Assert.equal( + "portmanteau", + Glean.testOnly.mabelsLabelMaker.singleword.testGetValue() + ); + Glean.testOnly.mabelsLabelMaker.snake_case.set("snek"); + Assert.equal( + "snek", + Glean.testOnly.mabelsLabelMaker.snake_case.testGetValue() + ); + Glean.testOnly.mabelsLabelMaker["dash-character"].set("Dash Rendar"); + Assert.equal( + "Dash Rendar", + Glean.testOnly.mabelsLabelMaker["dash-character"].testGetValue() + ); + Glean.testOnly.mabelsLabelMaker["dot.separated"].set("dot product"); + Assert.equal( + "dot product", + Glean.testOnly.mabelsLabelMaker["dot.separated"].testGetValue() + ); + Glean.testOnly.mabelsLabelMaker.camelCase.set("wednesday"); + Assert.equal( + "wednesday", + Glean.testOnly.mabelsLabelMaker.camelCase.testGetValue() + ); + const veryLong = "1".repeat(72); + Glean.testOnly.mabelsLabelMaker[veryLong].set("seventy-two"); + Assert.throws( + () => Glean.testOnly.mabelsLabelMaker[veryLong].testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of an invalid label." + ); + // This test should _now_ throw because we are calling data after an invalid + // label has been set. + Assert.throws( + () => Glean.testOnly.mabelsLabelMaker["dot.separated"].testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of an invalid label." + ); +}); + +add_task(async function test_fog_labeled_boolean_works() { + Assert.equal( + undefined, + Glean.testOnly.mabelsLikeBalloons.at_parties.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.testOnly.mabelsLikeBalloons.at_parties.set(true); + Glean.testOnly.mabelsLikeBalloons.at_funerals.set(false); + Assert.equal( + true, + Glean.testOnly.mabelsLikeBalloons.at_parties.testGetValue() + ); + Assert.equal( + false, + Glean.testOnly.mabelsLikeBalloons.at_funerals.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue() + ); + Glean.testOnly.mabelsLikeBalloons["1".repeat(72)].set(true); + Assert.throws( + () => Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of a recording error." + ); +}); + +add_task(async function test_fog_labeled_counter_works() { + Assert.equal( + undefined, + Glean.testOnly.mabelsKitchenCounters.near_the_sink.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.testOnly.mabelsKitchenCounters.near_the_sink.add(1); + Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.add(2); + Assert.equal( + 1, + Glean.testOnly.mabelsKitchenCounters.near_the_sink.testGetValue() + ); + Assert.equal( + 2, + Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue() + ); + Glean.testOnly.mabelsKitchenCounters["1".repeat(72)].add(1); + Assert.throws( + () => Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of a recording error." + ); +}); + +add_task(async function test_fog_labeled_string_works() { + Assert.equal( + undefined, + Glean.testOnly.mabelsBalloonStrings.colour_of_99.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.testOnly.mabelsBalloonStrings.colour_of_99.set("crimson"); + Glean.testOnly.mabelsBalloonStrings.string_lengths.set("various"); + Assert.equal( + "crimson", + Glean.testOnly.mabelsBalloonStrings.colour_of_99.testGetValue() + ); + Assert.equal( + "various", + Glean.testOnly.mabelsBalloonStrings.string_lengths.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue() + ); + Glean.testOnly.mabelsBalloonStrings["1".repeat(72)].set("valid"); + Assert.throws( + () => Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/ + ); +}); + +add_task(function test_fog_quantity_works() { + Glean.testOnly.meaningOfLife.set(42); + Assert.equal(42, Glean.testOnly.meaningOfLife.testGetValue()); +}); + +add_task(function test_fog_rate_works() { + // 1) Standard rate with internal denominator + Glean.testOnlyIpc.irate.addToNumerator(22); + Glean.testOnlyIpc.irate.addToDenominator(7); + Assert.deepEqual( + { numerator: 22, denominator: 7 }, + Glean.testOnlyIpc.irate.testGetValue() + ); + + // 2) Rate with external denominator + Glean.testOnlyIpc.anExternalDenominator.add(11); + Glean.testOnlyIpc.rateWithExternalDenominator.addToNumerator(121); + Assert.equal(11, Glean.testOnlyIpc.anExternalDenominator.testGetValue()); + Assert.deepEqual( + { numerator: 121, denominator: 11 }, + Glean.testOnlyIpc.rateWithExternalDenominator.testGetValue() + ); +}); + +add_task(async function test_fog_url_works() { + const value = "https://www.example.com/fog"; + Glean.testOnlyIpc.aUrl.set(value); + + Assert.equal(value, Glean.testOnlyIpc.aUrl.testGetValue("store1")); +}); + +add_task(async function test_fog_text_works() { + const value = + "Before the risin' sun, we fly, So many roads to choose, We'll start out walkin' and learn to run, (We've only just begun)"; + Glean.testOnlyIpc.aText.set(value); + + let rslt = Glean.testOnlyIpc.aText.testGetValue(); + + Assert.equal(value, rslt); + + Assert.equal(121, rslt.length); +}); + +add_task(async function test_fog_text_works_unusual_character() { + const value = + "The secret to Dominique Ansel's viennoiserie is the use of Isigny Sainte-Mère butter and Les Grands Moulins de Paris flour"; + Glean.testOnlyIpc.aText.set(value); + + let rslt = Glean.testOnlyIpc.aText.testGetValue(); + + Assert.equal(value, rslt); + + Assert.greater(rslt.length, 100); +}); diff --git a/toolkit/components/glean/tests/xpcshell/test_GleanExperiments.js b/toolkit/components/glean/tests/xpcshell/test_GleanExperiments.js new file mode 100644 index 0000000000..cf8871e9d1 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_GleanExperiments.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// FOG needs a profile directory to put its data in. +do_get_profile(); + +// We need to initialize it once, otherwise operations will be stuck in the pre-init queue. +Services.fog.initializeFOG(); + +add_task(function test_fog_experiment_annotations() { + const id = "my-experiment-id"; + const branch = "my-branch"; + const extra = { extra_key: "extra_value" }; + Services.fog.setExperimentActive(id, branch, extra); + + let data = Services.fog.testGetExperimentData(id); + Assert.equal(data.branch, branch); + Assert.deepEqual(data.extra, extra); + + // Unknown id gets nothing. + Assert.equal(undefined, Services.fog.testGetExperimentData(id + id)); + + // Inactive id gets nothing. + Services.fog.setExperimentInactive(id); + Assert.equal(undefined, Services.fog.testGetExperimentData(id)); +}); diff --git a/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js new file mode 100644 index 0000000000..95d14cfa27 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js @@ -0,0 +1,157 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +function sleep(ms) { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + return new Promise(resolve => setTimeout(resolve, ms)); +} + +add_setup( + /* on Android FOG is set up through head.js */ + { skip_if: () => !runningInParent || AppConstants.platform == "android" }, + function test_setup() { + // Give FOG a temp profile to init within. + do_get_profile(); + + // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. + Services.fog.initializeFOG(); + } +); + +const BAD_CODE_COUNT = 42; +const CHEESY_STRING = "a very cheesy string!"; +const CHEESIER_STRING = "a much cheesier string!"; +const EVENT_EXTRA = { extra1: "so very extra" }; +const MEMORIES = [13, 31]; +const MEMORY_BUCKETS = ["13509772", "32131834"]; // buckets are strings : | +const COUNTERS_NEAR_THE_SINK = 3; +const COUNTERS_WITH_JUNK_ON_THEM = 5; +const INVALID_COUNTERS = 7; + +add_task({ skip_if: () => runningInParent }, async function run_child_stuff() { + Glean.testOnly.badCode.add(BAD_CODE_COUNT); + Glean.testOnly.cheesyStringList.add(CHEESY_STRING); + Glean.testOnly.cheesyStringList.add(CHEESIER_STRING); + + Glean.testOnlyIpc.noExtraEvent.record(); + Glean.testOnlyIpc.anEvent.record(EVENT_EXTRA); + + for (let memory of MEMORIES) { + Glean.testOnly.doYouRemember.accumulate(memory); + } + + let t1 = Glean.testOnly.whatTimeIsIt.start(); + let t2 = Glean.testOnly.whatTimeIsIt.start(); + + await sleep(5); + + let t3 = Glean.testOnly.whatTimeIsIt.start(); + Glean.testOnly.whatTimeIsIt.cancel(t1); + + await sleep(5); + + Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t2); // 10ms + Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t3); // 5ms + + Glean.testOnlyIpc.aCustomDist.accumulateSamples([3, 4]); + + Glean.testOnly.mabelsKitchenCounters.near_the_sink.add( + COUNTERS_NEAR_THE_SINK + ); + Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.add( + COUNTERS_WITH_JUNK_ON_THEM + ); + + Glean.testOnly.mabelsBathroomCounters["1".repeat(72)].add(INVALID_COUNTERS); + + Glean.testOnlyIpc.irate.addToNumerator(44); + Glean.testOnlyIpc.irate.addToDenominator(14); +}); + +add_task( + { skip_if: () => !runningInParent }, + async function test_child_metrics() { + await run_test_in_child("test_GleanIPC.js"); + await Services.fog.testFlushAllChildren(); + + Assert.equal(Glean.testOnly.badCode.testGetValue(), BAD_CODE_COUNT); + + // Note: this will break if string list ever rearranges its items. + const cheesyStrings = Glean.testOnly.cheesyStringList.testGetValue(); + Assert.deepEqual(cheesyStrings, [CHEESY_STRING, CHEESIER_STRING]); + + const data = Glean.testOnly.doYouRemember.testGetValue(); + Assert.equal(MEMORIES.reduce((a, b) => a + b, 0) * 1024 * 1024, data.sum); + for (let [bucket, count] of Object.entries(data.values)) { + // We could assert instead, but let's skip to save the logspam. + if (count == 0) { + continue; + } + Assert.ok(count == 1 && MEMORY_BUCKETS.includes(bucket)); + } + + const customData = Glean.testOnlyIpc.aCustomDist.testGetValue("store1"); + Assert.equal(3 + 4, customData.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(customData.values)) { + Assert.ok( + count == 0 || (count == 2 && bucket == 1), // both values in the low bucket + `Only two buckets have a sample ${bucket} ${count}` + ); + } + + var events = Glean.testOnlyIpc.noExtraEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("no_extra_event", events[0].name); + + events = Glean.testOnlyIpc.anEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("test_only.ipc", events[0].category); + Assert.equal("an_event", events[0].name); + Assert.deepEqual(EVENT_EXTRA, events[0].extra); + + const NANOS_IN_MILLIS = 1e6; + const EPSILON = 40000; // bug 1701949 + const times = Glean.testOnly.whatTimeIsIt.testGetValue(); + Assert.greater(times.sum, 15 * NANOS_IN_MILLIS - EPSILON); + // We can't guarantee any specific time values (thank you clocks), + // but we can assert there are only two samples. + Assert.equal( + 2, + Object.entries(times.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ) + ); + + const mabelsCounters = Glean.testOnly.mabelsKitchenCounters; + Assert.equal( + mabelsCounters.near_the_sink.testGetValue(), + COUNTERS_NEAR_THE_SINK + ); + Assert.equal( + mabelsCounters.with_junk_on_them.testGetValue(), + COUNTERS_WITH_JUNK_ON_THEM + ); + + Assert.throws( + () => Glean.testOnly.mabelsBathroomCounters.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Invalid labels record errors, which throw" + ); + + Assert.deepEqual( + { numerator: 44, denominator: 14 }, + Glean.testOnlyIpc.irate.testGetValue() + ); + } +); diff --git a/toolkit/components/glean/tests/xpcshell/test_GleanServerKnobs.js b/toolkit/components/glean/tests/xpcshell/test_GleanServerKnobs.js new file mode 100644 index 0000000000..46d98aebfd --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_GleanServerKnobs.js @@ -0,0 +1,174 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_setup(function test_setup() { + // Give FOG a temp profile to init within. + do_get_profile(); + + // We need to initialize it once, otherwise operations will be stuck in + // the pre-init queue. + Services.fog.initializeFOG(); +}); + +add_task(function test_fog_metrics_disabled_remotely() { + // Set a cheesy string in the test metric. This should record because the + // metric has `disabled: false` by default. + const str1 = "a cheesy string!"; + Glean.testOnly.cheesyString.set(str1); + Assert.equal(str1, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Create and set a feature configuration that disables the test metric. + const feature_config = { + "test_only.cheesy_string": false, + }; + Services.fog.setMetricsFeatureConfig(JSON.stringify(feature_config)); + + // Attempt to set another cheesy string in the test metric. This should not + // record because of the override to the metric's default value in the + // feature configuration. + const str2 = "another cheesy string!"; + Glean.testOnly.cheesyString.set(str2); + Assert.equal(str1, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Clear the configuration so it doesn't interfere with other tests. + Services.fog.setMetricsFeatureConfig("{}"); +}); + +add_task(function test_fog_multiple_metrics_disabled_remotely() { + // Set some test metrics. This should record because the metrics are + // `disabled: false` by default. + const str1 = "yet another a cheesy string!"; + Glean.testOnly.cheesyString.set(str1); + Assert.equal(str1, Glean.testOnly.cheesyString.testGetValue("test-ping")); + const qty1 = 42; + Glean.testOnly.meaningOfLife.set(qty1); + Assert.equal(qty1, Glean.testOnly.meaningOfLife.testGetValue("test-ping")); + + // Create and set a feature configuration that disables multiple test + // metrics. + var feature_config = { + "test_only.cheesy_string": false, + "test_only.meaning_of_life": false, + }; + Services.fog.setMetricsFeatureConfig(JSON.stringify(feature_config)); + + // Attempt to set the metrics again. This should not record because of the + // override to the metrics' default value in the feature configuration. + const str2 = "another cheesy string v2!"; + Glean.testOnly.cheesyString.set(str2); + Assert.equal(str1, Glean.testOnly.cheesyString.testGetValue("test-ping")); + const qty2 = 52; + Glean.testOnly.meaningOfLife.set(qty2); + Assert.equal(qty1, Glean.testOnly.meaningOfLife.testGetValue("test-ping")); + + // Change the feature configuration to re-enable the `cheesy_string` metric. + feature_config = { + "test_only.cheesy_string": true, + "test_only.meaning_of_life": false, + }; + Services.fog.setMetricsFeatureConfig(JSON.stringify(feature_config)); + + // Attempt to set the metrics again. This should only record `cheesy_string` + // because of the most recent feature configuration. + const str3 = "another cheesy string v3!"; + Glean.testOnly.cheesyString.set(str3); + Assert.equal(str3, Glean.testOnly.cheesyString.testGetValue("test-ping")); + const qty3 = 62; + Glean.testOnly.meaningOfLife.set(qty3); + Assert.equal(qty1, Glean.testOnly.meaningOfLife.testGetValue("test-ping")); + + // Clear the configuration so it doesn't interfere with other tests. This + // is done by passing in an empty dictionary. + Services.fog.setMetricsFeatureConfig("{}"); + + // Set some final metrics. This should record in both metrics because they + // are both `disabled: false` by default. + const str4 = "another a cheesy string v4"; + Glean.testOnly.cheesyString.set(str4); + Assert.equal(str4, Glean.testOnly.cheesyString.testGetValue("test-ping")); + const qty4 = 72; + Glean.testOnly.meaningOfLife.set(qty4); + Assert.equal(qty4, Glean.testOnly.meaningOfLife.testGetValue("test-ping")); +}); + +add_task(function test_fog_metrics_feature_config_api_handles_null_values() { + // Set a cheesy string in the test metric. This should record because the + // metric has `disabled: false` by default. + const str1 = "a cheesy string!"; + Glean.testOnly.cheesyString.set(str1); + Assert.equal(str1, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Create and set a feature configuration that disables the test metric. + const feature_config = { + "test_only.cheesy_string": false, + }; + Services.fog.setMetricsFeatureConfig(JSON.stringify(feature_config)); + + // Attempt to set another cheesy string in the test metric. This should not + // record because of the override to the metric's default value in the + // feature configuration. + const str2 = "another cheesy string v2"; + Glean.testOnly.cheesyString.set(str2); + Assert.equal(str1, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Set the configuration to `null`. + Services.fog.setMetricsFeatureConfig(null); + + // Attempt to set another cheesy string in the test metric. This should now + // record because `null` should clear the configuration. + Glean.testOnly.cheesyString.set(str2); + Assert.equal(str2, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Reset back to the feature configuration that disables the test metric. + Services.fog.setMetricsFeatureConfig(JSON.stringify(feature_config)); + + // Attempt to set another cheesy string in the test metric. This should not + // record because of the override to the metric's default value in the + // feature configuration. + Glean.testOnly.cheesyString.set(str1); + Assert.equal(str2, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Set the configuration to `""` to replicate getting an empty string from + // Nimbus. + Services.fog.setMetricsFeatureConfig(""); + + // Attempt to set another cheesy string in the test metric. This should now + // record again because `""` should be clear the configuration. + const str3 = "another cheesy string v3"; + Glean.testOnly.cheesyString.set(str3); + Assert.equal(str3, Glean.testOnly.cheesyString.testGetValue("test-ping")); +}); + +add_task(function test_fog_metrics_disabled_reset_fog_behavior() { + // Set a cheesy string in the test metric. This should record because the + // metric has `disabled: false` by default. + const str1 = "a cheesy string!"; + Glean.testOnly.cheesyString.set(str1); + Assert.equal(str1, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Create and set a feature configuration that disables the test metric. + const feature_config = { + "test_only.cheesy_string": false, + }; + Services.fog.setMetricsFeatureConfig(JSON.stringify(feature_config)); + + // Attempt to set another cheesy string in the test metric. This should not + // record because of the override to the metric's default value in the + // feature configuration. + const str2 = "another cheesy string!"; + Glean.testOnly.cheesyString.set(str2); + Assert.equal(str1, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Now reset FOG to ensure that the feature configuration is also reset. + Services.fog.testResetFOG(); + + // Attempt to set the string again in the test metric. This should now + // record normally because we reset FOG. + Glean.testOnly.cheesyString.set(str2); + Assert.equal(str2, Glean.testOnly.cheesyString.testGetValue("test-ping")); + + // Clear the configuration so it doesn't interfere with other tests. + Services.fog.setMetricsFeatureConfig("{}"); +}); diff --git a/toolkit/components/glean/tests/xpcshell/test_JOG.js b/toolkit/components/glean/tests/xpcshell/test_JOG.js new file mode 100644 index 0000000000..bce5f59387 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_JOG.js @@ -0,0 +1,739 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +function sleep(ms) { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + return new Promise(resolve => setTimeout(resolve, ms)); +} + +add_task( + /* on Android FOG is set up through head.js */ + { skip_if: () => AppConstants.platform == "android" }, + function test_setup() { + // FOG needs a profile directory to put its data in. + do_get_profile(); + + // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. + Services.fog.initializeFOG(); + } +); + +add_task(function test_jog_counter_works() { + Services.fog.testRegisterRuntimeMetric( + "counter", + "jog_cat", + "jog_counter", + ["test-only"], + `"ping"`, + false + ); + Glean.jogCat.jogCounter.add(53); + Assert.equal(53, Glean.jogCat.jogCounter.testGetValue()); +}); + +add_task(async function test_jog_string_works() { + const value = "an active string!"; + Services.fog.testRegisterRuntimeMetric( + "string", + "jog_cat", + "jog_string", + ["test-only"], + `"ping"`, + false + ); + Glean.jogCat.jogString.set(value); + + Assert.equal(value, Glean.jogCat.jogString.testGetValue()); +}); + +add_task(async function test_jog_string_list_works() { + const value = "an active string!"; + const value2 = "a more active string!"; + const value3 = "the most active of strings."; + Services.fog.testRegisterRuntimeMetric( + "string_list", + "jog_cat", + "jog_string_list", + ["test-only"], + `"ping"`, + false + ); + + const jogList = [value, value2]; + Glean.jogCat.jogStringList.set(jogList); + + let val = Glean.jogCat.jogStringList.testGetValue(); + // Note: This is incredibly fragile and will break if we ever rearrange items + // in the string list. + Assert.deepEqual(jogList, val); + + Glean.jogCat.jogStringList.add(value3); + Assert.ok(Glean.jogCat.jogStringList.testGetValue().includes(value3)); +}); + +add_task(async function test_jog_timespan_works() { + Services.fog.testRegisterRuntimeMetric( + "timespan", + "jog_cat", + "jog_timespan", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ time_unit: "millisecond" }) + ); + Glean.jogCat.jogTimespan.start(); + Glean.jogCat.jogTimespan.cancel(); + Assert.equal(undefined, Glean.jogCat.jogTimespan.testGetValue()); + + // We start, briefly sleep and then stop. + // That guarantees some time to measure. + Glean.jogCat.jogTimespan.start(); + await sleep(10); + Glean.jogCat.jogTimespan.stop(); + + Assert.ok(Glean.jogCat.jogTimespan.testGetValue() > 0); +}); + +add_task(async function test_jog_uuid_works() { + const kTestUuid = "decafdec-afde-cafd-ecaf-decafdecafde"; + Services.fog.testRegisterRuntimeMetric( + "uuid", + "jog_cat", + "jog_uuid", + ["test-only"], + `"ping"`, + false + ); + Glean.jogCat.jogUuid.set(kTestUuid); + Assert.equal(kTestUuid, Glean.jogCat.jogUuid.testGetValue()); + + Glean.jogCat.jogUuid.generateAndSet(); + // Since we generate v4 UUIDs, and the first character of the third group + // isn't 4, this won't ever collide with kTestUuid. + Assert.notEqual(kTestUuid, Glean.jogCat.jogUuid.testGetValue()); +}); + +add_task(function test_jog_datetime_works() { + const value = new Date("2020-06-11T12:00:00"); + Services.fog.testRegisterRuntimeMetric( + "datetime", + "jog_cat", + "jog_datetime", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ time_unit: "nanosecond" }) + ); + + Glean.jogCat.jogDatetime.set(value.getTime() * 1000); + + const received = Glean.jogCat.jogDatetime.testGetValue(); + Assert.equal(received.getTime(), value.getTime()); +}); + +add_task(function test_jog_boolean_works() { + Services.fog.testRegisterRuntimeMetric( + "boolean", + "jog_cat", + "jog_bool", + ["test-only"], + `"ping"`, + false + ); + Glean.jogCat.jogBool.set(false); + Assert.equal(false, Glean.jogCat.jogBool.testGetValue()); +}); + +add_task(async function test_jog_event_works() { + Services.fog.testRegisterRuntimeMetric( + "event", + "jog_cat", + "jog_event_no_extra", + ["test-only"], + `"ping"`, + false + ); + Glean.jogCat.jogEventNoExtra.record(); + var events = Glean.jogCat.jogEventNoExtra.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("jog_cat", events[0].category); + Assert.equal("jog_event_no_extra", events[0].name); + + Services.fog.testRegisterRuntimeMetric( + "event", + "jog_cat", + "jog_event", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ allowed_extra_keys: ["extra1", "extra2"] }) + ); + let extra = { extra1: "can set extras", extra2: "passing more data" }; + Glean.jogCat.jogEvent.record(extra); + events = Glean.jogCat.jogEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("jog_cat", events[0].category); + Assert.equal("jog_event", events[0].name); + Assert.deepEqual(extra, events[0].extra); + + Services.fog.testRegisterRuntimeMetric( + "event", + "jog_cat", + "jog_event_with_extra", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ + allowed_extra_keys: ["extra1", "extra2", "extra3_longer_name"], + }) + ); + let extra2 = { + extra1: "can set extras", + extra2: 37, + extra3_longer_name: false, + }; + Glean.jogCat.jogEventWithExtra.record(extra2); + events = Glean.jogCat.jogEventWithExtra.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("jog_cat", events[0].category); + Assert.equal("jog_event_with_extra", events[0].name); + let expectedExtra = { + extra1: "can set extras", + extra2: "37", + extra3_longer_name: "false", + }; + Assert.deepEqual(expectedExtra, events[0].extra); + + // Quantities need to be non-negative. + let extra4 = { + extra2: -1, + }; + Glean.jogCat.jogEventWithExtra.record(extra4); + events = Glean.jogCat.jogEventWithExtra.testGetValue(); + Assert.equal(1, events.length, "Recorded one event too many."); + + // Invalid extra keys don't crash, the event is not recorded. + let extra3 = { + extra1_nonexistent_extra: "this does not crash", + }; + Glean.jogCat.jogEventWithExtra.record(extra3); + // And test methods throw appropriately + Assert.throws( + () => Glean.jogCat.jogEventWithExtra.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/ + ); +}); + +add_task(async function test_jog_memory_distribution_works() { + Services.fog.testRegisterRuntimeMetric( + "memory_distribution", + "jog_cat", + "jog_memory_dist", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ memory_unit: "megabyte" }) + ); + Glean.jogCat.jogMemoryDist.accumulate(7); + Glean.jogCat.jogMemoryDist.accumulate(17); + + let data = Glean.jogCat.jogMemoryDist.testGetValue(); + // `data.sum` is in bytes, but the metric is in MB. + Assert.equal(24 * 1024 * 1024, data.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(data.values)) { + Assert.ok( + count == 0 || (count == 1 && (bucket == 17520006 || bucket == 7053950)), + "Only two buckets have a sample" + ); + } +}); + +add_task(async function test_jog_custom_distribution_works() { + Services.fog.testRegisterRuntimeMetric( + "custom_distribution", + "jog_cat", + "jog_custom_dist", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ + range_min: 1, + range_max: 2147483646, + bucket_count: 10, + histogram_type: "linear", + }) + ); + Glean.jogCat.jogCustomDist.accumulateSamples([7, 268435458]); + + let data = Glean.jogCat.jogCustomDist.testGetValue(); + Assert.equal(7 + 268435458, data.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(data.values)) { + Assert.ok( + count == 0 || (count == 1 && (bucket == 1 || bucket == 268435456)), + `Only two buckets have a sample ${bucket} ${count}` + ); + } + + // Negative values will not be recorded, instead an error is recorded. + Glean.jogCat.jogCustomDist.accumulateSamples([-7]); + Assert.throws( + () => Glean.jogCat.jogCustomDist.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/ + ); +}); + +add_task(async function test_jog_custom_pings() { + Services.fog.testRegisterRuntimeMetric( + "boolean", + "jog_cat", + "jog_ping_bool", + ["jog-ping"], + `"ping"`, + false + ); + Services.fog.testRegisterRuntimePing("jog-ping", true, true, []); + Assert.ok("jogPing" in GleanPings); + let submitted = false; + Glean.jogCat.jogPingBool.set(false); + GleanPings.jogPing.testBeforeNextSubmit(reason => { + submitted = true; + Assert.equal(false, Glean.jogCat.jogPingBool.testGetValue()); + }); + GleanPings.jogPing.submit(); + Assert.ok(submitted, "Ping was submitted, callback was called."); + // ping-lifetime value was cleared. + Assert.equal(undefined, Glean.jogCat.jogPingBool.testGetValue()); +}); + +add_task(async function test_jog_timing_distribution_works() { + Services.fog.testRegisterRuntimeMetric( + "timing_distribution", + "jog_cat", + "jog_timing_dist", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ time_unit: "microsecond" }) + ); + let t1 = Glean.jogCat.jogTimingDist.start(); + let t2 = Glean.jogCat.jogTimingDist.start(); + + await sleep(5); + + let t3 = Glean.jogCat.jogTimingDist.start(); + Glean.jogCat.jogTimingDist.cancel(t1); + + await sleep(5); + + Glean.jogCat.jogTimingDist.stopAndAccumulate(t2); // 10ms + Glean.jogCat.jogTimingDist.stopAndAccumulate(t3); // 5ms + + let data = Glean.jogCat.jogTimingDist.testGetValue(); + const NANOS_IN_MILLIS = 1e6; + // bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough. + const EPSILON = 40000; + + // Variance in timing makes getting the sum impossible to know. + Assert.greater(data.sum, 15 * NANOS_IN_MILLIS - EPSILON); + + // No guarantees from timers means no guarantees on buckets. + // But we can guarantee it's only two samples. + Assert.equal( + 2, + Object.entries(data.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ), + "Only two buckets with samples" + ); +}); + +add_task(async function test_jog_labeled_boolean_works() { + Services.fog.testRegisterRuntimeMetric( + "labeled_boolean", + "jog_cat", + "jog_labeled_bool", + ["test-only"], + `"ping"`, + false + ); + Assert.equal( + undefined, + Glean.jogCat.jogLabeledBool.label_1.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.jogCat.jogLabeledBool.label_1.set(true); + Glean.jogCat.jogLabeledBool.label_2.set(false); + Assert.equal(true, Glean.jogCat.jogLabeledBool.label_1.testGetValue()); + Assert.equal(false, Glean.jogCat.jogLabeledBool.label_2.testGetValue()); + // What about invalid/__other__? + Assert.equal(undefined, Glean.jogCat.jogLabeledBool.__other__.testGetValue()); + Glean.jogCat.jogLabeledBool.NowValidLabel.set(true); + Assert.ok(Glean.jogCat.jogLabeledBool.NowValidLabel.testGetValue()); + Glean.jogCat.jogLabeledBool["1".repeat(72)].set(true); + Assert.throws( + () => Glean.jogCat.jogLabeledBool.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of a recording error." + ); +}); + +add_task(async function test_jog_labeled_boolean_with_static_labels_works() { + Services.fog.testRegisterRuntimeMetric( + "labeled_boolean", + "jog_cat", + "jog_labeled_bool_with_labels", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ ordered_labels: ["label_1", "label_2"] }) + ); + Assert.equal( + undefined, + Glean.jogCat.jogLabeledBoolWithLabels.label_1.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.jogCat.jogLabeledBoolWithLabels.label_1.set(true); + Glean.jogCat.jogLabeledBoolWithLabels.label_2.set(false); + Assert.equal( + true, + Glean.jogCat.jogLabeledBoolWithLabels.label_1.testGetValue() + ); + Assert.equal( + false, + Glean.jogCat.jogLabeledBoolWithLabels.label_2.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.jogCat.jogLabeledBoolWithLabels.__other__.testGetValue() + ); + Glean.jogCat.jogLabeledBoolWithLabels.label_3.set(true); + Assert.equal( + true, + Glean.jogCat.jogLabeledBoolWithLabels.__other__.testGetValue() + ); + // TODO: Test that we have the right number and type of errors (bug 1683171) +}); + +add_task(async function test_jog_labeled_counter_works() { + Services.fog.testRegisterRuntimeMetric( + "labeled_counter", + "jog_cat", + "jog_labeled_counter", + ["test-only"], + `"ping"`, + false + ); + Assert.equal( + undefined, + Glean.jogCat.jogLabeledCounter.label_1.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.jogCat.jogLabeledCounter.label_1.add(1); + Glean.jogCat.jogLabeledCounter.label_2.add(2); + Assert.equal(1, Glean.jogCat.jogLabeledCounter.label_1.testGetValue()); + Assert.equal(2, Glean.jogCat.jogLabeledCounter.label_2.testGetValue()); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.jogCat.jogLabeledCounter.__other__.testGetValue() + ); + Glean.jogCat.jogLabeledCounter["1".repeat(72)].add(1); + Assert.throws( + () => Glean.jogCat.jogLabeledCounter.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of a recording error." + ); +}); + +add_task(async function test_jog_labeled_counter_with_static_labels_works() { + Services.fog.testRegisterRuntimeMetric( + "labeled_counter", + "jog_cat", + "jog_labeled_counter_with_labels", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ ordered_labels: ["label_1", "label_2"] }) + ); + Assert.equal( + undefined, + Glean.jogCat.jogLabeledCounterWithLabels.label_1.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.jogCat.jogLabeledCounterWithLabels.label_1.add(1); + Glean.jogCat.jogLabeledCounterWithLabels.label_2.add(2); + Assert.equal( + 1, + Glean.jogCat.jogLabeledCounterWithLabels.label_1.testGetValue() + ); + Assert.equal( + 2, + Glean.jogCat.jogLabeledCounterWithLabels.label_2.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.jogCat.jogLabeledCounterWithLabels.__other__.testGetValue() + ); + Glean.jogCat.jogLabeledCounterWithLabels["1".repeat(72)].add(1); + // TODO:(bug 1766515) - This should throw. + /*Assert.throws( + () => Glean.jogCat.jogLabeledCounterWithLabels.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Should throw because of a recording error." + );*/ + Assert.equal( + 1, + Glean.jogCat.jogLabeledCounterWithLabels.__other__.testGetValue() + ); +}); + +add_task(async function test_jog_labeled_string_works() { + Services.fog.testRegisterRuntimeMetric( + "labeled_string", + "jog_cat", + "jog_labeled_string", + ["test-only"], + `"ping"`, + false + ); + Assert.equal( + undefined, + Glean.jogCat.jogLabeledString.label_1.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.jogCat.jogLabeledString.label_1.set("crimson"); + Glean.jogCat.jogLabeledString.label_2.set("various"); + Assert.equal("crimson", Glean.jogCat.jogLabeledString.label_1.testGetValue()); + Assert.equal("various", Glean.jogCat.jogLabeledString.label_2.testGetValue()); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.jogCat.jogLabeledString.__other__.testGetValue() + ); + Glean.jogCat.jogLabeledString["1".repeat(72)].set("valid"); + Assert.throws( + () => Glean.jogCat.jogLabeledString.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/ + ); +}); + +add_task(async function test_jog_labeled_string_with_labels_works() { + Services.fog.testRegisterRuntimeMetric( + "labeled_string", + "jog_cat", + "jog_labeled_string_with_labels", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ ordered_labels: ["label_1", "label_2"] }) + ); + Assert.equal( + undefined, + Glean.jogCat.jogLabeledStringWithLabels.label_1.testGetValue(), + "New labels with no values should return undefined" + ); + Glean.jogCat.jogLabeledStringWithLabels.label_1.set("crimson"); + Glean.jogCat.jogLabeledStringWithLabels.label_2.set("various"); + Assert.equal( + "crimson", + Glean.jogCat.jogLabeledStringWithLabels.label_1.testGetValue() + ); + Assert.equal( + "various", + Glean.jogCat.jogLabeledStringWithLabels.label_2.testGetValue() + ); + // What about invalid/__other__? + Assert.equal( + undefined, + Glean.jogCat.jogLabeledStringWithLabels.__other__.testGetValue() + ); + Glean.jogCat.jogLabeledStringWithLabels["1".repeat(72)].set("valid"); + // TODO:(bug 1766515) - This should throw. + /*Assert.throws( + () => Glean.jogCat.jogLabeledStringWithLabels.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/ + );*/ + Assert.equal( + "valid", + Glean.jogCat.jogLabeledStringWithLabels.__other__.testGetValue() + ); +}); + +add_task(function test_jog_quantity_works() { + Services.fog.testRegisterRuntimeMetric( + "quantity", + "jog_cat", + "jog_quantity", + ["test-only"], + `"ping"`, + false + ); + Glean.jogCat.jogQuantity.set(42); + Assert.equal(42, Glean.jogCat.jogQuantity.testGetValue()); +}); + +add_task(function test_jog_rate_works() { + Services.fog.testRegisterRuntimeMetric( + "rate", + "jog_cat", + "jog_rate", + ["test-only"], + `"ping"`, + false + ); + // 1) Standard rate with internal denominator + Glean.jogCat.jogRate.addToNumerator(22); + Glean.jogCat.jogRate.addToDenominator(7); + Assert.deepEqual( + { numerator: 22, denominator: 7 }, + Glean.jogCat.jogRate.testGetValue() + ); + + Services.fog.testRegisterRuntimeMetric( + "denominator", + "jog_cat", + "jog_denominator", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ + numerators: [ + { + name: "jog_rate_ext", + category: "jog_cat", + send_in_pings: ["test-only"], + lifetime: "ping", + disabled: false, + }, + ], + }) + ); + Services.fog.testRegisterRuntimeMetric( + "rate", + "jog_cat", + "jog_rate_ext", + ["test-only"], + `"ping"`, + false + ); + // 2) Rate with external denominator + Glean.jogCat.jogDenominator.add(11); + Glean.jogCat.jogRateExt.addToNumerator(121); + Assert.equal(11, Glean.jogCat.jogDenominator.testGetValue()); + Assert.deepEqual( + { numerator: 121, denominator: 11 }, + Glean.jogCat.jogRateExt.testGetValue() + ); +}); + +add_task(function test_jog_dotted_categories_work() { + Services.fog.testRegisterRuntimeMetric( + "counter", + "jog_cat.dotted", + "jog_counter", + ["test-only"], + `"ping"`, + false + ); + Glean.jogCatDotted.jogCounter.add(314); + Assert.equal(314, Glean.jogCatDotted.jogCounter.testGetValue()); +}); + +add_task(async function test_jog_ping_works() { + const kReason = "reason-1"; + Services.fog.testRegisterRuntimePing("my-ping", true, true, [kReason]); + let submitted = false; + GleanPings.myPing.testBeforeNextSubmit(reason => { + submitted = true; + Assert.equal(kReason, reason); + }); + GleanPings.myPing.submit("reason-1"); + Assert.ok(submitted, "Ping must have been submitted"); +}); + +add_task(function test_jog_name_collision() { + Assert.ok("aCounter" in Glean.testOnlyJog); + Assert.equal(undefined, Glean.testOnlyJog.aCounter.testGetValue()); + const kValue = 42; + Glean.testOnlyJog.aCounter.add(kValue); + Assert.equal(kValue, Glean.testOnlyJog.aCounter.testGetValue()); + + // Let's overwrite the test_only.jog.a_counter counter. + Services.fog.testRegisterRuntimeMetric( + "counter", + "test_only.jog", + "a_counter", + ["store1"], + `"ping"`, + true // changing the metric to disabled. + ); + + Assert.ok("aCounter" in Glean.testOnlyJog); + Assert.equal(kValue, Glean.testOnlyJog.aCounter.testGetValue()); + Glean.testOnlyJog.aCounter.add(kValue); + Assert.equal( + kValue, + Glean.testOnlyJog.aCounter.testGetValue(), + "value of now-disabled metric remains unchanged." + ); + + // Now let's mess with events: + Assert.ok("anEvent" in Glean.testOnlyJog); + Assert.equal(undefined, Glean.testOnlyJog.anEvent.testGetValue()); + const extra12 = { + extra1: "a value", + extra2: "another value", + }; + Glean.testOnlyJog.anEvent.record(extra12); + Assert.deepEqual(extra12, Glean.testOnlyJog.anEvent.testGetValue()[0].extra); + Services.fog.testRegisterRuntimeMetric( + "event", + "test_only.jog", + "an_event", + ["store1"], + `"ping"`, + false, + JSON.stringify({ allowed_extra_keys: ["extra1", "extra2", "extra3"] }) // New extra key just dropped + ); + const extra123 = { + extra1: "different value", + extra2: "another different value", + extra3: 42, + }; + Glean.testOnlyJog.anEvent.record(extra123); + Assert.deepEqual(extra123, Glean.testOnlyJog.anEvent.testGetValue()[1].extra); +}); + +add_task(function test_enumerable_names() { + Assert.ok(Object.keys(Glean).includes("testOnlyJog")); + Assert.ok(Object.keys(Glean.testOnlyJog).includes("aCounter")); + Assert.ok(Object.keys(GleanPings).includes("testPing")); +}); + +add_task(async function test_jog_text_works() { + const kValue = + "In the heart of the Opéra district in Paris, the Cédric Grolet Opéra bakery-pastry shop is a veritable temple of gourmet delights."; + Services.fog.testRegisterRuntimeMetric( + "text", + "test_only.jog", + "a_text", + ["test-only"], + `"ping"`, + false + ); + Glean.testOnlyJog.aText.set(kValue); + + Assert.equal(kValue, Glean.testOnlyJog.aText.testGetValue()); +}); diff --git a/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js new file mode 100644 index 0000000000..508a76c463 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js @@ -0,0 +1,266 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +function sleep(ms) { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + return new Promise(resolve => setTimeout(resolve, ms)); +} + +add_task( + /* on Android FOG is set up through head.js */ + { skip_if: () => !runningInParent || AppConstants.platform == "android" }, + function test_setup() { + // Give FOG a temp profile to init within. + do_get_profile(); + + // We need to initialize it once, otherwise operations will be stuck in the pre-init queue. + Services.fog.initializeFOG(); + } +); + +const COUNT = 42; +const STRING = "a string!"; +const ANOTHER_STRING = "another string!"; +const EVENT_EXTRA = { extra1: "so very extra" }; +const MEMORIES = [13, 31]; +const MEMORY_BUCKETS = ["13509772", "32131834"]; // buckets are strings : | +const COUNTERS_1 = 3; +const COUNTERS_2 = 5; +const INVALID_COUNTERS = 7; + +// It is CRUCIAL that we register metrics in the same order in the parent and +// in the child or their metric ids will not line up and ALL WILL EXPLODE. +const METRICS = [ + ["counter", "jog_ipc", "jog_counter", ["test-only"], `"ping"`, false], + ["string_list", "jog_ipc", "jog_string_list", ["test-only"], `"ping"`, false], + ["event", "jog_ipc", "jog_event_no_extra", ["test-only"], `"ping"`, false], + [ + "event", + "jog_ipc", + "jog_event", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ allowed_extra_keys: ["extra1"] }), + ], + [ + "memory_distribution", + "jog_ipc", + "jog_memory_dist", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ memory_unit: "megabyte" }), + ], + [ + "timing_distribution", + "jog_ipc", + "jog_timing_dist", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ time_unit: "nanosecond" }), + ], + [ + "custom_distribution", + "jog_ipc", + "jog_custom_dist", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ + range_min: 1, + range_max: 2147483646, + bucket_count: 10, + histogram_type: "linear", + }), + ], + [ + "labeled_counter", + "jog_ipc", + "jog_labeled_counter", + ["test-only"], + `"ping"`, + false, + ], + [ + "labeled_counter", + "jog_ipc", + "jog_labeled_counter_err", + ["test-only"], + `"ping"`, + false, + ], + [ + "labeled_counter", + "jog_ipc", + "jog_labeled_counter_with_labels", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ ordered_labels: ["label_1", "label_2"] }), + ], + [ + "labeled_counter", + "jog_ipc", + "jog_labeled_counter_with_labels_err", + ["test-only"], + `"ping"`, + false, + JSON.stringify({ ordered_labels: ["label_1", "label_2"] }), + ], + ["rate", "jog_ipc", "jog_rate", ["test-only"], `"ping"`, false], +]; + +add_task({ skip_if: () => runningInParent }, async function run_child_stuff() { + // Ensure any _actual_ runtime metrics are registered first. + // Otherwise the jog_ipc.* ones will have incorrect ids. + Glean.testOnly.badCode; + for (let metric of METRICS) { + Services.fog.testRegisterRuntimeMetric(...metric); + } + Glean.jogIpc.jogCounter.add(COUNT); + Glean.jogIpc.jogStringList.add(STRING); + Glean.jogIpc.jogStringList.add(ANOTHER_STRING); + + Glean.jogIpc.jogEventNoExtra.record(); + Glean.jogIpc.jogEvent.record(EVENT_EXTRA); + + for (let memory of MEMORIES) { + Glean.jogIpc.jogMemoryDist.accumulate(memory); + } + + let t1 = Glean.jogIpc.jogTimingDist.start(); + let t2 = Glean.jogIpc.jogTimingDist.start(); + + await sleep(5); + + let t3 = Glean.jogIpc.jogTimingDist.start(); + Glean.jogIpc.jogTimingDist.cancel(t1); + + await sleep(5); + + Glean.jogIpc.jogTimingDist.stopAndAccumulate(t2); // 10ms + Glean.jogIpc.jogTimingDist.stopAndAccumulate(t3); // 5ms + + Glean.jogIpc.jogCustomDist.accumulateSamples([3, 4]); + + Glean.jogIpc.jogLabeledCounter.label_1.add(COUNTERS_1); + Glean.jogIpc.jogLabeledCounter.label_2.add(COUNTERS_2); + + Glean.jogIpc.jogLabeledCounterErr["1".repeat(72)].add(INVALID_COUNTERS); + + Glean.jogIpc.jogLabeledCounterWithLabels.label_1.add(COUNTERS_1); + Glean.jogIpc.jogLabeledCounterWithLabels.label_2.add(COUNTERS_2); + + Glean.jogIpc.jogLabeledCounterWithLabelsErr["1".repeat(72)].add( + INVALID_COUNTERS + ); + + Glean.jogIpc.jogRate.addToNumerator(44); + Glean.jogIpc.jogRate.addToDenominator(14); +}); + +add_task( + { skip_if: () => !runningInParent }, + async function test_child_metrics() { + // Ensure any _actual_ runtime metrics are registered first. + // Otherwise the jog_ipc.* ones will have incorrect ids. + Glean.testOnly.badCode; + for (let metric of METRICS) { + Services.fog.testRegisterRuntimeMetric(...metric); + } + await run_test_in_child("test_JOGIPC.js"); + await Services.fog.testFlushAllChildren(); + + Assert.equal(Glean.jogIpc.jogCounter.testGetValue(), COUNT); + + // Note: this will break if string list ever rearranges its items. + const strings = Glean.jogIpc.jogStringList.testGetValue(); + Assert.deepEqual(strings, [STRING, ANOTHER_STRING]); + + const data = Glean.jogIpc.jogMemoryDist.testGetValue(); + Assert.equal(MEMORIES.reduce((a, b) => a + b, 0) * 1024 * 1024, data.sum); + for (let [bucket, count] of Object.entries(data.values)) { + // We could assert instead, but let's skip to save the logspam. + if (count == 0) { + continue; + } + Assert.ok(count == 1 && MEMORY_BUCKETS.includes(bucket)); + } + + const customData = Glean.jogIpc.jogCustomDist.testGetValue(); + Assert.equal(3 + 4, customData.sum, "Sum's correct"); + for (let [bucket, count] of Object.entries(customData.values)) { + Assert.ok( + count == 0 || (count == 2 && bucket == 1), // both values in the low bucket + `Only two buckets have a sample ${bucket} ${count}` + ); + } + + let events = Glean.jogIpc.jogEventNoExtra.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("jog_ipc", events[0].category); + Assert.equal("jog_event_no_extra", events[0].name); + + events = Glean.jogIpc.jogEvent.testGetValue(); + Assert.equal(1, events.length); + Assert.equal("jog_ipc", events[0].category); + Assert.equal("jog_event", events[0].name); + Assert.deepEqual(EVENT_EXTRA, events[0].extra); + + const NANOS_IN_MILLIS = 1e6; + const EPSILON = 40000; // bug 1701949 + const times = Glean.jogIpc.jogTimingDist.testGetValue(); + Assert.greater(times.sum, 15 * NANOS_IN_MILLIS - EPSILON); + // We can't guarantee any specific time values (thank you clocks), + // but we can assert there are only two samples. + Assert.equal( + 2, + Object.entries(times.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ) + ); + + const labeledCounter = Glean.jogIpc.jogLabeledCounter; + Assert.equal(labeledCounter.label_1.testGetValue(), COUNTERS_1); + Assert.equal(labeledCounter.label_2.testGetValue(), COUNTERS_2); + + Assert.throws( + () => Glean.jogIpc.jogLabeledCounterErr.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Invalid labels record errors, which throw" + ); + + const labeledCounterWLabels = Glean.jogIpc.jogLabeledCounterWithLabels; + Assert.equal(labeledCounterWLabels.label_1.testGetValue(), COUNTERS_1); + Assert.equal(labeledCounterWLabels.label_2.testGetValue(), COUNTERS_2); + + // TODO:(bug 1766515) - This should throw. + /*Assert.throws( + () => + Glean.jogIpc.jogLabeledCounterWithLabelsErr.__other__.testGetValue(), + /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/, + "Invalid labels record errors, which throw" + );*/ + Assert.equal( + Glean.jogIpc.jogLabeledCounterWithLabelsErr.__other__.testGetValue(), + INVALID_COUNTERS + ); + + Assert.deepEqual( + { numerator: 44, denominator: 14 }, + Glean.jogIpc.jogRate.testGetValue() + ); + } +); diff --git a/toolkit/components/glean/tests/xpcshell/test_MillionQ.js b/toolkit/components/glean/tests/xpcshell/test_MillionQ.js new file mode 100644 index 0000000000..d98e73b451 --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/test_MillionQ.js @@ -0,0 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function test_queue_longer_than_1k() { + // FOG needs a profile directory to put its data in. + do_get_profile(); + + // Before init, try and fill the preinit queue with > 1000 tasks. + const kIterations = 2000; + for (let _i = 0; _i < kIterations; _i++) { + Glean.testOnly.badCode.add(1); + } + + Services.fog.initializeFOG(); + + Assert.equal(kIterations, Glean.testOnly.badCode.testGetValue()); +}); diff --git a/toolkit/components/glean/tests/xpcshell/xpcshell.ini b/toolkit/components/glean/tests/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..b727b31d0d --- /dev/null +++ b/toolkit/components/glean/tests/xpcshell/xpcshell.ini @@ -0,0 +1,33 @@ +# Please keep test files lexicographically sorted, with whitespace between. +[DEFAULT] +firefox-appdir = browser +head = head.js + +[test_FOGInit.js] + +[test_FOGIPCLimit.js] + +[test_FOGPrefs.js] +skip-if = os == "android" # FOG isn't responsible for monitoring prefs and controlling upload on Android + +[test_GIFFT.js] +run-sequentially = very high failure rate in parallel + +[test_GIFFTIPC.js] + +[test_Glean.js] + +[test_GleanIPC.js] + +[test_GleanExperiments.js] +skip-if = os == "android" # FOG isn't responsible for experiment annotations on Android + +[test_GleanServerKnobs.js] +skip-if = os == "android" # Server Knobs on mobile will be handled by the specific app + +[test_JOG.js] + +[test_JOGIPC.js] + +[test_MillionQ.js] +skip-if = os == "android" # Android inits its own FOG, so the test won't work. |