Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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