diff options
Diffstat (limited to 'toolkit/components/glean/bindings')
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 */ |