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/bindings/jog | |
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/bindings/jog')
-rw-r--r-- | toolkit/components/glean/bindings/jog/Cargo.toml | 20 | ||||
-rw-r--r-- | toolkit/components/glean/bindings/jog/JOG.cpp | 276 | ||||
-rw-r--r-- | toolkit/components/glean/bindings/jog/JOG.h | 122 | ||||
-rw-r--r-- | toolkit/components/glean/bindings/jog/cbindgen.toml | 26 | ||||
-rw-r--r-- | toolkit/components/glean/bindings/jog/src/lib.rs | 256 |
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 +} |