summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/bindings
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean/bindings')
-rw-r--r--toolkit/components/glean/bindings/Category.cpp75
-rw-r--r--toolkit/components/glean/bindings/Category.h45
-rw-r--r--toolkit/components/glean/bindings/Glean.cpp111
-rw-r--r--toolkit/components/glean/bindings/Glean.h57
-rw-r--r--toolkit/components/glean/bindings/GleanMetric.cpp70
-rw-r--r--toolkit/components/glean/bindings/GleanMetric.h47
-rw-r--r--toolkit/components/glean/bindings/GleanPings.cpp92
-rw-r--r--toolkit/components/glean/bindings/GleanPings.h39
-rw-r--r--toolkit/components/glean/bindings/MetricTypes.h27
-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.rs267
-rw-r--r--toolkit/components/glean/bindings/private/Boolean.cpp71
-rw-r--r--toolkit/components/glean/bindings/private/Boolean.h80
-rw-r--r--toolkit/components/glean/bindings/private/Common.cpp33
-rw-r--r--toolkit/components/glean/bindings/private/Common.h24
-rw-r--r--toolkit/components/glean/bindings/private/Counter.cpp74
-rw-r--r--toolkit/components/glean/bindings/private/Counter.h77
-rw-r--r--toolkit/components/glean/bindings/private/CustomDistribution.cpp105
-rw-r--r--toolkit/components/glean/bindings/private/CustomDistribution.h95
-rw-r--r--toolkit/components/glean/bindings/private/Datetime.cpp121
-rw-r--r--toolkit/components/glean/bindings/private/Datetime.h79
-rw-r--r--toolkit/components/glean/bindings/private/Denominator.cpp64
-rw-r--r--toolkit/components/glean/bindings/private/Denominator.h77
-rw-r--r--toolkit/components/glean/bindings/private/DistributionData.h32
-rw-r--r--toolkit/components/glean/bindings/private/Event.cpp110
-rw-r--r--toolkit/components/glean/bindings/private/Event.h177
-rw-r--r--toolkit/components/glean/bindings/private/Labeled.cpp49
-rw-r--r--toolkit/components/glean/bindings/private/Labeled.h248
-rw-r--r--toolkit/components/glean/bindings/private/MemoryDistribution.cpp89
-rw-r--r--toolkit/components/glean/bindings/private/MemoryDistribution.h86
-rw-r--r--toolkit/components/glean/bindings/private/Numerator.cpp73
-rw-r--r--toolkit/components/glean/bindings/private/Numerator.h84
-rw-r--r--toolkit/components/glean/bindings/private/Ping.cpp85
-rw-r--r--toolkit/components/glean/bindings/private/Ping.h83
-rw-r--r--toolkit/components/glean/bindings/private/Quantity.cpp68
-rw-r--r--toolkit/components/glean/bindings/private/Quantity.h77
-rw-r--r--toolkit/components/glean/bindings/private/Rate.cpp86
-rw-r--r--toolkit/components/glean/bindings/private/Rate.h90
-rw-r--r--toolkit/components/glean/bindings/private/String.cpp75
-rw-r--r--toolkit/components/glean/bindings/private/String.h80
-rw-r--r--toolkit/components/glean/bindings/private/StringList.cpp80
-rw-r--r--toolkit/components/glean/bindings/private/StringList.h93
-rw-r--r--toolkit/components/glean/bindings/private/Text.cpp64
-rw-r--r--toolkit/components/glean/bindings/private/Text.h54
-rw-r--r--toolkit/components/glean/bindings/private/Timespan.cpp186
-rw-r--r--toolkit/components/glean/bindings/private/Timespan.h110
-rw-r--r--toolkit/components/glean/bindings/private/TimingDistribution.cpp242
-rw-r--r--toolkit/components/glean/bindings/private/TimingDistribution.h125
-rw-r--r--toolkit/components/glean/bindings/private/Url.cpp68
-rw-r--r--toolkit/components/glean/bindings/private/Url.h77
-rw-r--r--toolkit/components/glean/bindings/private/Uuid.cpp76
-rw-r--r--toolkit/components/glean/bindings/private/Uuid.h83
55 files changed, 5024 insertions, 0 deletions
diff --git a/toolkit/components/glean/bindings/Category.cpp b/toolkit/components/glean/bindings/Category.cpp
new file mode 100644
index 0000000000..1931b244a1
--- /dev/null
+++ b/toolkit/components/glean/bindings/Category.cpp
@@ -0,0 +1,75 @@
+/* -*- 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/dom/GleanBinding.h"
+#include "mozilla/glean/bindings/Glean.h"
+#include "mozilla/glean/bindings/Category.h"
+#include "mozilla/glean/bindings/GleanJSMetricsLookup.h"
+#include "mozilla/glean/bindings/jog/JOG.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Category, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Category)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Category)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Category)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* Category::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanCategory_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<GleanMetric> Category::NamedGetter(const nsAString& aName,
+ bool& aFound) {
+ aFound = false;
+
+ nsCString metricName;
+ metricName.AppendASCII(mName);
+ metricName.AppendLiteral(".");
+ AppendUTF16toUTF8(aName, metricName);
+
+ Maybe<uint32_t> metricIdx = JOG::GetMetric(metricName);
+ if (metricIdx.isNothing() && !JOG::AreRuntimeMetricsComprehensive()) {
+ metricIdx = MetricByNameLookup(metricName);
+ }
+
+ if (metricIdx.isNothing()) {
+ aFound = false;
+ return nullptr;
+ }
+
+ aFound = true; // Should always be true (MOZ_ASSERT_UNREACHABLE-guarded).
+ return NewMetricFromId(metricIdx.value(), mParent);
+}
+
+bool Category::NameIsEnumerable(const nsAString& aName) { return false; }
+
+void Category::GetSupportedNames(nsTArray<nsString>& aNames) {
+ JOG::GetMetricNames(mName, aNames);
+ if (!JOG::AreRuntimeMetricsComprehensive()) {
+ for (metric_entry_t entry : sMetricByNameLookupEntries) {
+ const char* identifierBuf = GetMetricIdentifier(entry);
+ nsDependentCString identifier(identifierBuf);
+
+ // We're iterating all metrics,
+ // so we need to check for the ones in the right category.
+ //
+ // We need to ensure that we found _only_ the exact category by checking
+ // it is followed by a dot.
+ if (StringBeginsWith(identifier, mName) &&
+ identifier.CharAt(mName.Length()) == '.') {
+ const char* metricName = &identifierBuf[mName.Length() + 1];
+ aNames.AppendElement()->AssignASCII(metricName);
+ }
+ }
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/Category.h b/toolkit/components/glean/bindings/Category.h
new file mode 100644
index 0000000000..f173912f32
--- /dev/null
+++ b/toolkit/components/glean/bindings/Category.h
@@ -0,0 +1,45 @@
+/* -*- 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_Category_h
+#define mozilla_glean_Category_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "nsISupports.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::glean {
+
+class Category final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Category)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() { return mParent; };
+
+ explicit Category(nsCString&& aName, nsISupports* aParent)
+ : mName(aName), mParent(aParent) {}
+
+ already_AddRefed<GleanMetric> NamedGetter(const nsAString& aName,
+ bool& aFound);
+ bool NameIsEnumerable(const nsAString& aName);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ private:
+ nsCString mName;
+ nsCOMPtr<nsISupports> mParent;
+
+ protected:
+ virtual ~Category() = default;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Category_h */
diff --git a/toolkit/components/glean/bindings/Glean.cpp b/toolkit/components/glean/bindings/Glean.cpp
new file mode 100644
index 0000000000..4f77f3a775
--- /dev/null
+++ b/toolkit/components/glean/bindings/Glean.cpp
@@ -0,0 +1,111 @@
+/* -*- 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/dom/DOMJSClass.h"
+#include "mozilla/dom/GleanBinding.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/glean/bindings/Glean.h"
+#include "mozilla/glean/bindings/Category.h"
+#include "mozilla/glean/bindings/GleanJSMetricsLookup.h"
+#include "mozilla/glean/bindings/jog/jog_ffi_generated.h"
+#include "mozilla/glean/bindings/jog/JOG.h"
+#include "MainThreadUtils.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+
+namespace mozilla::glean {
+
+// Whether the runtime-registered metrics should be treated as comprehensive,
+// or additive. If comprehensive, a metric not registered at runtime is a
+// metric that doesn't exist. If additive, a metric not registered at runtime
+// may still exist if it was registered at compile time.
+// If we're supporting Artefact Builds, we treat them as comprehensive.
+// Threading: Must only be read or written to on the main thread.
+static bool gRuntimeMetricsComprehensive = false;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Glean, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Glean)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Glean)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Glean)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* Glean::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanImpl_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+bool Glean::DefineGlean(JSContext* aCx, JS::Handle<JSObject*> aGlobal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(JS::GetClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
+ "Passed object is not a global object!");
+
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> glean(aCx);
+ js::AssertSameCompartment(aCx, aGlobal);
+
+ auto impl = MakeRefPtr<Glean>(global);
+ if (!dom::GetOrCreateDOMReflector(aCx, impl.get(), &glean)) {
+ return false;
+ }
+
+ return JS_DefineProperty(aCx, aGlobal, "Glean", glean, JSPROP_ENUMERATE);
+}
+
+already_AddRefed<Category> Glean::NamedGetter(const nsAString& aName,
+ bool& aFound) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JOG::EnsureRuntimeMetricsRegistered();
+
+ NS_ConvertUTF16toUTF8 categoryName(aName);
+ if (JOG::HasCategory(categoryName)) {
+ aFound = true;
+ return MakeAndAddRef<Category>(std::move(categoryName), mParent);
+ }
+
+ if (gRuntimeMetricsComprehensive) {
+ // This category might be built-in, but since the runtime metrics are
+ // comprehensive, that just signals that the category was removed locally.
+ aFound = false;
+ return nullptr;
+ }
+
+ Maybe<uint32_t> categoryIdx = CategoryByNameLookup(categoryName);
+ if (categoryIdx.isNothing()) {
+ aFound = false;
+ return nullptr;
+ }
+
+ aFound = true;
+ nsDependentCString name(GetCategoryName(categoryIdx.value()));
+ return MakeAndAddRef<Category>(std::move(name), mParent);
+}
+
+bool Glean::NameIsEnumerable(const nsAString& aName) { return false; }
+
+void Glean::GetSupportedNames(nsTArray<nsString>& aNames) {
+ JOG::GetCategoryNames(aNames);
+ if (!JOG::AreRuntimeMetricsComprehensive()) {
+ for (category_entry_t entry : sCategoryByNameLookupEntries) {
+ const char* categoryName = GetCategoryName(entry);
+ aNames.AppendElement()->AssignASCII(categoryName);
+ }
+ }
+}
+
+// static
+void Glean::TestSetRuntimeMetricsComprehensive(bool aIsComprehensive) {
+ MOZ_ASSERT(NS_IsMainThread());
+ gRuntimeMetricsComprehensive = aIsComprehensive;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/Glean.h b/toolkit/components/glean/bindings/Glean.h
new file mode 100644
index 0000000000..d9ffc97918
--- /dev/null
+++ b/toolkit/components/glean/bindings/Glean.h
@@ -0,0 +1,57 @@
+/* -*- 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_Glean_h
+#define mozilla_glean_Glean_h
+
+#include "js/TypeDecls.h"
+#include "nsGlobalWindowInner.h"
+#include "nsISupports.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::glean {
+
+class Category;
+
+class Glean final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Glean)
+
+ explicit Glean(nsIGlobalObject* aGlobal) : mParent(aGlobal) {}
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() { return mParent; }
+
+ static bool DefineGlean(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+ already_AddRefed<Category> NamedGetter(const nsAString& aName, bool& aFound);
+ bool NameIsEnumerable(const nsAString& aName);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ /*
+ * Test-only method.
+ *
+ * Set whether we should treat runtime-registered metrics as the
+ * comprehensive list of all metrics, or whether compile-time-registered
+ * metrics are allowed to count too.
+ *
+ * Allows us to test Artifact Build support flexibly.
+ */
+ static void TestSetRuntimeMetricsComprehensive(bool aIsComprehensive);
+
+ private:
+ nsCOMPtr<nsISupports> mParent;
+
+ protected:
+ virtual ~Glean() = default;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Glean */
diff --git a/toolkit/components/glean/bindings/GleanMetric.cpp b/toolkit/components/glean/bindings/GleanMetric.cpp
new file mode 100644
index 0000000000..cb15432913
--- /dev/null
+++ b/toolkit/components/glean/bindings/GleanMetric.cpp
@@ -0,0 +1,70 @@
+/* -*- 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/GleanMetric.h"
+
+#include "nsThreadUtils.h"
+#include "nsWrapperCache.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GleanMetric, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GleanMetric)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GleanMetric)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GleanMetric)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+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 mozilla::glean
diff --git a/toolkit/components/glean/bindings/GleanMetric.h b/toolkit/components/glean/bindings/GleanMetric.h
new file mode 100644
index 0000000000..65ac75191d
--- /dev/null
+++ b/toolkit/components/glean/bindings/GleanMetric.h
@@ -0,0 +1,47 @@
+/* -*- 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_GleanMetric_h
+#define mozilla_glean_GleanMetric_h
+
+#include "js/TypeDecls.h"
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+#include "nsClassHashtable.h"
+#include "nsTHashMap.h"
+#include "mozilla/DataMutex.h"
+
+namespace mozilla::Telemetry {
+enum class ScalarID : uint32_t;
+} // namespace mozilla::Telemetry
+
+namespace mozilla::glean {
+
+class GleanMetric : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(GleanMetric);
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ protected:
+ GleanMetric(nsISupports* aParent) : mParent(aParent) {}
+ virtual ~GleanMetric() = default;
+ nsCOMPtr<nsISupports> mParent;
+};
+
+typedef nsUint32HashKey SubmetricIdHashKey;
+typedef nsTHashMap<SubmetricIdHashKey,
+ std::tuple<Telemetry::ScalarID, nsString>>
+ SubmetricToLabeledMirrorMapType;
+typedef StaticDataMutex<UniquePtr<SubmetricToLabeledMirrorMapType>>
+ SubmetricToMirrorMutex;
+
+Maybe<SubmetricToMirrorMutex::AutoLock> GetLabeledMirrorLock();
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanMetric_h */
diff --git a/toolkit/components/glean/bindings/GleanPings.cpp b/toolkit/components/glean/bindings/GleanPings.cpp
new file mode 100644
index 0000000000..eab84192ad
--- /dev/null
+++ b/toolkit/components/glean/bindings/GleanPings.cpp
@@ -0,0 +1,92 @@
+/* -*- 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/GleanPings.h"
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/GleanPingsBinding.h"
+#include "mozilla/glean/bindings/GleanJSPingsLookup.h"
+#include "mozilla/glean/bindings/jog/JOG.h"
+#include "mozilla/glean/bindings/Ping.h"
+#include "MainThreadUtils.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+
+namespace mozilla::glean {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(GleanPings)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GleanPings)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GleanPings)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GleanPings)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* GleanPings::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanPingsImpl_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+bool GleanPings::DefineGleanPings(JSContext* aCx,
+ JS::Handle<JSObject*> aGlobal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(JS::GetClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
+ "Passed object is not a global object!");
+
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> gleanPings(aCx);
+ js::AssertSameCompartment(aCx, aGlobal);
+
+ auto impl = MakeRefPtr<GleanPings>();
+ if (!dom::GetOrCreateDOMReflector(aCx, impl.get(), &gleanPings)) {
+ return false;
+ }
+
+ return JS_DefineProperty(aCx, aGlobal, "GleanPings", gleanPings,
+ JSPROP_ENUMERATE);
+}
+
+already_AddRefed<GleanPing> GleanPings::NamedGetter(const nsAString& aName,
+ bool& aFound) {
+ aFound = false;
+
+ NS_ConvertUTF16toUTF8 pingName(aName);
+
+ JOG::EnsureRuntimeMetricsRegistered();
+
+ Maybe<uint32_t> pingId = JOG::GetPing(pingName);
+ if (pingId.isNothing() && !JOG::AreRuntimeMetricsComprehensive()) {
+ pingId = PingByNameLookup(pingName);
+ }
+
+ if (pingId.isNothing()) {
+ aFound = false;
+ return nullptr;
+ }
+
+ aFound = true;
+ return MakeAndAddRef<GleanPing>(pingId.value());
+}
+
+bool GleanPings::NameIsEnumerable(const nsAString& aName) { return false; }
+
+void GleanPings::GetSupportedNames(nsTArray<nsString>& aNames) {
+ JOG::GetPingNames(aNames);
+ if (!JOG::AreRuntimeMetricsComprehensive()) {
+ for (uint8_t idx : sPingByNameLookupEntries) {
+ const char* pingName = GetPingName(idx);
+ aNames.AppendElement()->AssignASCII(pingName);
+ }
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/GleanPings.h b/toolkit/components/glean/bindings/GleanPings.h
new file mode 100644
index 0000000000..b4a88bfc64
--- /dev/null
+++ b/toolkit/components/glean/bindings/GleanPings.h
@@ -0,0 +1,39 @@
+/* -*- 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_GleanPings_h
+#define mozilla_glean_GleanPings_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/Ping.h"
+#include "nsGlobalWindowInner.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::glean {
+
+class GleanPings final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(GleanPings)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() { return nullptr; }
+
+ static bool DefineGleanPings(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+ already_AddRefed<GleanPing> NamedGetter(const nsAString& aName, bool& aFound);
+ bool NameIsEnumerable(const nsAString& aName);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ protected:
+ virtual ~GleanPings() = default;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanPings */
diff --git a/toolkit/components/glean/bindings/MetricTypes.h b/toolkit/components/glean/bindings/MetricTypes.h
new file mode 100644
index 0000000000..a7ae09fe19
--- /dev/null
+++ b/toolkit/components/glean/bindings/MetricTypes.h
@@ -0,0 +1,27 @@
+/* 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_MetricTypes_h
+#define mozilla_Glean_MetricTypes_h
+
+#include "mozilla/glean/bindings/Boolean.h"
+#include "mozilla/glean/bindings/Counter.h"
+#include "mozilla/glean/bindings/CustomDistribution.h"
+#include "mozilla/glean/bindings/Datetime.h"
+#include "mozilla/glean/bindings/Denominator.h"
+#include "mozilla/glean/bindings/Event.h"
+#include "mozilla/glean/bindings/Labeled.h"
+#include "mozilla/glean/bindings/MemoryDistribution.h"
+#include "mozilla/glean/bindings/Numerator.h"
+#include "mozilla/glean/bindings/Quantity.h"
+#include "mozilla/glean/bindings/Rate.h"
+#include "mozilla/glean/bindings/String.h"
+#include "mozilla/glean/bindings/StringList.h"
+#include "mozilla/glean/bindings/Text.h"
+#include "mozilla/glean/bindings/Timespan.h"
+#include "mozilla/glean/bindings/TimingDistribution.h"
+#include "mozilla/glean/bindings/Url.h"
+#include "mozilla/glean/bindings/Uuid.h"
+
+#endif // mozilla_Glean_MetricTypes_h
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..4f2d439d80
--- /dev/null
+++ b/toolkit/components/glean/bindings/jog/src/lib.rs
@@ -0,0 +1,267 @@
+/* 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,
+ precise_timestamps: 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,
+ precise_timestamps,
+ 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,
+ precise_timestamps: 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,
+ precise_timestamps,
+ 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,
+ precise_timestamps: 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.precise_timestamps,
+ ping.reason_codes.unwrap_or_else(Vec::new),
+ );
+ }
+ true
+}
diff --git a/toolkit/components/glean/bindings/private/Boolean.cpp b/toolkit/components/glean/bindings/private/Boolean.cpp
new file mode 100644
index 0000000000..8300990b49
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Boolean.cpp
@@ -0,0 +1,71 @@
+/* -*- 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/Boolean.h"
+
+#include "nsString.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void BooleanMetric::Set(bool aValue) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId) {
+ Telemetry::ScalarSet(scalarId.extract(), aValue);
+ } else if (IsSubmetricId(mId)) {
+ GetLabeledMirrorLock().apply([&](auto& lock) {
+ auto tuple = lock.ref()->MaybeGet(mId);
+ if (tuple) {
+ Telemetry::ScalarSet(std::get<0>(tuple.ref()), std::get<1>(tuple.ref()),
+ aValue);
+ }
+ });
+ }
+ fog_boolean_set(mId, int(aValue));
+}
+
+Result<Maybe<bool>, nsCString> BooleanMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_boolean_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_boolean_test_has_value(mId, &aPingName)) {
+ return Maybe<bool>();
+ }
+ return Some(fog_boolean_test_get_value(mId, &aPingName));
+}
+
+} // namespace impl
+
+JSObject* GleanBoolean::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanBoolean_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanBoolean::Set(bool aValue) { mBoolean.Set(aValue); }
+
+dom::Nullable<bool> GleanBoolean::TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv) {
+ dom::Nullable<bool> ret;
+ auto result = mBoolean.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return ret;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ ret.SetValue(optresult.value());
+ }
+ return ret;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Boolean.h b/toolkit/components/glean/bindings/private/Boolean.h
new file mode 100644
index 0000000000..6947e1b713
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Boolean.h
@@ -0,0 +1,80 @@
+/* -*- 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_GleanBoolean_h
+#define mozilla_glean_GleanBoolean_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace glean {
+
+namespace impl {
+
+class BooleanMetric {
+ public:
+ constexpr explicit BooleanMetric(uint32_t id) : mId(id) {}
+
+ /**
+ * Set to the specified boolean value.
+ *
+ * @param aValue the value to set.
+ */
+ void Set(bool aValue) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a boolean.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric.
+ */
+ Result<Maybe<bool>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+
+} // namespace impl
+
+class GleanBoolean final : public GleanMetric {
+ public:
+ explicit GleanBoolean(uint32_t id, nsISupports* aParent)
+ : GleanMetric(aParent), mBoolean(id) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Set(bool aValue);
+
+ dom::Nullable<bool> TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanBoolean() = default;
+
+ const impl::BooleanMetric mBoolean;
+};
+
+} // namespace glean
+} // namespace mozilla
+
+#endif /* mozilla_glean_GleanBoolean.h */
diff --git a/toolkit/components/glean/bindings/private/Common.cpp b/toolkit/components/glean/bindings/private/Common.cpp
new file mode 100644
index 0000000000..f84c39c05d
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Common.cpp
@@ -0,0 +1,33 @@
+/* -*- 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 "Common.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::glean {
+
+// This is copied from TelemetryCommons.cpp (and modified because consoleservice
+// handles threading), but that one is not exported.
+// There's _at least_ a third instance of `LogToBrowserConsole`,
+// but that one is slightly different.
+void LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg) {
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->Init(aMsg, u""_ns, u""_ns, 0, 0, aLogLevel, "chrome javascript"_ns,
+ false /* from private window */, true /* from chrome context */);
+ console->LogMessage(error);
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Common.h b/toolkit/components/glean/bindings/private/Common.h
new file mode 100644
index 0000000000..e3dd7a0a47
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Common.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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_Common_h
+#define mozilla_glean_Common_h
+
+#include "nsIScriptError.h"
+
+namespace mozilla::glean {
+
+/**
+ * Dumps a log message to the Browser Console using the provided level.
+ *
+ * @param aLogLevel The level to use when displaying the message in the browser
+ * console (e.g. nsIScriptError::warningFlag, ...).
+ * @param aMsg The text message to print to the console.
+ */
+void LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg);
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Common_h */
diff --git a/toolkit/components/glean/bindings/private/Counter.cpp b/toolkit/components/glean/bindings/private/Counter.cpp
new file mode 100644
index 0000000000..f7f70f29eb
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Counter.cpp
@@ -0,0 +1,74 @@
+/* -*- 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/Counter.h"
+
+#include "nsString.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void CounterMetric::Add(int32_t aAmount) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (aAmount >= 0) {
+ if (scalarId) {
+ Telemetry::ScalarAdd(scalarId.extract(), aAmount);
+ } else if (IsSubmetricId(mId)) {
+ GetLabeledMirrorLock().apply([&](auto& lock) {
+ auto tuple = lock.ref()->MaybeGet(mId);
+ if (tuple && aAmount > 0) {
+ Telemetry::ScalarAdd(std::get<0>(tuple.ref()),
+ std::get<1>(tuple.ref()), (uint32_t)aAmount);
+ }
+ });
+ }
+ }
+ fog_counter_add(mId, aAmount);
+}
+
+Result<Maybe<int32_t>, nsCString> CounterMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_counter_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_counter_test_has_value(mId, &aPingName)) {
+ return Maybe<int32_t>(); // can't use Nothing() or templates will fail.
+ }
+ return Some(fog_counter_test_get_value(mId, &aPingName));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanCounter::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanCounter_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanCounter::Add(int32_t aAmount) { mCounter.Add(aAmount); }
+
+dom::Nullable<int32_t> GleanCounter::TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv) {
+ dom::Nullable<int32_t> ret;
+ auto result = mCounter.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return ret;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ ret.SetValue(optresult.value());
+ }
+ return ret;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Counter.h b/toolkit/components/glean/bindings/private/Counter.h
new file mode 100644
index 0000000000..c378df8e5d
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Counter.h
@@ -0,0 +1,77 @@
+/* -*- 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_GleanCounter_h
+#define mozilla_glean_GleanCounter_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class CounterMetric {
+ public:
+ constexpr explicit CounterMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Increases the counter by `amount`.
+ *
+ * @param aAmount The amount to increase by. Should be positive.
+ */
+ void Add(int32_t aAmount = 1) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<int32_t>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanCounter final : public GleanMetric {
+ public:
+ explicit GleanCounter(uint32_t id, nsISupports* aParent)
+ : GleanMetric(aParent), mCounter(id) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Add(int32_t aAmount);
+
+ dom::Nullable<int32_t> TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanCounter() = default;
+
+ const impl::CounterMetric mCounter;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanCounter_h */
diff --git a/toolkit/components/glean/bindings/private/CustomDistribution.cpp b/toolkit/components/glean/bindings/private/CustomDistribution.cpp
new file mode 100644
index 0000000000..2f0226cb58
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/CustomDistribution.cpp
@@ -0,0 +1,105 @@
+/* -*- 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/CustomDistribution.h"
+
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/HistogramGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void CustomDistributionMetric::AccumulateSamples(
+ const nsTArray<uint64_t>& aSamples) const {
+ auto hgramId = HistogramIdForMetric(mId);
+ if (hgramId) {
+ auto id = hgramId.extract();
+ // N.B.: There is an `Accumulate(nsTArray<T>)`, but `T` is `uint32_t` and
+ // we got `uint64_t`s here.
+ for (auto sample : aSamples) {
+ Telemetry::Accumulate(id, sample);
+ }
+ }
+ fog_custom_distribution_accumulate_samples(mId, &aSamples);
+}
+
+void CustomDistributionMetric::AccumulateSamplesSigned(
+ const nsTArray<int64_t>& aSamples) const {
+ auto hgramId = HistogramIdForMetric(mId);
+ if (hgramId) {
+ auto id = hgramId.extract();
+ // N.B.: There is an `Accumulate(nsTArray<T>)`, but `T` is `uint32_t` and
+ // we got `int64_t`s here.
+ for (auto sample : aSamples) {
+ Telemetry::Accumulate(id, sample);
+ }
+ }
+ fog_custom_distribution_accumulate_samples_signed(mId, &aSamples);
+}
+
+Result<Maybe<DistributionData>, nsCString>
+CustomDistributionMetric::TestGetValue(const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_custom_distribution_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_custom_distribution_test_has_value(mId, &aPingName)) {
+ return Maybe<DistributionData>();
+ }
+ nsTArray<uint64_t> buckets;
+ nsTArray<uint64_t> counts;
+ uint64_t sum;
+ fog_custom_distribution_test_get_value(mId, &aPingName, &sum, &buckets,
+ &counts);
+ return Some(DistributionData(buckets, counts, sum));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanCustomDistribution::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanCustomDistribution_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanCustomDistribution::AccumulateSamples(
+ const dom::Sequence<int64_t>& aSamples) {
+ mCustomDist.AccumulateSamplesSigned(aSamples);
+}
+
+void GleanCustomDistribution::TestGetValue(
+ const nsACString& aPingName,
+ dom::Nullable<dom::GleanDistributionData>& aRetval, ErrorResult& aRv) {
+ auto result = mCustomDist.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (optresult.isNothing()) {
+ return;
+ }
+
+ dom::GleanDistributionData ret;
+ ret.mSum = optresult.ref().sum;
+ auto& data = optresult.ref().values;
+ for (const auto& entry : data) {
+ dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket;
+ bucket.mKey = nsPrintfCString("%" PRIu64, entry.GetKey());
+ bucket.mValue = entry.GetData();
+ ret.mValues.Entries().EmplaceBack(std::move(bucket));
+ }
+ aRetval.SetValue(std::move(ret));
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/CustomDistribution.h b/toolkit/components/glean/bindings/private/CustomDistribution.h
new file mode 100644
index 0000000000..8227b024ad
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/CustomDistribution.h
@@ -0,0 +1,95 @@
+/* -*- 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_GleanCustomDistribution_h
+#define mozilla_glean_GleanCustomDistribution_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/glean/bindings/DistributionData.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+struct GleanDistributionData;
+} // namespace mozilla::dom
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class CustomDistributionMetric {
+ public:
+ constexpr explicit CustomDistributionMetric(uint32_t aId) : mId(aId) {}
+
+ /**
+ * Accumulates the provided samples in the metric.
+ *
+ * @param aSamples The vector holding the samples to be recorded by the
+ * metric.
+ */
+ void AccumulateSamples(const nsTArray<uint64_t>& aSamples) const;
+
+ /**
+ * Accumulates the provided samples in the metric.
+ *
+ * @param aSamples The vector holding the samples to be recorded by the
+ * metric.
+ *
+ * Notes: Discards any negative value in `samples`
+ * and reports an `InvalidValue` error for each of them.
+ */
+ void AccumulateSamplesSigned(const nsTArray<int64_t>& aSamples) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a DistributionData.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<DistributionData>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanCustomDistribution final : public GleanMetric {
+ public:
+ explicit GleanCustomDistribution(uint64_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mCustomDist(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void AccumulateSamples(const dom::Sequence<int64_t>& aSamples);
+
+ void TestGetValue(const nsACString& aPingName,
+ dom::Nullable<dom::GleanDistributionData>& aRetval,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanCustomDistribution() = default;
+
+ const impl::CustomDistributionMetric mCustomDist;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanCustomDistribution_h */
diff --git a/toolkit/components/glean/bindings/private/Datetime.cpp b/toolkit/components/glean/bindings/private/Datetime.cpp
new file mode 100644
index 0000000000..ac88c67426
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Datetime.cpp
@@ -0,0 +1,121 @@
+/* -*- 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/Datetime.h"
+
+#include "jsapi.h"
+#include "js/Date.h"
+#include "nsString.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "prtime.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void DatetimeMetric::Set(const PRExplodedTime* aValue) const {
+ PRExplodedTime exploded;
+ if (!aValue) {
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ } else {
+ exploded = *aValue;
+ }
+
+ auto id = ScalarIdForMetric(mId);
+ if (id) {
+ const uint32_t buflen = 64; // More than enough for now.
+ char buf[buflen];
+ uint32_t written = PR_FormatTime(buf, buflen, "%FT%T%z", &exploded);
+ if (written > 2 && written < 64) {
+ // Format's still not quite there. Gotta put a `:` between timezone
+ // hours and minutes
+ buf[written] = '\0';
+ buf[written - 1] = buf[written - 2];
+ buf[written - 2] = buf[written - 3];
+ buf[written - 3] = ':';
+ Telemetry::ScalarSet(id.extract(), NS_ConvertASCIItoUTF16(buf));
+ }
+ }
+
+ int32_t offset =
+ exploded.tm_params.tp_gmt_offset + exploded.tm_params.tp_dst_offset;
+ FogDatetime dt{exploded.tm_year,
+ static_cast<uint32_t>(exploded.tm_month + 1),
+ static_cast<uint32_t>(exploded.tm_mday),
+ static_cast<uint32_t>(exploded.tm_hour),
+ static_cast<uint32_t>(exploded.tm_min),
+ static_cast<uint32_t>(exploded.tm_sec),
+ static_cast<uint32_t>(exploded.tm_usec * 1000),
+ offset};
+ fog_datetime_set(mId, &dt);
+}
+
+Result<Maybe<PRExplodedTime>, nsCString> DatetimeMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_datetime_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_datetime_test_has_value(mId, &aPingName)) {
+ return Maybe<PRExplodedTime>();
+ }
+ FogDatetime ret{0};
+ fog_datetime_test_get_value(mId, &aPingName, &ret);
+ PRExplodedTime pret{0};
+ pret.tm_year = static_cast<PRInt16>(ret.year);
+ pret.tm_month = static_cast<PRInt32>(ret.month - 1);
+ pret.tm_mday = static_cast<PRInt32>(ret.day);
+ pret.tm_hour = static_cast<PRInt32>(ret.hour);
+ pret.tm_min = static_cast<PRInt32>(ret.minute);
+ pret.tm_sec = static_cast<PRInt32>(ret.second);
+ pret.tm_usec = static_cast<PRInt32>(ret.nano / 1000); // truncated is fine
+ pret.tm_params.tp_gmt_offset = static_cast<PRInt32>(ret.offset_seconds);
+ return Some(std::move(pret));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanDatetime::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanDatetime_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanDatetime::Set(const dom::Optional<int64_t>& aValue) {
+ if (aValue.WasPassed()) {
+ PRExplodedTime exploded;
+ PR_ExplodeTime(aValue.Value(), PR_LocalTimeParameters, &exploded);
+ mDatetime.Set(&exploded);
+ } else {
+ mDatetime.Set();
+ }
+}
+
+void GleanDatetime::TestGetValue(JSContext* aCx, const nsACString& aPingName,
+ JS::MutableHandle<JS::Value> aResult,
+ ErrorResult& aRv) {
+ auto result = mDatetime.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aResult.set(JS::UndefinedValue());
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (optresult.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ double millis =
+ static_cast<double>(PR_ImplodeTime(optresult.ptr())) / PR_USEC_PER_MSEC;
+ JS::Rooted<JSObject*> root(aCx,
+ JS::NewDateObject(aCx, JS::TimeClip(millis)));
+ aResult.setObject(*root);
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Datetime.h b/toolkit/components/glean/bindings/private/Datetime.h
new file mode 100644
index 0000000000..1dc4dfbe70
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Datetime.h
@@ -0,0 +1,79 @@
+/* -*- 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_GleanDatetime_h
+#define mozilla_glean_GleanDatetime_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+#include "prtime.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class DatetimeMetric {
+ public:
+ constexpr explicit DatetimeMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Set the datetime to the provided value, or the local now.
+ *
+ * @param amount The date value to set.
+ */
+ void Set(const PRExplodedTime* aValue = nullptr) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a PRExplodedTime.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<PRExplodedTime>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanDatetime final : public GleanMetric {
+ public:
+ explicit GleanDatetime(uint32_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mDatetime(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Set(const dom::Optional<int64_t>& aValue);
+
+ void TestGetValue(JSContext* aCx, const nsACString& aPingName,
+ JS::MutableHandle<JS::Value> aResult, ErrorResult& aRv);
+
+ private:
+ virtual ~GleanDatetime() = default;
+
+ const impl::DatetimeMetric mDatetime;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanDatetime_h */
diff --git a/toolkit/components/glean/bindings/private/Denominator.cpp b/toolkit/components/glean/bindings/private/Denominator.cpp
new file mode 100644
index 0000000000..459bb002a7
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Denominator.cpp
@@ -0,0 +1,64 @@
+/* -*- 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/Denominator.h"
+
+#include "nsString.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void DenominatorMetric::Add(int32_t aAmount) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId && aAmount >= 0) {
+ Telemetry::ScalarAdd(scalarId.extract(), aAmount);
+ }
+ fog_denominator_add(mId, aAmount);
+}
+
+Result<Maybe<int32_t>, nsCString> DenominatorMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_denominator_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_denominator_test_has_value(mId, &aPingName)) {
+ return Maybe<int32_t>();
+ }
+ return Some(fog_denominator_test_get_value(mId, &aPingName));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanDenominator::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanDenominator_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanDenominator::Add(int32_t aAmount) { mDenominator.Add(aAmount); }
+
+dom::Nullable<int32_t> GleanDenominator::TestGetValue(
+ const nsACString& aPingName, ErrorResult& aRv) {
+ dom::Nullable<int32_t> ret;
+ auto result = mDenominator.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return ret;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ ret.SetValue(optresult.value());
+ }
+ return ret;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Denominator.h b/toolkit/components/glean/bindings/private/Denominator.h
new file mode 100644
index 0000000000..c52b0cb7ad
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Denominator.h
@@ -0,0 +1,77 @@
+/* -*- 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_GleanDenominator_h
+#define mozilla_glean_GleanDenominator_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class DenominatorMetric {
+ public:
+ constexpr explicit DenominatorMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Increases the counter by `amount`.
+ *
+ * @param aAmount The amount to increase by. Should be positive.
+ */
+ void Add(int32_t aAmount = 1) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<int32_t>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanDenominator final : public GleanMetric {
+ public:
+ explicit GleanDenominator(uint32_t id, nsISupports* aParent)
+ : GleanMetric(aParent), mDenominator(id) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Add(int32_t aAmount);
+
+ dom::Nullable<int32_t> TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanDenominator() = default;
+
+ const impl::DenominatorMetric mDenominator;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanDenominator_h */
diff --git a/toolkit/components/glean/bindings/private/DistributionData.h b/toolkit/components/glean/bindings/private/DistributionData.h
new file mode 100644
index 0000000000..6ff995f222
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/DistributionData.h
@@ -0,0 +1,32 @@
+/* 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_DistributionData_h
+#define mozilla_glean_DistributionData_h
+
+#include "nsTHashMap.h"
+
+namespace mozilla::glean {
+
+struct DistributionData final {
+ uint64_t sum;
+ nsTHashMap<nsUint64HashKey, uint64_t> values;
+
+ /**
+ * Create distribution data from the buckets, counts and sum,
+ * as returned by `fog_*_distribution_test_get_value`.
+ */
+ DistributionData(const nsTArray<uint64_t>& aBuckets,
+ const nsTArray<uint64_t>& aCounts, uint64_t aSum)
+ : sum(aSum) {
+ for (size_t i = 0; i < aBuckets.Length(); ++i) {
+ this->values.InsertOrUpdate(aBuckets[i], aCounts[i]);
+ }
+ }
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_DistributionData_h */
diff --git a/toolkit/components/glean/bindings/private/Event.cpp b/toolkit/components/glean/bindings/private/Event.cpp
new file mode 100644
index 0000000000..6d4065947d
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Event.cpp
@@ -0,0 +1,110 @@
+/* -*- 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/Event.h"
+
+#include "nsString.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/glean/bindings/Common.h"
+#include "jsapi.h"
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty, JS_Enumerate, JS_GetProperty, JS_GetPropertyById
+
+namespace mozilla::glean {
+
+/* virtual */
+JSObject* GleanEvent::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanEvent_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// Convert all capital letters to "_x" where "x" is the corresponding lowercase.
+nsCString camelToSnake(const nsACString& aCamel) {
+ nsCString snake;
+ const auto* start = aCamel.BeginReading();
+ const auto* end = aCamel.EndReading();
+ for (; start != end; ++start) {
+ if ('A' <= *start && *start <= 'Z') {
+ snake.AppendLiteral("_");
+ snake.Append(static_cast<char>(std::tolower(*start)));
+ } else {
+ snake.Append(*start);
+ }
+ }
+ return snake;
+}
+
+void GleanEvent::Record(
+ const dom::Optional<dom::Record<nsCString, nsCString>>& aExtra) {
+ if (!aExtra.WasPassed()) {
+ mEvent.Record();
+ return;
+ }
+
+ nsTArray<nsCString> extraKeys;
+ nsTArray<nsCString> extraValues;
+ CopyableTArray<Telemetry::EventExtraEntry> telExtras;
+ for (const auto& entry : aExtra.Value().Entries()) {
+ if (entry.mValue.IsVoid()) {
+ // Someone passed undefined/null for this value.
+ // Pretend it wasn't here.
+ continue;
+ }
+ // We accept camelCase extra keys, but Glean requires snake_case.
+ auto snakeKey = camelToSnake(entry.mKey);
+
+ extraKeys.AppendElement(snakeKey);
+ extraValues.AppendElement(entry.mValue);
+ telExtras.EmplaceBack(Telemetry::EventExtraEntry{entry.mKey, entry.mValue});
+ }
+
+ // Since this calls the implementation directly, we need to implement GIFFT
+ // here as well as in EventMetric::Record.
+ auto id = EventIdForMetric(mEvent.mId);
+ if (id) {
+ Telemetry::RecordEvent(id.extract(), Nothing(),
+ telExtras.IsEmpty() ? Nothing() : Some(telExtras));
+ }
+
+ // Calling the implementation directly, because we have a `string->string`
+ // map, not a `T->string` map the C++ API expects.
+ impl::fog_event_record(mEvent.mId, &extraKeys, &extraValues);
+}
+
+void GleanEvent::TestGetValue(
+ const nsACString& aPingName,
+ dom::Nullable<nsTArray<dom::GleanEventRecord>>& aResult, ErrorResult& aRv) {
+ auto resEvents = mEvent.TestGetValue(aPingName);
+ if (resEvents.isErr()) {
+ aRv.ThrowDataError(resEvents.unwrapErr());
+ return;
+ }
+ auto optEvents = resEvents.unwrap();
+ if (optEvents.isNothing()) {
+ return;
+ }
+
+ nsTArray<dom::GleanEventRecord> ret;
+ for (auto& event : optEvents.extract()) {
+ dom::GleanEventRecord record;
+ if (!event.mExtra.IsEmpty()) {
+ record.mExtra.Construct();
+ for (auto& extraEntry : event.mExtra) {
+ dom::binding_detail::RecordEntry<nsCString, nsCString> extra;
+ extra.mKey = std::get<0>(extraEntry);
+ extra.mValue = std::get<1>(extraEntry);
+ record.mExtra.Value().Entries().EmplaceBack(std::move(extra));
+ }
+ }
+ record.mCategory = event.mCategory;
+ record.mName = event.mName;
+ record.mTimestamp = event.mTimestamp;
+ ret.EmplaceBack(std::move(record));
+ }
+ aResult.SetValue(std::move(ret));
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Event.h b/toolkit/components/glean/bindings/private/Event.h
new file mode 100644
index 0000000000..72fd70410c
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Event.h
@@ -0,0 +1,177 @@
+/* -*- 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_GleanEvent_h
+#define mozilla_glean_GleanEvent_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Record.h"
+#include "mozilla/glean/bindings/EventGIFFTMap.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/ResultVariant.h"
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+// forward declaration
+struct GleanEventRecord;
+} // namespace mozilla::dom
+
+namespace mozilla::glean {
+
+// forward declaration
+class GleanEvent;
+
+namespace impl {
+
+/**
+ * Represents the recorded data for a single event
+ */
+struct RecordedEvent {
+ public:
+ uint64_t mTimestamp;
+ nsCString mCategory;
+ nsCString mName;
+
+ nsTArray<std::tuple<nsCString, nsCString>> mExtra;
+};
+
+template <class T>
+class EventMetric {
+ friend class mozilla::glean::GleanEvent;
+
+ public:
+ constexpr explicit EventMetric(uint32_t id) : mId(id) {}
+
+ /**
+ * Record an event.
+ *
+ * @param aExtras The list of (extra key, value) pairs. Allowed extra keys are
+ * defined in the metric definition.
+ * If the wrong keys are used or values are too large
+ * an error is report and no event is recorded.
+ */
+ void Record(const Maybe<T>& aExtras = Nothing()) const {
+ auto id = EventIdForMetric(mId);
+ if (id) {
+ // NB. In case `aExtras` is filled we call `ToFfiExtra`, causing
+ // twice the required allocation. We could be smarter and reuse the data.
+ // But this is GIFFT-only allocation, so wait to be told it's a problem.
+ Maybe<CopyableTArray<Telemetry::EventExtraEntry>> telExtras;
+ if (aExtras) {
+ CopyableTArray<Telemetry::EventExtraEntry> extras;
+ auto serializedExtras = aExtras->ToFfiExtra();
+ auto keys = std::move(std::get<0>(serializedExtras));
+ auto values = std::move(std::get<1>(serializedExtras));
+ for (size_t i = 0; i < keys.Length(); i++) {
+ extras.EmplaceBack(Telemetry::EventExtraEntry{keys[i], values[i]});
+ }
+ telExtras = Some(extras);
+ }
+ Telemetry::RecordEvent(id.extract(), Nothing(), telExtras);
+ }
+ if (aExtras) {
+ auto extra = aExtras->ToFfiExtra();
+ fog_event_record(mId, &std::get<0>(extra), &std::get<1>(extra));
+ } else {
+ nsTArray<nsCString> keys;
+ nsTArray<nsCString> vals;
+ fog_event_record(mId, &keys, &vals);
+ }
+ }
+
+ /**
+ * **Test-only API**
+ *
+ * Get a list of currently stored events for this event metric.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<nsTArray<RecordedEvent>>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const {
+ nsCString err;
+ if (fog_event_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+
+ if (!fog_event_test_has_value(mId, &aPingName)) {
+ return Maybe<nsTArray<RecordedEvent>>();
+ }
+
+ nsTArray<FfiRecordedEvent> events;
+ fog_event_test_get_value(mId, &aPingName, &events);
+
+ nsTArray<RecordedEvent> result;
+ for (const auto& event : events) {
+ auto ev = result.AppendElement();
+ ev->mTimestamp = event.timestamp;
+ ev->mCategory.Append(event.category);
+ ev->mName.Assign(event.name);
+
+ MOZ_ASSERT(event.extras.Length() % 2 == 0);
+ ev->mExtra.SetCapacity(event.extras.Length() / 2);
+ for (unsigned int i = 0; i < event.extras.Length(); i += 2) {
+ // keys & values are interleaved.
+ nsCString key = std::move(event.extras[i]);
+ nsCString value = std::move(event.extras[i + 1]);
+ ev->mExtra.AppendElement(
+ std::make_tuple(std::move(key), std::move(value)));
+ }
+ }
+ return Some(std::move(result));
+ }
+
+ private:
+ static const nsCString ExtraStringForKey(uint32_t aKey);
+
+ const uint32_t mId;
+};
+
+} // namespace impl
+
+struct NoExtraKeys {
+ std::tuple<nsTArray<nsCString>, nsTArray<nsCString>> ToFfiExtra() const {
+ nsTArray<nsCString> extraKeys;
+ nsTArray<nsCString> extraValues;
+ return std::make_tuple(std::move(extraKeys), std::move(extraValues));
+ }
+};
+
+class GleanEvent final : public GleanMetric {
+ public:
+ explicit GleanEvent(uint32_t id, nsISupports* aParent)
+ : GleanMetric(aParent), mEvent(id) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Record(const dom::Optional<dom::Record<nsCString, nsCString>>& aExtra);
+
+ void TestGetValue(const nsACString& aPingName,
+ dom::Nullable<nsTArray<dom::GleanEventRecord>>& aResult,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanEvent() = default;
+
+ const impl::EventMetric<NoExtraKeys> mEvent;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanEvent.h */
diff --git a/toolkit/components/glean/bindings/private/Labeled.cpp b/toolkit/components/glean/bindings/private/Labeled.cpp
new file mode 100644
index 0000000000..23527708e0
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Labeled.cpp
@@ -0,0 +1,49 @@
+/* -*- 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/Labeled.h"
+
+#include "mozilla/dom/GleanBinding.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/glean/bindings/GleanJSMetricsLookup.h"
+#include "mozilla/glean/bindings/MetricTypes.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+JSObject* GleanLabeled::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanLabeled_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<GleanMetric> GleanLabeled::NamedGetter(const nsAString& aName,
+ bool& aFound) {
+ auto label = NS_ConvertUTF16toUTF8(aName);
+ // All strings will map to a label. Either a valid one or `__other__`.
+ aFound = true;
+ uint32_t submetricId = 0;
+ already_AddRefed<GleanMetric> submetric =
+ NewSubMetricFromIds(mTypeId, mId, label, &submetricId, mParent);
+
+ auto mirrorId = ScalarIdForMetric(mId);
+ if (mirrorId) {
+ GetLabeledMirrorLock().apply([&](auto& lock) {
+ auto tuple = std::make_tuple<Telemetry::ScalarID, nsString>(
+ mirrorId.extract(), nsString(aName));
+ lock.ref()->InsertOrUpdate(submetricId, std::move(tuple));
+ });
+ }
+ return submetric;
+}
+
+bool GleanLabeled::NameIsEnumerable(const nsAString& aName) { return false; }
+
+void GleanLabeled::GetSupportedNames(nsTArray<nsString>& aNames) {
+ // We really don't know, so don't do anything.
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Labeled.h b/toolkit/components/glean/bindings/private/Labeled.h
new file mode 100644
index 0000000000..65e31bd2bd
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Labeled.h
@@ -0,0 +1,248 @@
+/* -*- 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_Labeled_h
+#define mozilla_glean_Labeled_h
+
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/Boolean.h"
+#include "mozilla/glean/bindings/Counter.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/bindings/String.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+enum class DynamicLabel : uint16_t;
+
+namespace mozilla::glean {
+
+namespace impl {
+
+template <typename T, typename E>
+class Labeled {
+ public:
+ constexpr explicit Labeled<T, E>(uint32_t id) : mId(id) {}
+
+ /**
+ * Gets a specific metric for a given label.
+ *
+ * If a set of acceptable labels were specified in the `metrics.yaml` file,
+ * and the given label is not in the set, it will be recorded under the
+ * special `OTHER_LABEL` label.
+ *
+ * If a set of acceptable labels was not specified in the `metrics.yaml` file,
+ * only the first 16 unique labels will be used.
+ * After that, any additional labels will be recorded under the special
+ * `OTHER_LABEL` label.
+ *
+ * @param aLabel - a snake_case string under 30 characters in length,
+ * otherwise the metric will be recorded under the special
+ * `OTHER_LABEL` label and an error will be recorded.
+ */
+ T Get(const nsACString& aLabel) const;
+
+ /**
+ * Gets a specific metric for a given label, using the label's enum variant.
+ *
+ * @param aLabel - a variant of this label's label enum.
+ */
+ T EnumGet(E aLabel) const;
+
+ private:
+ const uint32_t mId;
+};
+
+static inline void UpdateLabeledMirror(Telemetry::ScalarID aMirrorId,
+ uint32_t aSubmetricId,
+ const nsACString& aLabel) {
+ GetLabeledMirrorLock().apply([&](auto& lock) {
+ auto tuple = std::make_tuple<Telemetry::ScalarID, nsString>(
+ std::move(aMirrorId), NS_ConvertUTF8toUTF16(aLabel));
+ lock.ref()->InsertOrUpdate(aSubmetricId, std::move(tuple));
+ });
+}
+
+template <typename E>
+class Labeled<BooleanMetric, E> {
+ public:
+ constexpr explicit Labeled(uint32_t id) : mId(id) {}
+
+ BooleanMetric Get(const nsACString& aLabel) const {
+ auto submetricId = fog_labeled_boolean_get(mId, &aLabel);
+ // If this labeled metric is mirrored, we need to map the submetric id back
+ // to the label string and mirrored scalar so we can mirror its operations.
+ auto mirrorId = ScalarIdForMetric(mId);
+ if (mirrorId) {
+ UpdateLabeledMirror(mirrorId.extract(), submetricId, aLabel);
+ }
+ return BooleanMetric(submetricId);
+ }
+
+ BooleanMetric EnumGet(E aLabel) const {
+ auto submetricId =
+ fog_labeled_boolean_enum_get(mId, static_cast<uint16_t>(aLabel));
+ auto mirrorId = ScalarIdForMetric(mId);
+ if (mirrorId) {
+ // Telemetry's keyed scalars operate on (16-bit) strings,
+ // so we're going to need the string for this enum.
+ nsCString label;
+ fog_labeled_enum_to_str(mId, static_cast<uint16_t>(aLabel), &label);
+ UpdateLabeledMirror(mirrorId.extract(), submetricId, label);
+ }
+ return BooleanMetric(submetricId);
+ }
+
+ private:
+ const uint32_t mId;
+};
+
+template <typename E>
+class Labeled<CounterMetric, E> {
+ public:
+ constexpr explicit Labeled(uint32_t id) : mId(id) {}
+
+ CounterMetric Get(const nsACString& aLabel) const {
+ auto submetricId = fog_labeled_counter_get(mId, &aLabel);
+ // If this labeled metric is mirrored, we need to map the submetric id back
+ // to the label string and mirrored scalar so we can mirror its operations.
+ auto mirrorId = ScalarIdForMetric(mId);
+ if (mirrorId) {
+ UpdateLabeledMirror(mirrorId.extract(), submetricId, aLabel);
+ }
+ return CounterMetric(submetricId);
+ }
+
+ CounterMetric EnumGet(E aLabel) const {
+ auto submetricId =
+ fog_labeled_counter_enum_get(mId, static_cast<uint16_t>(aLabel));
+ auto mirrorId = ScalarIdForMetric(mId);
+ if (mirrorId) {
+ // Telemetry's keyed scalars operate on (16-bit) strings,
+ // so we're going to need the string for this enum.
+ nsCString label;
+ fog_labeled_enum_to_str(mId, static_cast<uint16_t>(aLabel), &label);
+ UpdateLabeledMirror(mirrorId.extract(), submetricId, label);
+ }
+ return CounterMetric(submetricId);
+ }
+
+ private:
+ const uint32_t mId;
+};
+
+template <typename E>
+class Labeled<StringMetric, E> {
+ public:
+ constexpr explicit Labeled(uint32_t id) : mId(id) {}
+
+ StringMetric Get(const nsACString& aLabel) const {
+ auto submetricId = fog_labeled_string_get(mId, &aLabel);
+ // Why no GIFFT map here?
+ // Labeled Strings can't be mirrored. Telemetry has no compatible probe.
+ return StringMetric(submetricId);
+ }
+
+ StringMetric EnumGet(E aLabel) const {
+ auto submetricId =
+ fog_labeled_string_enum_get(mId, static_cast<uint16_t>(aLabel));
+ // Why no GIFFT map here?
+ // Labeled Strings can't be mirrored. Telemetry has no compatible probe.
+ return StringMetric(submetricId);
+ }
+
+ private:
+ const uint32_t mId;
+};
+
+template <>
+class Labeled<BooleanMetric, DynamicLabel> {
+ public:
+ constexpr explicit Labeled(uint32_t id) : mId(id) {}
+
+ BooleanMetric Get(const nsACString& aLabel) const {
+ auto submetricId = fog_labeled_boolean_get(mId, &aLabel);
+ // If this labeled metric is mirrored, we need to map the submetric id back
+ // to the label string and mirrored scalar so we can mirror its operations.
+ auto mirrorId = ScalarIdForMetric(mId);
+ if (mirrorId) {
+ UpdateLabeledMirror(mirrorId.extract(), submetricId, aLabel);
+ }
+ return BooleanMetric(submetricId);
+ }
+
+ BooleanMetric EnumGet(DynamicLabel aLabel) const = delete;
+
+ private:
+ const uint32_t mId;
+};
+
+template <>
+class Labeled<CounterMetric, DynamicLabel> {
+ public:
+ constexpr explicit Labeled(uint32_t id) : mId(id) {}
+
+ CounterMetric Get(const nsACString& aLabel) const {
+ auto submetricId = fog_labeled_counter_get(mId, &aLabel);
+ // If this labeled metric is mirrored, we need to map the submetric id back
+ // to the label string and mirrored scalar so we can mirror its operations.
+ auto mirrorId = ScalarIdForMetric(mId);
+ if (mirrorId) {
+ UpdateLabeledMirror(mirrorId.extract(), submetricId, aLabel);
+ }
+ return CounterMetric(submetricId);
+ }
+
+ CounterMetric EnumGet(DynamicLabel aLabel) const = delete;
+
+ private:
+ const uint32_t mId;
+};
+
+template <>
+class Labeled<StringMetric, DynamicLabel> {
+ public:
+ constexpr explicit Labeled(uint32_t id) : mId(id) {}
+
+ StringMetric Get(const nsACString& aLabel) const {
+ auto submetricId = fog_labeled_string_get(mId, &aLabel);
+ // Why no GIFFT map here?
+ // Labeled Strings can't be mirrored. Telemetry has no compatible probe.
+ return StringMetric(submetricId);
+ }
+
+ StringMetric EnumGet(DynamicLabel aLabel) const = delete;
+
+ private:
+ const uint32_t mId;
+};
+
+} // namespace impl
+
+class GleanLabeled final : public GleanMetric {
+ public:
+ explicit GleanLabeled(uint32_t aId, uint32_t aTypeId, nsISupports* aParent)
+ : GleanMetric(aParent), mId(aId), mTypeId(aTypeId) {}
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override final;
+
+ already_AddRefed<GleanMetric> NamedGetter(const nsAString& aName,
+ bool& aFound);
+ bool NameIsEnumerable(const nsAString& aName);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ private:
+ virtual ~GleanLabeled() = default;
+
+ const uint32_t mId;
+ const uint32_t mTypeId;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Labeled_h */
diff --git a/toolkit/components/glean/bindings/private/MemoryDistribution.cpp b/toolkit/components/glean/bindings/private/MemoryDistribution.cpp
new file mode 100644
index 0000000000..a580c5df3c
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/MemoryDistribution.cpp
@@ -0,0 +1,89 @@
+/* -*- 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/MemoryDistribution.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/HistogramGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsIClassInfoImpl.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void MemoryDistributionMetric::Accumulate(size_t aSample) const {
+ auto hgramId = HistogramIdForMetric(mId);
+ if (hgramId) {
+ Telemetry::Accumulate(hgramId.extract(), aSample);
+ }
+ static_assert(sizeof(size_t) <= sizeof(uint64_t),
+ "Memory distribution samples might overflow.");
+ fog_memory_distribution_accumulate(mId, aSample);
+}
+
+Result<Maybe<DistributionData>, nsCString>
+MemoryDistributionMetric::TestGetValue(const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_memory_distribution_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_memory_distribution_test_has_value(mId, &aPingName)) {
+ return Maybe<DistributionData>();
+ }
+ nsTArray<uint64_t> buckets;
+ nsTArray<uint64_t> counts;
+ uint64_t sum;
+ fog_memory_distribution_test_get_value(mId, &aPingName, &sum, &buckets,
+ &counts);
+ return Some(DistributionData(buckets, counts, sum));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanMemoryDistribution::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanMemoryDistribution_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanMemoryDistribution::Accumulate(uint64_t aSample) {
+ mMemoryDist.Accumulate(aSample);
+}
+
+void GleanMemoryDistribution::TestGetValue(
+ const nsACString& aPingName,
+ dom::Nullable<dom::GleanDistributionData>& aRetval, ErrorResult& aRv) {
+ auto result = mMemoryDist.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (optresult.isNothing()) {
+ return;
+ }
+
+ dom::GleanDistributionData ret;
+ ret.mSum = optresult.ref().sum;
+ auto& data = optresult.ref().values;
+ for (const auto& entry : data) {
+ dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket;
+ bucket.mKey = nsPrintfCString("%" PRIu64, entry.GetKey());
+ bucket.mValue = entry.GetData();
+ ret.mValues.Entries().EmplaceBack(std::move(bucket));
+ }
+ aRetval.SetValue(std::move(ret));
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/MemoryDistribution.h b/toolkit/components/glean/bindings/private/MemoryDistribution.h
new file mode 100644
index 0000000000..6b58fc7f75
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/MemoryDistribution.h
@@ -0,0 +1,86 @@
+/* -*- 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_GleanMemoryDistribution_h
+#define mozilla_glean_GleanMemoryDistribution_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/DistributionData.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+struct GleanDistributionData;
+} // namespace mozilla::dom
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class MemoryDistributionMetric {
+ public:
+ constexpr explicit MemoryDistributionMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Accumulates the provided sample in the metric.
+ *
+ * @param aSample The sample to be recorded by the metric. The sample is
+ * assumed to be in the confgured memory unit of the metric.
+ *
+ * Notes: Values bigger than 1 Terabyte (2^40 bytes) are truncated and an
+ * InvalidValue error is recorded.
+ */
+ void Accumulate(size_t aSample) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a DistributionData.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<DistributionData>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanMemoryDistribution final : public GleanMetric {
+ public:
+ explicit GleanMemoryDistribution(uint64_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mMemoryDist(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Accumulate(uint64_t aSample);
+
+ void TestGetValue(const nsACString& aPingName,
+ dom::Nullable<dom::GleanDistributionData>& aRetval,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanMemoryDistribution() = default;
+
+ const impl::MemoryDistributionMetric mMemoryDist;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanMemoryDistribution_h */
diff --git a/toolkit/components/glean/bindings/private/Numerator.cpp b/toolkit/components/glean/bindings/private/Numerator.cpp
new file mode 100644
index 0000000000..87f931e53d
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Numerator.cpp
@@ -0,0 +1,73 @@
+/* -*- 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/Numerator.h"
+
+#include "nsString.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "jsapi.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void NumeratorMetric::AddToNumerator(int32_t aAmount) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId && aAmount >= 0) {
+ Telemetry::ScalarAdd(scalarId.extract(), u"numerator"_ns, aAmount);
+ }
+ fog_numerator_add_to_numerator(mId, aAmount);
+}
+
+Result<Maybe<std::pair<int32_t, int32_t>>, nsCString>
+NumeratorMetric::TestGetValue(const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_numerator_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_numerator_test_has_value(mId, &aPingName)) {
+ return Maybe<std::pair<int32_t, int32_t>>();
+ }
+ int32_t num = 0;
+ int32_t den = 0;
+ fog_numerator_test_get_value(mId, &aPingName, &num, &den);
+ return Some(std::make_pair(num, den));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanNumerator::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanNumerator_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanNumerator::AddToNumerator(int32_t aAmount) {
+ mNumerator.AddToNumerator(aAmount);
+}
+
+void GleanNumerator::TestGetValue(const nsACString& aPingName,
+ dom::Nullable<dom::GleanRateData>& aResult,
+ ErrorResult& aRv) {
+ auto result = mNumerator.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ dom::GleanRateData ret;
+ auto pair = optresult.extract();
+ ret.mNumerator = pair.first;
+ ret.mDenominator = pair.second;
+ aResult.SetValue(std::move(ret));
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Numerator.h b/toolkit/components/glean/bindings/private/Numerator.h
new file mode 100644
index 0000000000..a2ea02936a
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Numerator.h
@@ -0,0 +1,84 @@
+/* -*- 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_GleanNumerator_h
+#define mozilla_glean_GleanNumerator_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+
+namespace mozilla::dom {
+struct GleanRateData;
+} // namespace mozilla::dom
+
+namespace mozilla::glean {
+
+namespace impl {
+
+// Actually a RateMetric, but one whose denominator is a CounterMetric external
+// to the RateMetric.
+class NumeratorMetric {
+ public:
+ constexpr explicit NumeratorMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Increases the numerator by `amount`.
+ *
+ * @param aAmount The amount to increase by. Should be positive.
+ */
+ void AddToNumerator(int32_t aAmount = 1) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a pair of integers.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<std::pair<int32_t, int32_t>>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanNumerator final : public GleanMetric {
+ public:
+ explicit GleanNumerator(uint32_t id, nsISupports* aParent)
+ : GleanMetric(aParent), mNumerator(id) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void AddToNumerator(int32_t aAmount);
+
+ void TestGetValue(const nsACString& aPingName,
+ dom::Nullable<dom::GleanRateData>& aResult,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanNumerator() = default;
+
+ const impl::NumeratorMetric mNumerator;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanNumerator_h */
diff --git a/toolkit/components/glean/bindings/private/Ping.cpp b/toolkit/components/glean/bindings/private/Ping.cpp
new file mode 100644
index 0000000000..19f4fb5f77
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Ping.cpp
@@ -0,0 +1,85 @@
+/* -*- 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/Ping.h"
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+#include "nsTHashMap.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+using CallbackMapType = nsTHashMap<uint32_t, PingTestCallback>;
+using MetricIdToCallbackMutex = StaticDataMutex<UniquePtr<CallbackMapType>>;
+static Maybe<MetricIdToCallbackMutex::AutoLock> GetCallbackMapLock() {
+ static MetricIdToCallbackMutex sCallbacks("sCallbacks");
+ auto lock = sCallbacks.Lock();
+ // Test callbacks will continue to work until the end of AppShutdownTelemetry
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ return Nothing();
+ }
+ if (!*lock) {
+ *lock = MakeUnique<CallbackMapType>();
+ RunOnShutdown(
+ [&] {
+ auto lock = sCallbacks.Lock();
+ *lock = nullptr; // deletes, see UniquePtr.h
+ },
+ ShutdownPhase::XPCOMWillShutdown);
+ }
+ return Some(std::move(lock));
+}
+
+void Ping::Submit(const nsACString& aReason) const {
+ {
+ auto callback = Maybe<PingTestCallback>();
+ GetCallbackMapLock().apply(
+ [&](auto& lock) { callback = lock.ref()->Extract(mId); });
+ // Calling the callback outside of the lock allows it to register a new
+ // callback itself.
+ if (callback) {
+ callback.extract()(aReason);
+ }
+ }
+ fog_submit_ping_by_id(mId, &aReason);
+}
+
+void Ping::TestBeforeNextSubmit(PingTestCallback&& aCallback) const {
+ {
+ GetCallbackMapLock().apply(
+ [&](auto& lock) { lock.ref()->InsertOrUpdate(mId, aCallback); });
+ }
+}
+
+} // namespace impl
+
+NS_IMPL_CLASSINFO(GleanPing, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanPing, nsIGleanPing)
+
+NS_IMETHODIMP
+GleanPing::Submit(const nsACString& aReason) {
+ mPing.Submit(aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanPing::TestBeforeNextSubmit(nsIGleanPingTestCallback* aCallback) {
+ if (NS_WARN_IF(!aCallback)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ // Throw the bare ptr into a COM ptr to keep it around in the lambda.
+ nsCOMPtr<nsIGleanPingTestCallback> callback = aCallback;
+ mPing.TestBeforeNextSubmit(
+ [callback](const nsACString& aReason) { callback->Call(aReason); });
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Ping.h b/toolkit/components/glean/bindings/private/Ping.h
new file mode 100644
index 0000000000..2a92d87995
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Ping.h
@@ -0,0 +1,83 @@
+/* -*- 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_Ping_h
+#define mozilla_glean_Ping_h
+
+#include "mozilla/DataMutex.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/Maybe.h"
+#include "nsIGleanPing.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+typedef std::function<void(const nsACString& aReason)> PingTestCallback;
+
+namespace impl {
+
+class Ping {
+ public:
+ constexpr explicit Ping(uint32_t aId) : mId(aId) {}
+
+ /**
+ * Collect and submit the ping for eventual upload.
+ *
+ * This will collect all stored data to be included in the ping.
+ * Data with lifetime `ping` will then be reset.
+ *
+ * If the ping is configured with `send_if_empty = false`
+ * and the ping currently contains no content,
+ * it will not be queued for upload.
+ * If the ping is configured with `send_if_empty = true`
+ * it will be queued for upload even if empty.
+ *
+ * Pings always contain the `ping_info` and `client_info` sections.
+ * See [ping
+ * sections](https://mozilla.github.io/glean/book/user/pings/index.html#ping-sections)
+ * for details.
+ *
+ * @param aReason - Optional. The reason the ping is being submitted.
+ * Must match one of the configured `reason_codes`.
+ */
+ void Submit(const nsACString& aReason = nsCString()) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Register a callback to be called right before this ping is next submitted.
+ * The provided function is called exactly once before submitting.
+ *
+ * Note: The callback will be called on any call to submit.
+ * A ping may not be sent afterwards, e.g. if the ping is empty and
+ * `send_if_empty` is `false`
+ *
+ * @param aCallback - The callback to call on the next submit.
+ */
+ void TestBeforeNextSubmit(PingTestCallback&& aCallback) const;
+
+ private:
+ const uint32_t mId;
+};
+
+} // namespace impl
+
+class GleanPing final : public nsIGleanPing {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANPING
+
+ explicit GleanPing(uint32_t aId) : mPing(aId) {}
+
+ private:
+ virtual ~GleanPing() = default;
+
+ const impl::Ping mPing;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Ping_h */
diff --git a/toolkit/components/glean/bindings/private/Quantity.cpp b/toolkit/components/glean/bindings/private/Quantity.cpp
new file mode 100644
index 0000000000..9b7034e013
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Quantity.cpp
@@ -0,0 +1,68 @@
+/* -*- 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/Quantity.h"
+
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void QuantityMetric::Set(int64_t aValue) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId && aValue >= 0) {
+ uint32_t theValue = static_cast<uint32_t>(aValue);
+ if (aValue > std::numeric_limits<uint32_t>::max()) {
+ theValue = std::numeric_limits<uint32_t>::max();
+ }
+ Telemetry::ScalarSet(scalarId.extract(), theValue);
+ }
+ fog_quantity_set(mId, aValue);
+}
+
+Result<Maybe<int64_t>, nsCString> QuantityMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_quantity_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_quantity_test_has_value(mId, &aPingName)) {
+ return Maybe<int64_t>();
+ }
+ return Some(fog_quantity_test_get_value(mId, &aPingName));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanQuantity::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanQuantity_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanQuantity::Set(int64_t aValue) { mQuantity.Set(aValue); }
+
+dom::Nullable<int64_t> GleanQuantity::TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv) {
+ dom::Nullable<int64_t> ret;
+ auto result = mQuantity.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return ret;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ ret.SetValue(optresult.value());
+ }
+ return ret;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Quantity.h b/toolkit/components/glean/bindings/private/Quantity.h
new file mode 100644
index 0000000000..bf7485a33c
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Quantity.h
@@ -0,0 +1,77 @@
+/* -*- 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_GleanQuantity_h
+#define mozilla_glean_GleanQuantity_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "nsTString.h"
+#include "nsIScriptError.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class QuantityMetric {
+ public:
+ constexpr explicit QuantityMetric(uint32_t id) : mId(id) {}
+
+ /**
+ * Set to the specified value.
+ *
+ * @param aValue the value to set.
+ */
+ void Set(int64_t aValue) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric.
+ */
+ Result<Maybe<int64_t>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+
+} // namespace impl
+
+class GleanQuantity final : public GleanMetric {
+ public:
+ explicit GleanQuantity(uint32_t id, nsISupports* aParent)
+ : GleanMetric(aParent), mQuantity(id) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Set(int64_t aValue);
+
+ dom::Nullable<int64_t> TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanQuantity() = default;
+
+ const impl::QuantityMetric mQuantity;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanQuantity.h */
diff --git a/toolkit/components/glean/bindings/private/Rate.cpp b/toolkit/components/glean/bindings/private/Rate.cpp
new file mode 100644
index 0000000000..d317b5ad7d
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Rate.cpp
@@ -0,0 +1,86 @@
+/* -*- 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/Rate.h"
+
+#include "jsapi.h"
+#include "nsString.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void RateMetric::AddToNumerator(int32_t aAmount) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId && aAmount >= 0) {
+ Telemetry::ScalarAdd(scalarId.extract(), u"numerator"_ns, aAmount);
+ }
+ fog_rate_add_to_numerator(mId, aAmount);
+}
+
+void RateMetric::AddToDenominator(int32_t aAmount) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId && aAmount >= 0) {
+ Telemetry::ScalarAdd(scalarId.extract(), u"denominator"_ns, aAmount);
+ }
+ fog_rate_add_to_denominator(mId, aAmount);
+}
+
+Result<Maybe<std::pair<int32_t, int32_t>>, nsCString> RateMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_rate_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_rate_test_has_value(mId, &aPingName)) {
+ return Maybe<std::pair<int32_t, int32_t>>();
+ }
+ int32_t num = 0;
+ int32_t den = 0;
+ fog_rate_test_get_value(mId, &aPingName, &num, &den);
+ return Some(std::make_pair(num, den));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanRate::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanRate_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanRate::AddToNumerator(int32_t aAmount) {
+ mRate.AddToNumerator(aAmount);
+}
+
+void GleanRate::AddToDenominator(int32_t aAmount) {
+ mRate.AddToDenominator(aAmount);
+}
+
+void GleanRate::TestGetValue(const nsACString& aPingName,
+ dom::Nullable<dom::GleanRateData>& aResult,
+ ErrorResult& aRv) {
+ auto result = mRate.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ dom::GleanRateData ret;
+ auto pair = optresult.extract();
+ ret.mNumerator = pair.first;
+ ret.mDenominator = pair.second;
+ aResult.SetValue(std::move(ret));
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Rate.h b/toolkit/components/glean/bindings/private/Rate.h
new file mode 100644
index 0000000000..227372bdd2
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Rate.h
@@ -0,0 +1,90 @@
+/* -*- 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_GleanRate_h
+#define mozilla_glean_GleanRate_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+
+namespace mozilla::dom {
+struct GleanRateData;
+} // namespace mozilla::dom
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class RateMetric {
+ public:
+ constexpr explicit RateMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Increases the numerator by `amount`.
+ *
+ * @param aAmount The amount to increase by. Should be positive.
+ */
+ void AddToNumerator(int32_t aAmount = 1) const;
+
+ /*
+ * Increases the denominator by `amount`.
+ *
+ * @param aAmount The amount to increase by. Should be positive.
+ */
+ void AddToDenominator(int32_t aAmount = 1) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a pair of integers.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<std::pair<int32_t, int32_t>>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanRate final : public GleanMetric {
+ public:
+ explicit GleanRate(uint32_t id, nsISupports* aParent)
+ : GleanMetric(aParent), mRate(id) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void AddToNumerator(int32_t aAmount);
+ void AddToDenominator(int32_t aAmount);
+
+ void TestGetValue(const nsACString& aPingName,
+ dom::Nullable<dom::GleanRateData>& aResult,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanRate() = default;
+
+ const impl::RateMetric mRate;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanRate_h */
diff --git a/toolkit/components/glean/bindings/private/String.cpp b/toolkit/components/glean/bindings/private/String.cpp
new file mode 100644
index 0000000000..f012884c8c
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/String.cpp
@@ -0,0 +1,75 @@
+/* -*- 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/String.h"
+
+#include "jsapi.h"
+#include "js/String.h"
+#include "nsString.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void StringMetric::Set(const nsACString& aValue) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId) {
+ Telemetry::ScalarSet(scalarId.extract(), NS_ConvertUTF8toUTF16(aValue));
+ }
+ fog_string_set(mId, &aValue);
+}
+
+Result<Maybe<nsCString>, nsCString> StringMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_string_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_string_test_has_value(mId, &aPingName)) {
+ return Maybe<nsCString>();
+ }
+ nsCString ret;
+ fog_string_test_get_value(mId, &aPingName, &ret);
+ return Some(ret);
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanString::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanString_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanString::Set(const nsACString& aValue) {
+ if (aValue.IsVoid()) {
+ // TODO: Instrument this error (bug 1691073)
+ return;
+ }
+ mString.Set(aValue);
+}
+
+void GleanString::TestGetValue(const nsACString& aPingName, nsCString& aResult,
+ ErrorResult& aRv) {
+ auto result = mString.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ aResult.Assign(optresult.extract());
+ } else {
+ aResult.SetIsVoid(true);
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/String.h b/toolkit/components/glean/bindings/private/String.h
new file mode 100644
index 0000000000..b4aadb63f9
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/String.h
@@ -0,0 +1,80 @@
+/* -*- 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_GleanString_h
+#define mozilla_glean_GleanString_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class StringMetric {
+ public:
+ constexpr explicit StringMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Set to the specified value.
+ *
+ * Truncates the value if it is longer than 100 bytes and logs an error.
+ * See https://mozilla.github.io/glean/book/user/metrics/string.html#limits.
+ *
+ * @param aValue The string to set the metric to.
+ */
+ void Set(const nsACString& aValue) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a string.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<nsCString>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanString final : public GleanMetric {
+ public:
+ explicit GleanString(uint32_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mString(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Set(const nsACString& aValue);
+
+ void TestGetValue(const nsACString& aPingName, nsCString& aResult,
+ ErrorResult& aRv);
+
+ virtual ~GleanString() = default;
+
+ private:
+ const impl::StringMetric mString;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanString_h */
diff --git a/toolkit/components/glean/bindings/private/StringList.cpp b/toolkit/components/glean/bindings/private/StringList.cpp
new file mode 100644
index 0000000000..b2875807a1
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/StringList.cpp
@@ -0,0 +1,80 @@
+/* -*- 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/StringList.h"
+
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void StringListMetric::Add(const nsACString& aValue) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId) {
+ Telemetry::ScalarSet(scalarId.extract(), NS_ConvertUTF8toUTF16(aValue),
+ true);
+ }
+ fog_string_list_add(mId, &aValue);
+}
+
+void StringListMetric::Set(const nsTArray<nsCString>& aValue) const {
+ // Calling `Set` on a mirrored labeled_string is likely an error.
+ // We can't remove keys from the mirror scalar and handle this 'properly',
+ // so you shouldn't use this operation at all.
+ (void)NS_WARN_IF(ScalarIdForMetric(mId).isSome());
+ fog_string_list_set(mId, &aValue);
+}
+
+Result<Maybe<nsTArray<nsCString>>, nsCString> StringListMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_string_list_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_string_list_test_has_value(mId, &aPingName)) {
+ return Maybe<nsTArray<nsCString>>();
+ }
+ nsTArray<nsCString> ret;
+ fog_string_list_test_get_value(mId, &aPingName, &ret);
+ return Some(std::move(ret));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanStringList::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanStringList_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanStringList::Add(const nsACString& aValue) { mStringList.Add(aValue); }
+
+void GleanStringList::Set(const dom::Sequence<nsCString>& aValue) {
+ mStringList.Set(aValue);
+}
+
+void GleanStringList::TestGetValue(const nsACString& aPingName,
+ dom::Nullable<nsTArray<nsCString>>& aResult,
+ ErrorResult& aRv) {
+ auto result = mStringList.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ aResult.SetValue(optresult.extract());
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/StringList.h b/toolkit/components/glean/bindings/private/StringList.h
new file mode 100644
index 0000000000..94cb830cf6
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/StringList.h
@@ -0,0 +1,93 @@
+/* -*- 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_GleanStringList_h
+#define mozilla_glean_GleanStringList_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class StringListMetric {
+ public:
+ constexpr explicit StringListMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Adds a new string to the list.
+ *
+ * Truncates the value if it is longer than 50 bytes and logs an error.
+ *
+ * @param aValue The string to add.
+ */
+ void Add(const nsACString& aValue) const;
+
+ /*
+ * Set to a specific list of strings.
+ *
+ * Truncates any values longer than 50 bytes and logs an error.
+ * Truncates the list if it is over 20 items long.
+ * See
+ * https://mozilla.github.io/glean/book/user/metrics/string_list.html#limits.
+ *
+ * @param aValue The list of strings to set the metric to.
+ */
+ void Set(const nsTArray<nsCString>& aValue) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<nsTArray<nsCString>>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanStringList final : public GleanMetric {
+ public:
+ explicit GleanStringList(uint32_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mStringList(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Add(const nsACString& aValue);
+ void Set(const dom::Sequence<nsCString>& aValue);
+
+ void TestGetValue(const nsACString& aPingName,
+ dom::Nullable<nsTArray<nsCString>>& aResult,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanStringList() = default;
+
+ const impl::StringListMetric mStringList;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanStringList_h */
diff --git a/toolkit/components/glean/bindings/private/Text.cpp b/toolkit/components/glean/bindings/private/Text.cpp
new file mode 100644
index 0000000000..fd36f66136
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Text.cpp
@@ -0,0 +1,64 @@
+/* -*- 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/Text.h"
+
+#include "jsapi.h"
+#include "js/String.h"
+#include "nsString.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void TextMetric::Set(const nsACString& aValue) const {
+ fog_text_set(mId, &aValue);
+}
+
+Result<Maybe<nsCString>, nsCString> TextMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_text_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_text_test_has_value(mId, &aPingName)) {
+ return Maybe<nsCString>();
+ }
+ nsCString ret;
+ fog_text_test_get_value(mId, &aPingName, &ret);
+ return Some(ret);
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanText::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanText_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanText::Set(const nsACString& aValue) { mText.Set(aValue); }
+
+void GleanText::TestGetValue(const nsACString& aPingName, nsCString& aResult,
+ ErrorResult& aRv) {
+ auto result = mText.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ aResult.Assign(optresult.extract());
+ } else {
+ aResult.SetIsVoid(true);
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Text.h b/toolkit/components/glean/bindings/private/Text.h
new file mode 100644
index 0000000000..545b1e68a0
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Text.h
@@ -0,0 +1,54 @@
+/* -*- 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_GleanText_h
+#define mozilla_glean_GleanText_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class TextMetric {
+ public:
+ constexpr explicit TextMetric(uint32_t aId) : mId(aId) {}
+
+ void Set(const nsACString& aValue) const;
+
+ Result<Maybe<nsCString>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+
+} // namespace impl
+
+class GleanText final : public GleanMetric {
+ public:
+ explicit GleanText(uint32_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mText(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Set(const nsACString& aValue);
+
+ void TestGetValue(const nsACString& aPingName, nsCString& aResult,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanText() = default;
+
+ const impl::TextMetric mText;
+};
+} // namespace mozilla::glean
+#endif
diff --git a/toolkit/components/glean/bindings/private/Timespan.cpp b/toolkit/components/glean/bindings/private/Timespan.cpp
new file mode 100644
index 0000000000..13e57317fa
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Timespan.cpp
@@ -0,0 +1,186 @@
+/* -*- 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/Timespan.h"
+
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+namespace {
+class ScalarIDHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const ScalarID&;
+ using KeyTypePointer = const ScalarID*;
+
+ 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
+
+using TimesToStartsMutex =
+ StaticDataMutex<UniquePtr<nsTHashMap<ScalarIDHashKey, TimeStamp>>>;
+static 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));
+}
+
+void TimespanMetric::Start() const {
+ auto optScalarId = ScalarIdForMetric(mId);
+ if (optScalarId) {
+ auto scalarId = optScalarId.extract();
+ GetTimesToStartsLock().apply([&](auto& lock) {
+ (void)NS_WARN_IF(lock.ref()->Remove(scalarId));
+ lock.ref()->InsertOrUpdate(scalarId, TimeStamp::Now());
+ });
+ }
+ fog_timespan_start(mId);
+}
+
+void TimespanMetric::Stop() const {
+ auto optScalarId = ScalarIdForMetric(mId);
+ if (optScalarId) {
+ auto scalarId = optScalarId.extract();
+ GetTimesToStartsLock().apply([&](auto& lock) {
+ auto optStart = lock.ref()->Extract(scalarId);
+ if (!NS_WARN_IF(!optStart)) {
+ double delta = (TimeStamp::Now() - optStart.extract()).ToMilliseconds();
+ uint32_t theDelta = static_cast<uint32_t>(delta);
+ if (delta > std::numeric_limits<uint32_t>::max()) {
+ theDelta = std::numeric_limits<uint32_t>::max();
+ } else if (MOZ_UNLIKELY(delta < 0)) {
+ theDelta = 0;
+ }
+ Telemetry::ScalarSet(scalarId, theDelta);
+ }
+ });
+ }
+ fog_timespan_stop(mId);
+}
+
+void TimespanMetric::Cancel() const {
+ auto optScalarId = ScalarIdForMetric(mId);
+ if (optScalarId) {
+ auto scalarId = optScalarId.extract();
+ GetTimesToStartsLock().apply(
+ [&](auto& lock) { lock.ref()->Remove(scalarId); });
+ }
+ fog_timespan_cancel(mId);
+}
+
+void TimespanMetric::SetRaw(uint32_t aDuration) const {
+ auto optScalarId = ScalarIdForMetric(mId);
+ if (optScalarId) {
+ auto scalarId = optScalarId.extract();
+ Telemetry::ScalarSet(scalarId, aDuration);
+ }
+ fog_timespan_set_raw(mId, aDuration);
+}
+
+Result<Maybe<uint64_t>, nsCString> TimespanMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_timespan_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_timespan_test_has_value(mId, &aPingName)) {
+ return Maybe<uint64_t>();
+ }
+ return Some(fog_timespan_test_get_value(mId, &aPingName));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanTimespan::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanTimespan_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanTimespan::Start() { mTimespan.Start(); }
+
+void GleanTimespan::Stop() { mTimespan.Stop(); }
+
+void GleanTimespan::Cancel() { mTimespan.Cancel(); }
+
+void GleanTimespan::SetRaw(uint32_t aDuration) { mTimespan.SetRaw(aDuration); }
+
+dom::Nullable<uint64_t> GleanTimespan::TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv) {
+ dom::Nullable<uint64_t> ret;
+ auto result = mTimespan.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return ret;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ ret.SetValue(optresult.value());
+ }
+ return ret;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Timespan.h b/toolkit/components/glean/bindings/private/Timespan.h
new file mode 100644
index 0000000000..296e1cb28f
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Timespan.h
@@ -0,0 +1,110 @@
+/* -*- 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_GleanTimespan_h
+#define mozilla_glean_GleanTimespan_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "nsTString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class TimespanMetric {
+ public:
+ constexpr explicit TimespanMetric(uint32_t aId) : mId(aId) {}
+
+ /**
+ * Start tracking time for the provided metric.
+ *
+ * This records an error if it’s already tracking time (i.e. start was already
+ * called with no corresponding [stop]): in that case the original
+ * start time will be preserved.
+ */
+ void Start() const;
+
+ /**
+ * Stop tracking time for the provided metric.
+ *
+ * Sets the metric to the elapsed time, but does not overwrite an already
+ * existing value.
+ * This will record an error if no [start] was called or there is an already
+ * existing value.
+ */
+ void Stop() const;
+
+ /**
+ * Abort a previous Start.
+ *
+ * No error will be recorded if no Start was called.
+ */
+ void Cancel() const;
+
+ /**
+ * Explicitly sets the timespan value
+ *
+ * This API should only be used if you cannot make use of
+ * `start`/`stop`/`cancel`.
+ *
+ * @param aDuration The duration of this timespan, in units matching the
+ * `time_unit` of this metric's definition.
+ */
+ void SetRaw(uint32_t aDuration) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<uint64_t>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanTimespan final : public GleanMetric {
+ public:
+ explicit GleanTimespan(uint32_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mTimespan(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Start();
+ void Stop();
+ void Cancel();
+ void SetRaw(uint32_t aDuration);
+
+ dom::Nullable<uint64_t> TestGetValue(const nsACString& aPingName,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanTimespan() = default;
+
+ const impl::TimespanMetric mTimespan;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanTimespan_h */
diff --git a/toolkit/components/glean/bindings/private/TimingDistribution.cpp b/toolkit/components/glean/bindings/private/TimingDistribution.cpp
new file mode 100644
index 0000000000..f7a78165ae
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/TimingDistribution.cpp
@@ -0,0 +1,242 @@
+/* -*- 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/TimingDistribution.h"
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/glean/bindings/HistogramGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+
+namespace mozilla::glean {
+
+using MetricId = uint32_t; // Same type as in api/src/private/mod.rs
+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(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;
+};
+
+using TimerToStampMutex =
+ StaticDataMutex<UniquePtr<nsTHashMap<MetricTimerTupleHashKey, TimeStamp>>>;
+static 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));
+}
+
+} // namespace mozilla::glean
+
+// Called from within FOG's Rust impl.
+extern "C" NS_EXPORT void GIFFT_TimingDistributionStart(
+ uint32_t aMetricId, mozilla::glean::TimerId aTimerId) {
+ auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId);
+ if (mirrorId) {
+ mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
+ auto tuple = std::make_tuple(aMetricId, aTimerId);
+ // It should be all but impossible for anyone to have already inserted
+ // this timer for this metric given the monotonicity of timer ids.
+ (void)NS_WARN_IF(lock.ref()->Remove(tuple));
+ lock.ref()->InsertOrUpdate(tuple, mozilla::TimeStamp::Now());
+ });
+ }
+}
+
+// Called from within FOG's Rust impl.
+extern "C" NS_EXPORT void GIFFT_TimingDistributionStopAndAccumulate(
+ uint32_t aMetricId, mozilla::glean::TimerId aTimerId) {
+ auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId);
+ if (mirrorId) {
+ mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
+ auto optStart = lock.ref()->Extract(std::make_tuple(aMetricId, aTimerId));
+ // The timer might not be in the map to be removed if it's already been
+ // cancelled or stop_and_accumulate'd.
+ if (!NS_WARN_IF(!optStart)) {
+ AccumulateTimeDelta(mirrorId.extract(), optStart.extract());
+ }
+ });
+ }
+}
+
+// Called from within FOG's Rust impl.
+extern "C" NS_EXPORT void GIFFT_TimingDistributionAccumulateRawMillis(
+ uint32_t aMetricId, uint32_t aMS) {
+ auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId);
+ if (mirrorId) {
+ Accumulate(mirrorId.extract(), aMS);
+ }
+}
+
+// Called from within FOG's Rust impl.
+extern "C" NS_EXPORT void GIFFT_TimingDistributionCancel(
+ uint32_t aMetricId, mozilla::glean::TimerId aTimerId) {
+ auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId);
+ if (mirrorId) {
+ mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
+ // The timer might not be in the map to be removed if it's already been
+ // cancelled or stop_and_accumulate'd.
+ (void)NS_WARN_IF(
+ !lock.ref()->Remove(std::make_tuple(aMetricId, aTimerId)));
+ });
+ }
+}
+
+namespace mozilla::glean {
+
+namespace impl {
+
+TimerId TimingDistributionMetric::Start() const {
+ return fog_timing_distribution_start(mId);
+}
+
+void TimingDistributionMetric::StopAndAccumulate(const TimerId&& aId) const {
+ fog_timing_distribution_stop_and_accumulate(mId, aId);
+}
+
+// Intentionally not exposed to JS for lack of use case and a time duration
+// type.
+void TimingDistributionMetric::AccumulateRawDuration(
+ const TimeDuration& aDuration) const {
+ fog_timing_distribution_accumulate_raw_nanos(
+ mId, uint64_t(aDuration.ToMicroseconds() * 1000.00));
+}
+
+void TimingDistributionMetric::Cancel(const TimerId&& aId) const {
+ fog_timing_distribution_cancel(mId, aId);
+}
+
+Result<Maybe<DistributionData>, nsCString>
+TimingDistributionMetric::TestGetValue(const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_timing_distribution_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_timing_distribution_test_has_value(mId, &aPingName)) {
+ return Maybe<DistributionData>();
+ }
+ nsTArray<uint64_t> buckets;
+ nsTArray<uint64_t> counts;
+ uint64_t sum;
+ fog_timing_distribution_test_get_value(mId, &aPingName, &sum, &buckets,
+ &counts);
+ return Some(DistributionData(buckets, counts, sum));
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanTimingDistribution::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanTimingDistribution_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+uint64_t GleanTimingDistribution::Start() { return mTimingDist.Start(); }
+
+void GleanTimingDistribution::StopAndAccumulate(uint64_t aId) {
+ mTimingDist.StopAndAccumulate(std::move(aId));
+}
+
+void GleanTimingDistribution::Cancel(uint64_t aId) {
+ mTimingDist.Cancel(std::move(aId));
+}
+
+void GleanTimingDistribution::TestGetValue(
+ const nsACString& aPingName,
+ dom::Nullable<dom::GleanDistributionData>& aRetval, ErrorResult& aRv) {
+ auto result = mTimingDist.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (optresult.isNothing()) {
+ return;
+ }
+
+ dom::GleanDistributionData ret;
+ ret.mSum = optresult.ref().sum;
+ auto& data = optresult.ref().values;
+ for (const auto& entry : data) {
+ dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket;
+ bucket.mKey = nsPrintfCString("%" PRIu64, entry.GetKey());
+ bucket.mValue = entry.GetData();
+ ret.mValues.Entries().EmplaceBack(std::move(bucket));
+ }
+ aRetval.SetValue(std::move(ret));
+}
+
+void GleanTimingDistribution::TestAccumulateRawMillis(uint64_t aSample) {
+ mTimingDist.AccumulateRawDuration(TimeDuration::FromMilliseconds(aSample));
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/TimingDistribution.h b/toolkit/components/glean/bindings/private/TimingDistribution.h
new file mode 100644
index 0000000000..ae4a796279
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/TimingDistribution.h
@@ -0,0 +1,125 @@
+/* -*- 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_GleanTimingDistribution_h
+#define mozilla_glean_GleanTimingDistribution_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/DistributionData.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+struct GleanDistributionData;
+} // namespace mozilla::dom
+
+namespace mozilla::glean {
+
+typedef uint64_t TimerId;
+
+namespace impl {
+class TimingDistributionMetric {
+ public:
+ constexpr explicit TimingDistributionMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Starts tracking time for the provided metric.
+ *
+ * @returns A unique TimerId for the new timer
+ */
+ TimerId Start() const;
+
+ /*
+ * Stops tracking time for the provided metric and associated timer id.
+ *
+ * Adds a count to the corresponding bucket in the timing distribution.
+ * This will record an error if no `Start` was called on this TimerId or
+ * if this TimerId was used to call `Cancel`.
+ *
+ * @param aId The TimerId to associate with this timing. This allows for
+ * concurrent timing of events associated with different ids.
+ */
+ void StopAndAccumulate(const TimerId&& aId) const;
+
+ /*
+ * Adds a duration sample to a timing distribution metric.
+ *
+ * Adds a count to the corresponding bucket in the timing distribution.
+ * Prefer Start() and StopAndAccumulate() where possible.
+ * Users of this API are responsible for ensuring the timing source used
+ * to calculate the TimeDuration is monotonic and consistent accross
+ * platforms.
+ *
+ * NOTE: Negative durations are not handled and will saturate to INT64_MAX
+ * nanoseconds.
+ *
+ * @param aDuration The duration of the sample to add to the distribution.
+ */
+ void AccumulateRawDuration(const TimeDuration& aDuration) const;
+
+ /*
+ * Aborts a previous `Start` call. No error is recorded if no `Start` was
+ * called.
+ *
+ * @param aId The TimerId whose `Start` you wish to abort.
+ */
+ void Cancel(const TimerId&& aId) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a DistributionData.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<DistributionData>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanTimingDistribution final : public GleanMetric {
+ public:
+ explicit GleanTimingDistribution(uint64_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mTimingDist(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ uint64_t Start();
+ void StopAndAccumulate(uint64_t aId);
+ void Cancel(uint64_t aId);
+
+ void TestGetValue(const nsACString& aPingName,
+ dom::Nullable<dom::GleanDistributionData>& aRetval,
+ ErrorResult& aRv);
+
+ void TestAccumulateRawMillis(uint64_t aSample);
+
+ private:
+ virtual ~GleanTimingDistribution() = default;
+
+ const impl::TimingDistributionMetric mTimingDist;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanTimingDistribution_h */
diff --git a/toolkit/components/glean/bindings/private/Url.cpp b/toolkit/components/glean/bindings/private/Url.cpp
new file mode 100644
index 0000000000..060b27f125
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Url.cpp
@@ -0,0 +1,68 @@
+/* -*- 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/Url.h"
+
+#include "jsapi.h"
+#include "js/String.h"
+#include "nsString.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void UrlMetric::Set(const nsACString& aValue) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId) {
+ Telemetry::ScalarSet(scalarId.extract(), NS_ConvertUTF8toUTF16(aValue));
+ }
+ fog_url_set(mId, &aValue);
+}
+
+Result<Maybe<nsCString>, nsCString> UrlMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_url_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_url_test_has_value(mId, &aPingName)) {
+ return Maybe<nsCString>();
+ }
+ nsCString ret;
+ fog_url_test_get_value(mId, &aPingName, &ret);
+ return Some(ret);
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanUrl::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanUrl_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanUrl::Set(const nsACString& aValue) { mUrl.Set(aValue); }
+
+void GleanUrl::TestGetValue(const nsACString& aPingName, nsCString& aResult,
+ ErrorResult& aRv) {
+ auto result = mUrl.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ aResult.Assign(optresult.extract());
+ } else {
+ aResult.SetIsVoid(true);
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Url.h b/toolkit/components/glean/bindings/private/Url.h
new file mode 100644
index 0000000000..26ebdafdd0
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Url.h
@@ -0,0 +1,77 @@
+/* -*- 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_GleanUrl_h
+#define mozilla_glean_GleanUrl_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class UrlMetric {
+ public:
+ constexpr explicit UrlMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Set to the specified value.
+ *
+ * @param aValue The stringified Url to set the metric to.
+ */
+ void Set(const nsACString& aValue) const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a string.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<nsCString>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanUrl final : public GleanMetric {
+ public:
+ explicit GleanUrl(uint32_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mUrl(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Set(const nsACString& aValue);
+
+ void TestGetValue(const nsACString& aPingName, nsCString& aResult,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanUrl() = default;
+
+ const impl::UrlMetric mUrl;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanUrl_h */
diff --git a/toolkit/components/glean/bindings/private/Uuid.cpp b/toolkit/components/glean/bindings/private/Uuid.cpp
new file mode 100644
index 0000000000..6503b5ffd8
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Uuid.cpp
@@ -0,0 +1,76 @@
+/* -*- 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/Uuid.h"
+
+#include "jsapi.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/glean/bindings/ScalarGIFFTMap.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+void UuidMetric::Set(const nsACString& aValue) const {
+ auto scalarId = ScalarIdForMetric(mId);
+ if (scalarId) {
+ Telemetry::ScalarSet(scalarId.extract(), NS_ConvertUTF8toUTF16(aValue));
+ }
+ fog_uuid_set(mId, &aValue);
+}
+
+void UuidMetric::GenerateAndSet() const {
+ // We don't have the generated value to mirror to the scalar,
+ // so calling this function on a mirrored metric is likely an error.
+ (void)NS_WARN_IF(ScalarIdForMetric(mId).isSome());
+ fog_uuid_generate_and_set(mId);
+}
+
+Result<Maybe<nsCString>, nsCString> UuidMetric::TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_uuid_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_uuid_test_has_value(mId, &aPingName)) {
+ return Maybe<nsCString>();
+ }
+ nsCString ret;
+ fog_uuid_test_get_value(mId, &aPingName, &ret);
+ return Some(ret);
+}
+
+} // namespace impl
+
+/* virtual */
+JSObject* GleanUuid::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanUuid_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanUuid::Set(const nsACString& aValue) { mUuid.Set(aValue); }
+
+void GleanUuid::GenerateAndSet() { mUuid.GenerateAndSet(); }
+
+void GleanUuid::TestGetValue(const nsACString& aPingName, nsCString& aResult,
+ ErrorResult& aRv) {
+ auto result = mUuid.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (!optresult.isNothing()) {
+ aResult.Assign(optresult.extract());
+ } else {
+ aResult.SetIsVoid(true);
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Uuid.h b/toolkit/components/glean/bindings/private/Uuid.h
new file mode 100644
index 0000000000..b4c5e2bc32
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Uuid.h
@@ -0,0 +1,83 @@
+/* -*- 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_GleanUuid_h
+#define mozilla_glean_GleanUuid_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class UuidMetric {
+ public:
+ constexpr explicit UuidMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Sets to the specified value.
+ *
+ * @param aValue The UUID to set the metric to.
+ */
+ void Set(const nsACString& aValue) const;
+
+ /*
+ * Generate a new random UUID and set the metric to it.
+ */
+ void GenerateAndSet() const;
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a hyphenated string.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<nsCString>, nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const;
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanUuid final : public GleanMetric {
+ public:
+ explicit GleanUuid(uint32_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mUuid(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Set(const nsACString& aValue);
+ void GenerateAndSet();
+
+ void TestGetValue(const nsACString& aPingName, nsCString& aResult,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~GleanUuid() = default;
+
+ const impl::UuidMetric mUuid;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanUuid_h */