Bug 1698184 - Test runtime registration of FOG metrics r=janerik

Adds a test-only method to JS that permits the runtime registration of metrics.
Also uses that to cover JOG with tests: registering and smoke-testing metrics
of each metric type.
(Events being a notable (temporary) exception)

Instead of writing parsers, use serde_json for the optional extra metric args.

Differential Revision: https://phabricator.services.mozilla.com/D143051
This commit is contained in:
Chris H-C 2022-06-21 20:34:56 +00:00
Родитель 5de7c2d4c9
Коммит 557f0cf57b
10 изменённых файлов: 1045 добавлений и 28 удалений

5
Cargo.lock сгенерированный
Просмотреть файл

@ -2764,8 +2764,13 @@ name = "jog"
version = "0.1.0"
dependencies = [
"fog",
"log",
"mozbuild",
"nsstring",
"once_cell",
"serde",
"serde_json",
"thin-vec",
]
[[package]]

Просмотреть файл

@ -7,5 +7,13 @@ publish = false
[dependencies]
fog = { path = "../../api" }
once_cell = "1.2.0"
log = "0.4"
mozbuild = "0.1"
nsstring = { path = "../../../../../xpcom/rust/nsstring", optional = true }
once_cell = "1.2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
[features]
with_gecko = [ "nsstring" ]

Просмотреть файл

@ -14,10 +14,10 @@ line_length = 100
tab_width = 2
language = "C++"
namespaces = ["mozilla::glean::jog"]
#includes = ["nsTArray.h", "nsString.h"]
includes = ["nsTArray.h", "nsString.h"]
[export.rename]
#"ThinVec" = "nsTArray"
"ThinVec" = "nsTArray"
#"nsCStringRepr" = "nsCString"
[parse]

Просмотреть файл

@ -3,7 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use fog::factory;
use fog::private::Lifetime;
use fog::private::traits::HistogramType;
use fog::private::{CommonMetricData, MemoryUnit, TimeUnit};
#[cfg(feature = "with_gecko")]
use nsstring::{nsACString, nsCString};
use serde::Deserialize;
use thin_vec::ThinVec;
/// Activate the JOG runtime registrar.
/// This is where the Artefact Build support happens.
@ -11,39 +16,86 @@ use fog::private::Lifetime;
/// returns whether it successfully found and processed metrics files.
#[no_mangle]
pub extern "C" fn jog_runtime_registrar() -> bool {
let _ = factory::create_and_register_metric(
"counter",
"category".into(),
"name".into(),
vec!["store1".into()],
Lifetime::Ping,
true,
None,
None,
None,
None,
None,
None,
None,
None,
None,
);
false
}
#[derive(Default, Deserialize)]
struct ExtraMetricArgs {
time_unit: Option<TimeUnit>,
memory_unit: Option<MemoryUnit>,
allowed_extra_keys: Option<Vec<String>>,
range_min: Option<u64>,
range_max: Option<u64>,
bucket_count: Option<u64>,
histogram_type: Option<HistogramType>,
numerators: Option<Vec<CommonMetricData>>,
labels: Option<Vec<String>>,
}
/// Test-only method.
///
/// Registers a metric.
/// Doesn't check to see if it's been registered before.
/// Doesn't check that it would pass schema validation if it were a real metric.
///
/// `extra_args` is a JSON-encoded string in a form that serde can read into an ExtraMetricArgs.
///
/// No effort has been made to make this pleasant to use, since it's for
/// internal testing only (ie, the testing of JOG itself).
#[cfg(feature = "with_gecko")]
#[no_mangle]
pub extern "C" fn jog_test_register_metric() {
// What information do we need for registration?
// Pretty much everything in CommonMetricData except `disabled` which we presume is `false`, so
// name, category, send_in_pings, lifetime
pub extern "C" fn jog_test_register_metric(
metric_type: &nsACString,
category: &nsACString,
name: &nsACString,
send_in_pings: &ThinVec<nsCString>,
lifetime: &nsACString,
disabled: bool,
extra_args: &nsACString,
) -> u32 {
log::warn!("Type: {:?}, Category: {:?}, Name: {:?}, SendInPings: {:?}, Lifetime: {:?}, Disabled: {}, ExtraArgs: {}",
metric_type, category, name, send_in_pings, lifetime, disabled, extra_args);
let ns_category = category;
let ns_name = name;
let metric_type = &metric_type.to_utf8();
let category = category.to_string();
let name = name.to_string();
let send_in_pings = send_in_pings.iter().map(|ping| ping.to_string()).collect();
let lifetime = serde_json::from_str(&lifetime.to_utf8())
.expect("Lifetime didn't deserialize happily. Is it valid JSON?");
// Then we need everything else we might need (cf glean_parser.util.extra_metric_args:
// time_unit, memory_unit, allowed_extra_keys, reason_codes, range_min, range_max, bucket_count, histogram_type, numerators
let extra_args: ExtraMetricArgs = if extra_args.is_empty() {
Default::default()
} else {
serde_json::from_str(&extra_args.to_utf8())
.expect("Extras didn't deserialize happily. Are they valid JSON?")
};
let metric = factory::create_and_register_metric(
metric_type,
category,
name,
send_in_pings,
lifetime,
disabled,
extra_args.time_unit,
extra_args.memory_unit,
extra_args.allowed_extra_keys,
extra_args.range_min,
extra_args.range_max,
extra_args.bucket_count,
extra_args.histogram_type,
extra_args.numerators,
extra_args.labels,
);
extern "C" {
fn JOG_RegisterMetric(category: &nsACString, name: &nsACString, metric: u32);
}
let metric = metric.unwrap();
// Safety: We're loaning to C++ the same nsCStrings they leant us.
unsafe {
JOG_RegisterMetric(ns_category, ns_name, metric);
}
metric
}
/// Test-only method.

Просмотреть файл

@ -0,0 +1,653 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
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(
{ skip_if: () => true /* bug XXX */ },
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.testOnlyIpc.noExtraEvent.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({ extras: { extra1: "string", extra2: "string" } })
);
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({
extras: {
extra1: "string",
extra2: "quantity",
extra3_longer_name: "boolean",
},
})
);
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);
// 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);
events = Glean.jogCat.jogEventWithExtra.testGetValue();
Assert.equal(1, events.length, "Recorded one event too many.");
// 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.");
}
);
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(
/* TODO(bug 1737520 and XXX): Enable custom ping support in JOG and on Android */
{ skip_if: () => true /*|| AppConstants.platform == "android"*/ },
function test_jog_custom_pings() {
Services.fog.testRegisterRuntimeMetric(
"boolean",
"jog_cat",
"jog_ping_bool",
["jog-ping"],
`"ping"`,
false
);
//Services.fog.testRegisterRuntimePing("jog-ping");
Assert.ok("jogPing" in GleanPings);
let submitted = false;
Glean.jogCat.jogPingBool.set(false);
GleanPings.onePingOnly.testBeforeNextSubmit(reason => {
submitted = true;
Assert.equal(false, Glean.jogCat.jogPingBool.testGetValue());
});
GleanPings.onePingOnly.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.InvalidLabel.set(true);
Assert.equal(true, Glean.jogCat.jogLabeledBool.__other__.testGetValue());
// TODO: Test that we have the right number and type of errors (bug 1683171)
});
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({ 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.InvalidLabel.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({ 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.InvalidLabel.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.InvalidLabel.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({ 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.InvalidLabel.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());
});

Просмотреть файл

@ -0,0 +1,258 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
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!";
/* TODO:(bugXXX) Support events in JOG
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],
/* TODO:(bugXXX) Support events in JOG
["event", "jog_ipc", "jog_event_no_extra", ["test-only"], `"ping"`, false],
["event", "jog_ipc", "jog_event", ["test-only"], `"ping"`, false, JSON.stringify({extras: {extra1: "string"})}],
*/
[
"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({ labels: ["label_1", "label_2"] }),
],
[
"labeled_counter",
"jog_ipc",
"jog_labeled_counter_with_labels_err",
["test-only"],
`"ping"`,
false,
JSON.stringify({ labels: ["label_1", "label_2"] }),
],
["rate", "jog_ipc", "jog_rate", ["test-only"], `"ping"`, false],
];
add_task({ skip_if: () => runningInParent }, async function run_child_stuff() {
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);
/* TODO:(bugXXX) Support events in JOG
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.InvalidLabel.add(INVALID_COUNTERS);
Glean.jogIpc.jogLabeledCounterWithLabels.label_1.add(COUNTERS_1);
Glean.jogIpc.jogLabeledCounterWithLabels.label_2.add(COUNTERS_2);
Glean.jogIpc.jogLabeledCounterWithLabelsErr.InvalidLabel.add(
INVALID_COUNTERS
);
Glean.jogIpc.jogRate.addToNumerator(44);
Glean.jogIpc.jogRate.addToDenominator(14);
});
add_task(
{ skip_if: () => !runningInParent },
async function test_child_metrics() {
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}`
);
}
/* TODO(bug XXX): Support events in JOG
var 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()
);
}
);

Просмотреть файл

@ -18,3 +18,7 @@ skip-if = os == "android" # FOG isn't responsible for monitoring prefs and contr
[test_GleanExperiments.js]
skip-if = os == "android" # FOG isn't responsible for experiment annotations on Android
[test_JOG.js]
[test_JOGIPC.js]

Просмотреть файл

@ -10,6 +10,7 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/FOGIPC.h"
#include "mozilla/glean/bindings/Common.h"
#include "mozilla/glean/bindings/jog/jog_ffi_generated.h"
#include "mozilla/glean/fog_ffi_generated.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/MozPromise.h"
@ -336,6 +337,18 @@ FOG::TestTriggerMetrics(uint32_t aProcessType, JSContext* aCx,
return NS_OK;
}
NS_IMETHODIMP
FOG::TestRegisterRuntimeMetric(
const nsACString& aType, const nsACString& aCategory,
const nsACString& aName, const nsTArray<nsCString>& aPings,
const nsACString& aLifetime, const bool aDisabled,
const nsACString& aExtraArgs, uint32_t* aMetricIdOut) {
*aMetricIdOut = 0;
*aMetricIdOut = glean::jog::jog_test_register_metric(
&aType, &aCategory, &aName, &aPings, &aLifetime, aDisabled, &aExtraArgs);
return NS_OK;
}
NS_IMPL_ISUPPORTS(FOG, nsIFOG, nsIObserver)
} // namespace mozilla

Просмотреть файл

@ -133,4 +133,28 @@ interface nsIFOG : nsISupports
*/
[implicit_jscontext]
Promise testTriggerMetrics(in unsigned long aProcessType);
/**
* ** Test-only Method **
*
* Register a metric.
*
* This function is deliberately not too friendly to use. You probably aren't
* supposed to use it unless you're testing metric registration itself.
*
* @param aType - The metric's type.
* @param aCategory - The metric's category.
* @param aName - The metric's name.
* @param aPings - The pings to send it in.
* @param aLifetime - The metric's lifetime.
* @param aDisabled - Whether the metric, though existing, isn't enabled.
* @param aExtraArgs - Optional JSON string of extra args.
*/
uint32_t testRegisterRuntimeMetric(in ACString aType,
in ACString aCategory,
in ACString aName,
in Array<ACString> aPings,
in ACString aLifetime,
in boolean aDisabled,
[optional] in ACString aExtraArgs);
};

Просмотреть файл

@ -124,7 +124,7 @@ new_xulstore = ["xulstore"]
libfuzzer = ["neqo_glue/fuzzing"]
webrtc = ["mdns_service"]
glean_disable_upload = ["fog_control/disable_upload"]
glean_with_gecko = ["fog_control/with_gecko"]
glean_with_gecko = ["fog_control/with_gecko", "jog/with_gecko"]
oxidized_breakpad = ["rust_minidump_writer_linux"]
with_dbus = ["audio_thread_priority/with_dbus"]
thread_sanitizer = ["xpcom/thread_sanitizer"]