diff options
Diffstat (limited to 'toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.cpp')
-rw-r--r-- | toolkit/components/telemetry/geckoview/streaming/GeckoViewStreamingTelemetry.cpp | 282 |
1 files changed, 282 insertions, 0 deletions
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 |