summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/bindings/jog
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/glean/bindings/jog
parentInitial commit. (diff)
downloadfirefox-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/bindings/jog')
-rw-r--r--toolkit/components/glean/bindings/jog/Cargo.toml20
-rw-r--r--toolkit/components/glean/bindings/jog/JOG.cpp276
-rw-r--r--toolkit/components/glean/bindings/jog/JOG.h122
-rw-r--r--toolkit/components/glean/bindings/jog/cbindgen.toml26
-rw-r--r--toolkit/components/glean/bindings/jog/src/lib.rs256
5 files changed, 700 insertions, 0 deletions
diff --git a/toolkit/components/glean/bindings/jog/Cargo.toml b/toolkit/components/glean/bindings/jog/Cargo.toml
new file mode 100644
index 0000000000..de2d8dce5a
--- /dev/null
+++ b/toolkit/components/glean/bindings/jog/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "jog"
+version = "0.1.0"
+authors = ["Glean SDK team <glean-team@mozilla.com>"]
+edition = "2021"
+publish = false
+license = "MPL-2.0"
+
+[dependencies]
+firefox-on-glean = { path = "../../api" }
+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" ]
diff --git a/toolkit/components/glean/bindings/jog/JOG.cpp b/toolkit/components/glean/bindings/jog/JOG.cpp
new file mode 100644
index 0000000000..56119cef0e
--- /dev/null
+++ b/toolkit/components/glean/bindings/jog/JOG.cpp
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/jog/JOG.h"
+
+#include <locale>
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/glean/bindings/jog/jog_ffi_generated.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_telemetry.h"
+#include "mozilla/AppShutdown.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsThreadUtils.h"
+#include "nsTHashMap.h"
+#include "nsTHashSet.h"
+
+namespace mozilla::glean {
+
+using mozilla::LogLevel;
+static mozilla::LazyLogModule sLog("jog");
+
+// Storage
+// Thread Safety: Only used on the main thread.
+StaticAutoPtr<nsTHashSet<nsCString>> gCategories;
+StaticAutoPtr<nsTHashMap<nsCString, uint32_t>> gMetrics;
+StaticAutoPtr<nsTHashMap<uint32_t, nsCString>> gMetricNames;
+StaticAutoPtr<nsTHashMap<nsCString, uint32_t>> gPings;
+
+// static
+bool JOG::HasCategory(const nsACString& aCategoryName) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return gCategories && gCategories->Contains(aCategoryName);
+}
+
+static Maybe<bool> sFoundAndLoadedJogfile;
+
+// static
+bool JOG::EnsureRuntimeMetricsRegistered(bool aForce) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sFoundAndLoadedJogfile) {
+ return sFoundAndLoadedJogfile.value();
+ }
+ sFoundAndLoadedJogfile = Some(false);
+
+ MOZ_LOG(sLog, LogLevel::Debug, ("Determining whether there's JOG for you."));
+
+ if (!mozilla::StaticPrefs::telemetry_fog_artifact_build()) {
+ // Supporting Artifact Builds is a developer-only thing.
+ // We're on the main thread here.
+ // Let's not spend any more time than we need to.
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("!telemetry.fog.artifact_build. No JOG for you."));
+ return false;
+ }
+ // The metrics we need to process were placed in GreD in jogfile.json
+ // That file was generated by
+ // toolkit/components/glean/build_scripts/glean_parser_ext/jog.py
+ nsCOMPtr<nsIFile> jogfile;
+ if (NS_WARN_IF(NS_FAILED(
+ NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(jogfile))))) {
+ return false;
+ }
+ if (NS_WARN_IF(NS_FAILED(jogfile->Append(u"jogfile.json"_ns)))) {
+ return false;
+ }
+ bool jogfileExists = false;
+ if (NS_WARN_IF(NS_FAILED(jogfile->Exists(&jogfileExists))) ||
+ !jogfileExists) {
+ return false;
+ }
+
+ // We _could_ register everything here in C++ land,
+ // but let's use Rust because (among other reasons) it's more fun.
+ nsAutoString jogfileString;
+ if (NS_WARN_IF(NS_FAILED(jogfile->GetPath(jogfileString)))) {
+ return false;
+ }
+ sFoundAndLoadedJogfile = Some(jog::jog_load_jogfile(&jogfileString));
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("%s", sFoundAndLoadedJogfile.value()
+ ? "Found and loaded jogfile. Yes! JOG for you!"
+ : "Couldn't find and load jogfile. No JOG for you."));
+ return sFoundAndLoadedJogfile.value();
+}
+
+// static
+bool JOG::AreRuntimeMetricsComprehensive() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return sFoundAndLoadedJogfile && sFoundAndLoadedJogfile.value();
+}
+
+// static
+void JOG::GetCategoryNames(nsTArray<nsString>& aNames) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gCategories) {
+ return;
+ }
+ for (const auto& category : *gCategories) {
+ aNames.EmplaceBack(NS_ConvertUTF8toUTF16(category));
+ }
+}
+
+// static
+Maybe<uint32_t> JOG::GetMetric(const nsACString& aMetricName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return !gMetrics ? Nothing() : gMetrics->MaybeGet(aMetricName);
+}
+
+// static
+Maybe<nsCString> JOG::GetMetricName(uint32_t aMetricId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return !gMetricNames ? Nothing() : gMetricNames->MaybeGet(aMetricId);
+}
+
+// static
+void JOG::GetMetricNames(const nsACString& aCategoryName,
+ nsTArray<nsString>& aNames) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gMetricNames) {
+ return;
+ }
+ for (const auto& identifier : gMetricNames->Values()) {
+ if (StringBeginsWith(identifier, aCategoryName) &&
+ identifier.CharAt(aCategoryName.Length()) == '.') {
+ const char* metricName = &identifier.Data()[aCategoryName.Length() + 1];
+ aNames.AppendElement()->AssignASCII(metricName);
+ }
+ }
+}
+
+// static
+Maybe<uint32_t> JOG::GetPing(const nsACString& aPingName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return !gPings ? Nothing() : gPings->MaybeGet(aPingName);
+}
+
+// static
+void JOG::GetPingNames(nsTArray<nsString>& aNames) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gPings) {
+ return;
+ }
+ for (const auto& ping : gPings->Keys()) {
+ aNames.EmplaceBack(NS_ConvertUTF8toUTF16(ping));
+ }
+}
+
+} // namespace mozilla::glean
+
+// static
+nsCString dottedSnakeToCamel(const nsACString& aSnake) {
+ nsCString camel;
+ bool first = true;
+ for (const nsACString& segment : aSnake.Split('_')) {
+ for (const nsACString& part : segment.Split('.')) {
+ if (first) {
+ first = false;
+ camel.Append(part);
+ } else if (part.Length()) {
+ char lower = part.CharAt(0);
+ if ('a' <= lower && lower <= 'z') {
+ camel.Append(
+ std::toupper(lower, std::locale())); // append the Capital.
+ camel.Append(part.BeginReading() + 1,
+ part.Length() - 1); // append the rest.
+ } else {
+ // Not gonna try to capitalize anything outside a->z.
+ camel.Append(part);
+ }
+ }
+ }
+ }
+ return camel;
+}
+
+// static
+nsCString kebabToCamel(const nsACString& aKebab) {
+ nsCString camel;
+ bool first = true;
+ for (const nsACString& segment : aKebab.Split('-')) {
+ if (first) {
+ first = false;
+ camel.Append(segment);
+ } else if (segment.Length()) {
+ char lower = segment.CharAt(0);
+ if ('a' <= lower && lower <= 'z') {
+ camel.Append(
+ std::toupper(lower, std::locale())); // append the Capital.
+ camel.Append(segment.BeginReading() + 1,
+ segment.Length() - 1); // append the rest.
+ } else {
+ // Not gonna try to capitalize anything outside a->z.
+ camel.Append(segment);
+ }
+ }
+ }
+ return camel;
+}
+
+using mozilla::AppShutdown;
+using mozilla::ShutdownPhase;
+using mozilla::glean::gCategories;
+using mozilla::glean::gMetricNames;
+using mozilla::glean::gMetrics;
+using mozilla::glean::gPings;
+
+extern "C" NS_EXPORT void JOG_RegisterMetric(
+ const nsACString& aCategory, const nsACString& aName,
+ uint32_t aMetric, // includes type.
+ uint32_t aMetricId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ return;
+ }
+
+ MOZ_LOG(mozilla::glean::sLog, mozilla::LogLevel::Verbose,
+ ("Registering metric %s.%s id %" PRIu32 " id+type %" PRIu32 "",
+ PromiseFlatCString(aCategory).get(), PromiseFlatCString(aName).get(),
+ aMetricId, aMetric));
+
+ // aCategory is dotted.snake_case. aName is snake_case.
+ auto categoryCamel = dottedSnakeToCamel(aCategory);
+ auto nameCamel = dottedSnakeToCamel(aName);
+
+ // Register the category
+ if (!gCategories) {
+ gCategories = new nsTHashSet<nsCString>();
+ RunOnShutdown([&] { gCategories = nullptr; },
+ ShutdownPhase::XPCOMWillShutdown);
+ }
+ gCategories->Insert(categoryCamel);
+
+ // Register the metric
+ if (!gMetrics) {
+ gMetrics = new nsTHashMap<nsCString, uint32_t>();
+ RunOnShutdown([&] { gMetrics = nullptr; },
+ ShutdownPhase::XPCOMWillShutdown);
+ }
+ gMetrics->InsertOrUpdate(categoryCamel + "."_ns + nameCamel, aMetric);
+
+ // Register the metric name (for GIFFT)
+ if (!gMetricNames) {
+ gMetricNames = new nsTHashMap<uint32_t, nsCString>();
+ RunOnShutdown([&] { gMetricNames = nullptr; },
+ ShutdownPhase::XPCOMWillShutdown);
+ }
+ gMetricNames->InsertOrUpdate(aMetricId, categoryCamel + "."_ns + nameCamel);
+}
+
+extern "C" NS_EXPORT void JOG_RegisterPing(const nsACString& aPingName,
+ uint32_t aPingId) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ return;
+ }
+
+ // aPingName is kebab-case. JS expects camelCase.
+ auto pingCamel = kebabToCamel(aPingName);
+
+ // Register the ping
+ if (!gPings) {
+ gPings = new nsTHashMap<nsCString, uint32_t>();
+ RunOnShutdown([&] { gPings = nullptr; }, ShutdownPhase::XPCOMWillShutdown);
+ }
+ gPings->InsertOrUpdate(pingCamel, aPingId);
+}
diff --git a/toolkit/components/glean/bindings/jog/JOG.h b/toolkit/components/glean/bindings/jog/JOG.h
new file mode 100644
index 0000000000..ce51249d00
--- /dev/null
+++ b/toolkit/components/glean/bindings/jog/JOG.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_JOG_h
+#define mozilla_glean_JOG_h
+
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "mozilla/Maybe.h"
+
+namespace mozilla::glean {
+
+class JOG {
+ public:
+ /**
+ * Returns whether JOG knows about a category by this name
+ *
+ * @param aCategoryName The category name to check.
+ *
+ * @returns true if JOG is aware of a category by the given name at this time
+ */
+ static bool HasCategory(const nsACString& aCategoryName);
+
+ /**
+ * Runs the runtime registrar.
+ *
+ * Locates the runtime metrics file and, if present, loads and processes it.
+ *
+ * Only does any work at all if !mozilla::IsPackagedBuild()
+ *
+ * **Note:** When this function does something, it is expensive, running
+ * synchronous file I/O to ensure that the registration is complete when this
+ * call returns.
+ *
+ * @param aForce Set to `true` if you want to force the I/O to run. Defaults
+ * to `false`, which doesn't run the I/O if it's already run and
+ * returns the previous return value.
+ * @returns whether it found the runtime metrics file and succesfully loaded,
+ * processed, and registered the described metrics.
+ */
+ static bool EnsureRuntimeMetricsRegistered(bool aForce = false);
+
+ /**
+ * Returns whether, if a metric is absent in the runtime-registered metrics,
+ * you should check the compile-time-registered metrics.
+ *
+ * Runtime-registered metrics can either replace all compile-time-registered
+ * metrics (like in artefact builds) or just be supplementing compile-time-
+ * registered metrics (like addons/dynamic telemetry/etc).
+ *
+ * This is tied to the current state of runtime metric registration. So it
+ * may return false at one time and true later (e.g. if RuntimeRegistrar is
+ * run in between).
+ *
+ * @return true if you should treat the runtime-registered metrics as
+ * authoritative and comprehensive.
+ */
+ static bool AreRuntimeMetricsComprehensive();
+
+ /**
+ * Adds the runtime-registered metrics' categories to `aNames`.
+ *
+ * @param aNames The list to add the categories' names to.
+ */
+ static void GetCategoryNames(nsTArray<nsString>& aNames);
+
+ /**
+ * Get the metric id+type in a u32 for a named runtime-registered metric.
+ *
+ * Return value's only useful to GleanJSMetricsLookup.h
+ *
+ * @param aMetricName The `myCategory.myName` dotted.camelCase metric name.
+ * @return Nothing() if no metric by that name was registered at runtime.
+ * Otherwise, the encoded u32 with metric id and metric type id for
+ * the runtime-registered metric.
+ */
+ static Maybe<uint32_t> GetMetric(const nsACString& aMetricName);
+
+ /**
+ * Get the metric name for an identified runtime-registered metric.
+ *
+ * @param aMetricId The id of the runtime-registered metric.
+ * @return Nothing() if no metric by that id has been registered.
+ * Otherwise, the `myCategory.myName` dotted.camelCase metric name of
+ * the runtime-registered metric.
+ */
+ static Maybe<nsCString> GetMetricName(uint32_t aMetricId);
+
+ /**
+ * Adds `aCategoryName`'s runtime-registered metrics' names to `aNames`.
+ *
+ * @param aCategoryName The name of the category we want the metric names for.
+ * @param aNames The list to add the metrics' names to.
+ */
+ static void GetMetricNames(const nsACString& aCategoryName,
+ nsTArray<nsString>& aNames);
+
+ /**
+ * Get the ping id in a u32 for a named runtime-registered ping.
+ *
+ * Return value's only useful to GleanJSPingsLookup.h
+ *
+ * @param aPingName The ping name.
+ * @return Nothing() if no ping by that name was registered at runtime.
+ * Otherwise, the id for the runtime-registered ping.
+ */
+ static Maybe<uint32_t> GetPing(const nsACString& aPingName);
+
+ /**
+ * Adds the runtime-registered pings' names to `aNames`.
+ *
+ * @param aNames The list to add the pings' names to.
+ */
+ static void GetPingNames(nsTArray<nsString>& aNames);
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_JOG_h */
diff --git a/toolkit/components/glean/bindings/jog/cbindgen.toml b/toolkit/components/glean/bindings/jog/cbindgen.toml
new file mode 100644
index 0000000000..62139cc6c6
--- /dev/null
+++ b/toolkit/components/glean/bindings/jog/cbindgen.toml
@@ -0,0 +1,26 @@
+header = """/* 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_jog_ffi_generated_h
+#define mozilla_glean_jog_ffi_generated_h
+"""
+trailer = """
+#endif // mozilla_glean_jog_ffi_generated_h
+"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla::glean::jog"]
+includes = ["nsTArray.h", "nsString.h"]
+
+[export.rename]
+"ThinVec" = "nsTArray"
+#"nsCStringRepr" = "nsCString"
+
+[parse]
+#parse_deps = true
+#include = ["fog"]
+#extra_bindings = ["fog"]
diff --git a/toolkit/components/glean/bindings/jog/src/lib.rs b/toolkit/components/glean/bindings/jog/src/lib.rs
new file mode 100644
index 0000000000..9515f1306d
--- /dev/null
+++ b/toolkit/components/glean/bindings/jog/src/lib.rs
@@ -0,0 +1,256 @@
+/* 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 firefox_on_glean::factory;
+use firefox_on_glean::private::traits::HistogramType;
+use firefox_on_glean::private::{CommonMetricData, Lifetime, MemoryUnit, TimeUnit};
+#[cfg(feature = "with_gecko")]
+use nsstring::{nsACString, nsAString, nsCString};
+use serde::Deserialize;
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+use std::fs::File;
+use std::io::BufReader;
+use thin_vec::ThinVec;
+
+#[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>>,
+ ordered_labels: Option<Vec<Cow<'static, str>>>,
+}
+
+/// 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(
+ 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 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?");
+
+ 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?")
+ };
+ create_and_register_metric(
+ metric_type,
+ category,
+ name,
+ send_in_pings,
+ lifetime,
+ disabled,
+ extra_args,
+ )
+ .expect("Creation/Registration of metric failed") // ok to panic in test-only method
+ .0
+}
+
+fn create_and_register_metric(
+ metric_type: &str,
+ category: String,
+ name: String,
+ send_in_pings: Vec<String>,
+ lifetime: Lifetime,
+ disabled: bool,
+ extra_args: ExtraMetricArgs,
+) -> Result<(u32, u32), Box<dyn std::error::Error>> {
+ let ns_name = nsCString::from(&name);
+ let ns_category = nsCString::from(&category);
+ 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.or_else(|| Some(Vec::new())),
+ extra_args.range_min,
+ extra_args.range_max,
+ extra_args.bucket_count,
+ extra_args.histogram_type,
+ extra_args.numerators,
+ extra_args.ordered_labels,
+ );
+ extern "C" {
+ fn JOG_RegisterMetric(
+ category: &nsACString,
+ name: &nsACString,
+ metric: u32,
+ metric_id: u32,
+ );
+ }
+ if let Ok((metric, metric_id)) = metric {
+ unsafe {
+ // Safety: We're loaning to C++ data we don't later use.
+ JOG_RegisterMetric(&ns_category, &ns_name, metric, metric_id);
+ }
+ } else {
+ log::warn!(
+ "Could not register metric {}.{} due to {:?}",
+ ns_category,
+ ns_name,
+ metric
+ );
+ }
+ metric
+}
+
+/// Test-only method.
+///
+/// Registers a ping. 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 ping.
+#[no_mangle]
+pub extern "C" fn jog_test_register_ping(
+ name: &nsACString,
+ include_client_id: bool,
+ send_if_empty: bool,
+ reason_codes: &ThinVec<nsCString>,
+) -> u32 {
+ let ping_name = name.to_string();
+ let reason_codes = reason_codes
+ .iter()
+ .map(|reason| reason.to_string())
+ .collect();
+ create_and_register_ping(ping_name, include_client_id, send_if_empty, reason_codes)
+ .expect("Creation or registration of ping failed.") // permitted to panic in test-only method.
+}
+
+fn create_and_register_ping(
+ ping_name: String,
+ include_client_id: bool,
+ send_if_empty: bool,
+ reason_codes: Vec<String>,
+) -> Result<u32, Box<dyn std::error::Error>> {
+ let ns_name = nsCString::from(&ping_name);
+ let ping_id = factory::create_and_register_ping(
+ ping_name,
+ include_client_id,
+ send_if_empty,
+ reason_codes,
+ );
+ extern "C" {
+ fn JOG_RegisterPing(name: &nsACString, ping_id: u32);
+ }
+ if let Ok(ping_id) = ping_id {
+ unsafe {
+ // Safety: We're loaning to C++ data we don't later use.
+ JOG_RegisterPing(&ns_name, ping_id);
+ }
+ } else {
+ log::warn!("Could not register ping {} due to {:?}", ns_name, ping_id);
+ }
+ ping_id
+}
+
+/// Test-only method.
+///
+/// Clears all runtime registration storage of registered metrics and pings.
+#[no_mangle]
+pub extern "C" fn jog_test_clear_registered_metrics_and_pings() {}
+
+#[derive(Default, Deserialize)]
+struct Jogfile {
+ // Using BTreeMap to ensure stable iteration ordering.
+ metrics: BTreeMap<String, Vec<MetricDefinitionData>>,
+ pings: Vec<PingDefinitionData>,
+}
+
+#[derive(Default, Deserialize)]
+struct MetricDefinitionData {
+ metric_type: String,
+ name: String,
+ send_in_pings: Vec<String>,
+ lifetime: Lifetime,
+ disabled: bool,
+ #[serde(default)]
+ extra_args: Option<ExtraMetricArgs>,
+}
+
+#[derive(Default, Deserialize)]
+struct PingDefinitionData {
+ name: String,
+ include_client_id: bool,
+ send_if_empty: bool,
+ reason_codes: Option<Vec<String>>,
+}
+
+/// Read the file at the provided location, interpret it as a jogfile,
+/// and register those pings and metrics.
+/// Returns true if we successfully parsed the jogfile. Does not mean
+/// all or any metrics and pings successfully registered,
+/// just that serde managed to deserialize it into metrics and pings and we tried to register them all.
+#[no_mangle]
+pub extern "C" fn jog_load_jogfile(jogfile_path: &nsAString) -> bool {
+ let f = match File::open(jogfile_path.to_string()) {
+ Ok(f) => f,
+ _ => {
+ log::error!("Boo, couldn't open jogfile at {}", jogfile_path.to_string());
+ return false;
+ }
+ };
+ let reader = BufReader::new(f);
+
+ let j: Jogfile = match serde_json::from_reader(reader) {
+ Ok(j) => j,
+ Err(e) => {
+ log::error!("Boo, couldn't read jogfile because of: {:?}", e);
+ return false;
+ }
+ };
+ log::trace!("Loaded jogfile. Registering metrics+pings.");
+ for (category, metrics) in j.metrics.into_iter() {
+ for metric in metrics.into_iter() {
+ let _ = create_and_register_metric(
+ &metric.metric_type,
+ category.to_string(),
+ metric.name,
+ metric.send_in_pings,
+ metric.lifetime,
+ metric.disabled,
+ metric.extra_args.unwrap_or_else(Default::default),
+ );
+ }
+ }
+ for ping in j.pings.into_iter() {
+ let _ = create_and_register_ping(
+ ping.name,
+ ping.include_client_id,
+ ping.send_if_empty,
+ ping.reason_codes.unwrap_or_else(Vec::new),
+ );
+ }
+ true
+}