summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/geckoview
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/geckoview')
-rw-r--r--toolkit/components/telemetry/geckoview/gtest/TestGeckoViewStreaming.cpp237
-rw-r--r--toolkit/components/telemetry/geckoview/gtest/moz.build28
-rw-r--r--toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.cpp282
-rw-r--r--toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.h55
-rw-r--r--toolkit/components/telemetry/geckoview/streaming/metrics.yaml13
5 files changed, 615 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/geckoview/gtest/TestGeckoViewStreaming.cpp b/toolkit/components/telemetry/geckoview/gtest/TestGeckoViewStreaming.cpp
new file mode 100644
index 0000000000..ebaa7099a8
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/gtest/TestGeckoViewStreaming.cpp
@@ -0,0 +1,237 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+#include "streaming/GeckoViewStreamingTelemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::Telemetry;
+using namespace TelemetryTestHelpers;
+using GeckoViewStreamingTelemetry::StreamingTelemetryDelegate;
+using mozilla::Telemetry::ScalarID;
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::StrictMock;
+
+namespace {
+
+const char* kGeckoViewStreamingPref = "toolkit.telemetry.geckoview.streaming";
+const char* kBatchTimeoutPref = "toolkit.telemetry.geckoview.batchDurationMS";
+
+constexpr auto kTestHgramName = "TELEMETRY_TEST_STREAMING"_ns;
+constexpr auto kTestHgramName2 = "TELEMETRY_TEST_STREAMING_2"_ns;
+constexpr auto kTestCategoricalName = "TELEMETRY_TEST_CATEGORICAL_OPTOUT"_ns;
+const HistogramID kTestHgram = Telemetry::TELEMETRY_TEST_STREAMING;
+const HistogramID kTestHgram2 = Telemetry::TELEMETRY_TEST_STREAMING_2;
+
+class TelemetryStreamingFixture : public TelemetryTestFixture {
+ protected:
+ virtual void SetUp() {
+ TelemetryTestFixture::SetUp();
+ Preferences::SetBool(kGeckoViewStreamingPref, true);
+ Preferences::SetInt(kBatchTimeoutPref, 5000);
+ }
+ virtual void TearDown() {
+ TelemetryTestFixture::TearDown();
+ Preferences::SetBool(kGeckoViewStreamingPref, false);
+ GeckoViewStreamingTelemetry::RegisterDelegate(nullptr);
+ }
+};
+
+class MockDelegate : public StreamingTelemetryDelegate {
+ public:
+ MOCK_METHOD2(ReceiveHistogramSamples,
+ void(const nsCString& aHistogramName,
+ const nsTArray<uint32_t>& aSamples));
+ MOCK_METHOD2(ReceiveCategoricalHistogramSamples,
+ void(const nsCString& aHistogramName,
+ const nsTArray<uint32_t>& aSamples));
+ MOCK_METHOD2(ReceiveBoolScalarValue,
+ void(const nsCString& aScalarName, bool aValue));
+ MOCK_METHOD2(ReceiveStringScalarValue,
+ void(const nsCString& aScalarName, const nsCString& aValue));
+ MOCK_METHOD2(ReceiveUintScalarValue,
+ void(const nsCString& aScalarName, uint32_t aValue));
+}; // class MockDelegate
+
+TEST_F(TelemetryStreamingFixture, HistogramSamples) {
+ const uint32_t kSampleOne = 401;
+ const uint32_t kSampleTwo = 2019;
+
+ CopyableTArray<uint32_t> samplesArray;
+ samplesArray.AppendElement(kSampleOne);
+ samplesArray.AppendElement(kSampleTwo);
+
+ auto md = MakeRefPtr<MockDelegate>();
+ EXPECT_CALL(*md, ReceiveHistogramSamples(Eq(kTestHgramName),
+ Eq(std::move(samplesArray))));
+ GeckoViewStreamingTelemetry::RegisterDelegate(md);
+
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_STREAMING, kSampleOne);
+ Preferences::SetInt(kBatchTimeoutPref, 0);
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_STREAMING, kSampleTwo);
+}
+
+TEST_F(TelemetryStreamingFixture, CategoricalHistogramSamples) {
+ auto kSampleOne =
+ Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL_OPTOUT::CommonLabel;
+ auto kSampleTwo = Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL_OPTOUT::Label5;
+
+ CopyableTArray<uint32_t> samplesArray;
+ samplesArray.AppendElement(static_cast<uint32_t>(kSampleOne));
+ samplesArray.AppendElement(static_cast<uint32_t>(kSampleOne));
+ samplesArray.AppendElement(static_cast<uint32_t>(kSampleTwo));
+
+ auto md = MakeRefPtr<MockDelegate>();
+ EXPECT_CALL(*md, ReceiveCategoricalHistogramSamples(
+ Eq(kTestCategoricalName), Eq(std::move(samplesArray))));
+ GeckoViewStreamingTelemetry::RegisterDelegate(md);
+
+ Telemetry::AccumulateCategorical(kSampleOne);
+ Telemetry::AccumulateCategorical(kSampleOne);
+ Preferences::SetInt(kBatchTimeoutPref, 0);
+ Telemetry::AccumulateCategorical(kSampleTwo);
+}
+
+TEST_F(TelemetryStreamingFixture, MultipleHistograms) {
+ const uint32_t kSample1 = 400;
+ const uint32_t kSample2 = 1 << 31;
+ const uint32_t kSample3 = 7;
+ CopyableTArray<uint32_t> samplesArray1;
+ samplesArray1.AppendElement(kSample1);
+ samplesArray1.AppendElement(kSample2);
+ CopyableTArray<uint32_t> samplesArray2;
+ samplesArray2.AppendElement(kSample3);
+
+ auto md = MakeRefPtr<MockDelegate>();
+ EXPECT_CALL(*md, ReceiveHistogramSamples(Eq(kTestHgramName),
+ Eq(std::move(samplesArray1))));
+ EXPECT_CALL(*md, ReceiveHistogramSamples(Eq(kTestHgramName2),
+ Eq(std::move(samplesArray2))));
+
+ GeckoViewStreamingTelemetry::RegisterDelegate(md);
+
+ Telemetry::Accumulate(kTestHgram, kSample1);
+ Telemetry::Accumulate(kTestHgram2, kSample3);
+ Preferences::SetInt(kBatchTimeoutPref, 0);
+ Telemetry::Accumulate(kTestHgram, kSample2);
+}
+
+// If we can find a way to convert the expectation's arg into an stl container,
+// we can use gmock's own ::testing::UnorderedElementsAre() instead.
+auto MatchUnordered(uint32_t sample1, uint32_t sample2) {
+ CopyableTArray<uint32_t> samplesArray1;
+ samplesArray1.AppendElement(sample1);
+ samplesArray1.AppendElement(sample2);
+
+ CopyableTArray<uint32_t> samplesArray2;
+ samplesArray2.AppendElement(sample2);
+ samplesArray2.AppendElement(sample1);
+
+ return ::testing::AnyOf(Eq(std::move(samplesArray1)),
+ Eq(std::move(samplesArray2)));
+}
+
+TEST_F(TelemetryStreamingFixture, MultipleThreads) {
+ const uint32_t kSample1 = 4;
+ const uint32_t kSample2 = 14;
+
+ auto md = MakeRefPtr<MockDelegate>();
+ // In this test, samples for the second test hgram are uninteresting.
+ EXPECT_CALL(*md, ReceiveHistogramSamples(Eq(kTestHgramName2), _));
+ EXPECT_CALL(*md, ReceiveHistogramSamples(Eq(kTestHgramName),
+ MatchUnordered(kSample1, kSample2)));
+
+ GeckoViewStreamingTelemetry::RegisterDelegate(md);
+
+ nsCOMPtr<nsIThread> t1;
+ nsCOMPtr<nsIThread> t2;
+ nsCOMPtr<nsIThread> t3;
+
+ nsCOMPtr<nsIRunnable> r1 = NS_NewRunnableFunction(
+ "accumulate 4", [&]() { Telemetry::Accumulate(kTestHgram, kSample1); });
+ nsCOMPtr<nsIRunnable> r2 = NS_NewRunnableFunction(
+ "accumulate 14", [&]() { Telemetry::Accumulate(kTestHgram, kSample2); });
+
+ nsresult rv = NS_NewNamedThread("t1", getter_AddRefs(t1), r1);
+ EXPECT_NS_SUCCEEDED(rv);
+ rv = NS_NewNamedThread("t2", getter_AddRefs(t2), r2);
+ EXPECT_NS_SUCCEEDED(rv);
+
+ // Give the threads a chance to do their work.
+ PR_Sleep(PR_MillisecondsToInterval(1));
+
+ Preferences::SetInt(kBatchTimeoutPref, 0);
+ Telemetry::Accumulate(kTestHgram2, kSample1);
+}
+
+TEST_F(TelemetryStreamingFixture, ScalarValues) {
+ constexpr auto kBoolScalarName = "telemetry.test.boolean_kind"_ns;
+ constexpr auto kStringScalarName = "telemetry.test.string_kind"_ns;
+ constexpr auto kUintScalarName = "telemetry.test.unsigned_int_kind"_ns;
+
+ const bool kBoolScalarValue = true;
+ constexpr auto kStringScalarValue = "a string scalar value"_ns;
+ const uint32_t kUintScalarValue = 42;
+
+ auto md = MakeRefPtr<MockDelegate>();
+ EXPECT_CALL(
+ *md, ReceiveBoolScalarValue(Eq(kBoolScalarName), Eq(kBoolScalarValue)));
+ EXPECT_CALL(*md, ReceiveStringScalarValue(Eq(kStringScalarName),
+ Eq(kStringScalarValue)));
+ EXPECT_CALL(
+ *md, ReceiveUintScalarValue(Eq(kUintScalarName), Eq(kUintScalarValue)));
+
+ GeckoViewStreamingTelemetry::RegisterDelegate(md);
+
+ Telemetry::ScalarSet(ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, kBoolScalarValue);
+ Telemetry::ScalarSet(ScalarID::TELEMETRY_TEST_STRING_KIND,
+ NS_ConvertUTF8toUTF16(kStringScalarValue));
+ Preferences::SetInt(kBatchTimeoutPref,
+ 0); // Trigger batch on next accumulation.
+ Telemetry::ScalarSet(ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND,
+ kUintScalarValue);
+}
+
+TEST_F(TelemetryStreamingFixture, ExpiredHistogram) {
+ const HistogramID kExpiredHistogram = Telemetry::TELEMETRY_TEST_EXPIRED;
+ const uint32_t kSample = 401;
+
+ // Strict Mock fails on any method calls.
+ auto md = MakeRefPtr<StrictMock<MockDelegate>>();
+ GeckoViewStreamingTelemetry::RegisterDelegate(md);
+
+ Preferences::SetInt(kBatchTimeoutPref, 0);
+ Telemetry::Accumulate(kExpiredHistogram, kSample);
+}
+
+TEST_F(TelemetryStreamingFixture, SendOnAppBackground) {
+ constexpr auto kBoolScalarName = "telemetry.test.boolean_kind"_ns;
+ const bool kBoolScalarValue = true;
+ const char* kApplicationBackgroundTopic = "application-background";
+
+ auto md = MakeRefPtr<MockDelegate>();
+ EXPECT_CALL(
+ *md, ReceiveBoolScalarValue(Eq(kBoolScalarName), Eq(kBoolScalarValue)));
+
+ GeckoViewStreamingTelemetry::RegisterDelegate(md);
+ Telemetry::ScalarSet(ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, kBoolScalarValue);
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ ASSERT_TRUE(!!os)
+ << "Observer Service unavailable?!?!";
+ os->NotifyObservers(nullptr, kApplicationBackgroundTopic, nullptr);
+}
+
+} // namespace
diff --git a/toolkit/components/telemetry/geckoview/gtest/moz.build b/toolkit/components/telemetry/geckoview/gtest/moz.build
new file mode 100644
index 0000000000..eb6a2f9293
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/gtest/moz.build
@@ -0,0 +1,28 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Library("telemetrygeckoviewtest")
+
+LOCAL_INCLUDES += [
+ "../",
+ "../..",
+ "../../..",
+ "/toolkit/components/telemetry/tests/gtest",
+ "/xpcom/io",
+]
+
+# GeckoView Streaming Telemetry is only available on Android.
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ UNIFIED_SOURCES += [
+ "TestGeckoViewStreaming.cpp",
+ ]
+
+# We need the following line otherwise including
+# "TelemetryHistogram.h" in tests will fail due to
+# missing headers.
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.cpp b/toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.cpp
new file mode 100644
index 0000000000..6c4b9590c0
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.cpp
@@ -0,0 +1,282 @@
+/* -*- 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 "GeckoViewStreamingTelemetry.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StaticPrefs_toolkit.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTHashMap.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+using mozilla::Runnable;
+using mozilla::StaticMutex;
+using mozilla::StaticMutexAutoLock;
+using mozilla::StaticRefPtr;
+using mozilla::TimeStamp;
+
+// Batches and streams Telemetry samples to a JNI delegate which will
+// (presumably) do something with the data. Expected to be used to route data
+// up to the Android Components layer to be translated into Glean metrics.
+namespace GeckoViewStreamingTelemetry {
+
+class LifecycleObserver;
+void SendBatch(const StaticMutexAutoLock& aLock);
+
+// Topic on which we flush the batch.
+static const char* const kApplicationBackgroundTopic = "application-background";
+
+static StaticMutex gMutex MOZ_UNANNOTATED;
+
+// -- The following state is accessed across threads.
+// -- Do not touch these if you do not hold gMutex.
+
+// The time the batch began.
+TimeStamp gBatchBegan;
+// The batch of histograms and samples.
+typedef nsTHashMap<nsCStringHashKey, nsTArray<uint32_t>> HistogramBatch;
+HistogramBatch gBatch;
+HistogramBatch gCategoricalBatch;
+// The batches of Scalars and their values.
+typedef nsTHashMap<nsCStringHashKey, bool> BoolScalarBatch;
+BoolScalarBatch gBoolScalars;
+typedef nsTHashMap<nsCStringHashKey, nsCString> StringScalarBatch;
+StringScalarBatch gStringScalars;
+typedef nsTHashMap<nsCStringHashKey, uint32_t> UintScalarBatch;
+UintScalarBatch gUintScalars;
+// The delegate to receive the samples and values.
+StaticRefPtr<StreamingTelemetryDelegate> gDelegate;
+// Lifecycle observer used to flush the batch when backgrounded.
+StaticRefPtr<LifecycleObserver> gObserver;
+
+// -- End of gMutex-protected thread-unsafe-accessed data
+
+// Timer that ensures data in the batch never gets too stale.
+// This timer may only be manipulated on the Main Thread.
+StaticRefPtr<nsITimer> gJICTimer;
+
+class LifecycleObserver final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ LifecycleObserver() = default;
+
+ protected:
+ ~LifecycleObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(LifecycleObserver, nsIObserver);
+
+NS_IMETHODIMP
+LifecycleObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, kApplicationBackgroundTopic)) {
+ StaticMutexAutoLock lock(gMutex);
+ SendBatch(lock);
+ }
+ return NS_OK;
+}
+
+void RegisterDelegate(const RefPtr<StreamingTelemetryDelegate>& aDelegate) {
+ StaticMutexAutoLock lock(gMutex);
+ gDelegate = aDelegate;
+}
+
+class SendBatchRunnable : public Runnable {
+ public:
+ explicit SendBatchRunnable(RefPtr<StreamingTelemetryDelegate> aDelegate,
+ HistogramBatch&& aBatch,
+ HistogramBatch&& aCategoricalBatch,
+ BoolScalarBatch&& aBoolScalars,
+ StringScalarBatch&& aStringScalars,
+ UintScalarBatch&& aUintScalars)
+ : Runnable("SendBatchRunnable"),
+ mDelegate(std::move(aDelegate)),
+ mBatch(std::move(aBatch)),
+ mCategoricalBatch(std::move(aCategoricalBatch)),
+ mBoolScalars(std::move(aBoolScalars)),
+ mStringScalars(std::move(aStringScalars)),
+ mUintScalars(std::move(aUintScalars)) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDelegate);
+
+ if (gJICTimer) {
+ gJICTimer->Cancel();
+ }
+
+ for (const auto& entry : mBatch) {
+ const nsCString& histogramName = PromiseFlatCString(entry.GetKey());
+ const nsTArray<uint32_t>& samples = entry.GetData();
+
+ mDelegate->ReceiveHistogramSamples(histogramName, samples);
+ }
+ mBatch.Clear();
+
+ for (const auto& entry : mCategoricalBatch) {
+ const nsCString& histogramName = PromiseFlatCString(entry.GetKey());
+ const nsTArray<uint32_t>& samples = entry.GetData();
+
+ mDelegate->ReceiveCategoricalHistogramSamples(histogramName, samples);
+ }
+ mCategoricalBatch.Clear();
+
+ for (const auto& entry : mBoolScalars) {
+ const nsCString& scalarName = PromiseFlatCString(entry.GetKey());
+ mDelegate->ReceiveBoolScalarValue(scalarName, entry.GetData());
+ }
+ mBoolScalars.Clear();
+
+ for (const auto& entry : mStringScalars) {
+ const nsCString& scalarName = PromiseFlatCString(entry.GetKey());
+ const nsCString& scalarValue = PromiseFlatCString(entry.GetData());
+ mDelegate->ReceiveStringScalarValue(scalarName, scalarValue);
+ }
+ mStringScalars.Clear();
+
+ for (const auto& entry : mUintScalars) {
+ const nsCString& scalarName = PromiseFlatCString(entry.GetKey());
+ mDelegate->ReceiveUintScalarValue(scalarName, entry.GetData());
+ }
+ mUintScalars.Clear();
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<StreamingTelemetryDelegate> mDelegate;
+ HistogramBatch mBatch;
+ HistogramBatch mCategoricalBatch;
+ BoolScalarBatch mBoolScalars;
+ StringScalarBatch mStringScalars;
+ UintScalarBatch mUintScalars;
+}; // class SendBatchRunnable
+
+// Can be called on any thread.
+// NOTE: Pay special attention to what you call in this method as if it
+// accumulates to a gv-streaming-enabled probe we will deadlock the calling
+// thread.
+void SendBatch(const StaticMutexAutoLock& aLock) {
+ if (!gDelegate) {
+ NS_WARNING(
+ "Being asked to send Streaming Telemetry with no registered Streaming "
+ "Telemetry Delegate. Will try again later.");
+ // Give us another full Batch Duration to register a delegate.
+ gBatchBegan = TimeStamp::Now();
+ return;
+ }
+
+ // To make it so accumulations within the delegation don't deadlock us,
+ // move the batches' contents into the Runner.
+ HistogramBatch histogramCopy;
+ gBatch.SwapElements(histogramCopy);
+ HistogramBatch categoricalCopy;
+ gCategoricalBatch.SwapElements(categoricalCopy);
+ BoolScalarBatch boolScalarCopy;
+ gBoolScalars.SwapElements(boolScalarCopy);
+ StringScalarBatch stringScalarCopy;
+ gStringScalars.SwapElements(stringScalarCopy);
+ UintScalarBatch uintScalarCopy;
+ gUintScalars.SwapElements(uintScalarCopy);
+ RefPtr<SendBatchRunnable> runnable = new SendBatchRunnable(
+ gDelegate, std::move(histogramCopy), std::move(categoricalCopy),
+ std::move(boolScalarCopy), std::move(stringScalarCopy),
+ std::move(uintScalarCopy));
+
+ // To make things easier for the delegate, dispatch to the main thread.
+ NS_DispatchToMainThread(runnable);
+}
+
+// Can be called on any thread.
+void BatchCheck(const StaticMutexAutoLock& aLock) {
+ if (!gObserver) {
+ gObserver = new LifecycleObserver();
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(gObserver, kApplicationBackgroundTopic, false);
+ }
+ }
+ if (gBatchBegan.IsNull()) {
+ // Time to begin a new batch.
+ gBatchBegan = TimeStamp::Now();
+ // Set a just-in-case timer to enforce an upper-bound on batch staleness.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "GeckoviewStreamingTelemetry::ArmTimer", []() -> void {
+ if (!gJICTimer) {
+ gJICTimer = NS_NewTimer().take();
+ }
+ if (gJICTimer) {
+ gJICTimer->InitWithNamedFuncCallback(
+ [](nsITimer*, void*) -> void {
+ StaticMutexAutoLock locker(gMutex);
+ SendBatch(locker);
+ },
+ nullptr,
+ mozilla::StaticPrefs::
+ toolkit_telemetry_geckoview_maxBatchStalenessMS(),
+ nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
+ "GeckoviewStreamingTelemetry::SendBatch");
+ }
+ }));
+ }
+ double batchDurationMs = (TimeStamp::Now() - gBatchBegan).ToMilliseconds();
+ if (batchDurationMs >
+ mozilla::StaticPrefs::toolkit_telemetry_geckoview_batchDurationMS()) {
+ SendBatch(aLock);
+ gBatchBegan = TimeStamp();
+ }
+}
+
+// Can be called on any thread.
+void HistogramAccumulate(const nsCString& aName, bool aIsCategorical,
+ uint32_t aValue) {
+ StaticMutexAutoLock lock(gMutex);
+
+ if (aIsCategorical) {
+ nsTArray<uint32_t>& samples = gCategoricalBatch.LookupOrInsert(aName);
+ samples.AppendElement(aValue);
+ } else {
+ nsTArray<uint32_t>& samples = gBatch.LookupOrInsert(aName);
+ samples.AppendElement(aValue);
+ }
+
+ BatchCheck(lock);
+}
+
+void BoolScalarSet(const nsCString& aName, bool aValue) {
+ StaticMutexAutoLock lock(gMutex);
+
+ gBoolScalars.InsertOrUpdate(aName, aValue);
+
+ BatchCheck(lock);
+}
+
+void StringScalarSet(const nsCString& aName, const nsCString& aValue) {
+ StaticMutexAutoLock lock(gMutex);
+
+ gStringScalars.InsertOrUpdate(aName, aValue);
+
+ BatchCheck(lock);
+}
+
+void UintScalarSet(const nsCString& aName, uint32_t aValue) {
+ StaticMutexAutoLock lock(gMutex);
+
+ gUintScalars.InsertOrUpdate(aName, aValue);
+
+ BatchCheck(lock);
+}
+
+} // namespace GeckoViewStreamingTelemetry
diff --git a/toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.h b/toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.h
new file mode 100644
index 0000000000..458224a3c2
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.h
@@ -0,0 +1,55 @@
+/* -*- 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 GeckoViewStreamingTelemetry_h__
+#define GeckoViewStreamingTelemetry_h__
+
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+
+#include <cstdint>
+
+namespace GeckoViewStreamingTelemetry {
+
+void HistogramAccumulate(const nsCString& aName, bool aIsCategorical,
+ uint32_t aValue);
+
+void BoolScalarSet(const nsCString& aName, bool aValue);
+void StringScalarSet(const nsCString& aName, const nsCString& aValue);
+void UintScalarSet(const nsCString& aName, uint32_t aValue);
+
+// Classes wishing to receive Streaming Telemetry must implement this interface
+// and register themselves via RegisterDelegate.
+class StreamingTelemetryDelegate {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StreamingTelemetryDelegate)
+
+ // Receive* methods will be called from time to time on the main thread.
+ virtual void ReceiveHistogramSamples(const nsCString& aName,
+ const nsTArray<uint32_t>& aSamples) = 0;
+ virtual void ReceiveCategoricalHistogramSamples(
+ const nsCString& aName, const nsTArray<uint32_t>& aSamples) = 0;
+ virtual void ReceiveBoolScalarValue(const nsCString& aName, bool aValue) = 0;
+ virtual void ReceiveStringScalarValue(const nsCString& aName,
+ const nsCString& aValue) = 0;
+ virtual void ReceiveUintScalarValue(const nsCString& aName,
+ uint32_t aValue) = 0;
+
+ protected:
+ virtual ~StreamingTelemetryDelegate() = default;
+};
+
+// Registers the provided StreamingTelemetryDelegate to receive Streaming
+// Telemetry, overwriting any previous delegate registration.
+// Call on any thread.
+void RegisterDelegate(const RefPtr<StreamingTelemetryDelegate>& aDelegate);
+
+} // namespace GeckoViewStreamingTelemetry
+
+#endif // GeckoViewStreamingTelemetry_h__
diff --git a/toolkit/components/telemetry/geckoview/streaming/metrics.yaml b/toolkit/components/telemetry/geckoview/streaming/metrics.yaml
new file mode 100644
index 0000000000..2386fb2171
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/streaming/metrics.yaml
@@ -0,0 +1,13 @@
+# 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/.
+
+# This file defines the metrics that are recorded by the Glean SDK. They are
+# automatically converted to platform-specific code at build time using the
+# `glean_parser` PyPI package.
+
+# Adding a new metric? Please don't!
+# (At least not without the permission of a Telemetry Module Peer)
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0