diff options
Diffstat (limited to 'toolkit/components/telemetry/core/ipc')
5 files changed, 981 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/core/ipc/TelemetryComms.h b/toolkit/components/telemetry/core/ipc/TelemetryComms.h new file mode 100644 index 0000000000..75f59209b0 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryComms.h @@ -0,0 +1,400 @@ +/* -*- 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 Telemetry_Comms_h__ +#define Telemetry_Comms_h__ + +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" +#include "nsITelemetry.h" + +namespace mozilla { +namespace Telemetry { + +// Histogram accumulation types. +enum HistogramID : uint32_t; + +struct HistogramAccumulation { + mozilla::Telemetry::HistogramID mId; + uint32_t mSample; +}; + +struct KeyedHistogramAccumulation { + mozilla::Telemetry::HistogramID mId; + uint32_t mSample; + nsCString mKey; +}; + +// Scalar accumulation types. +enum class ScalarID : uint32_t; + +enum class ScalarActionType : uint32_t { eSet = 0, eAdd = 1, eSetMaximum = 2 }; + +typedef mozilla::Variant<uint32_t, bool, nsString> ScalarVariant; + +struct ScalarAction { + uint32_t mId; + bool mDynamic; + ScalarActionType mActionType; + // We need to wrap mData in a Maybe otherwise the IPC system + // is unable to instantiate a ScalarAction. + Maybe<ScalarVariant> mData; + // The process type this scalar should be recorded for. + // The IPC system will determine the process this action was coming from + // later. + mozilla::Telemetry::ProcessID mProcessType; +}; + +struct KeyedScalarAction { + uint32_t mId; + bool mDynamic; + ScalarActionType mActionType; + nsCString mKey; + // We need to wrap mData in a Maybe otherwise the IPC system + // is unable to instantiate a ScalarAction. + Maybe<ScalarVariant> mData; + // The process type this scalar should be recorded for. + // The IPC system will determine the process this action was coming from + // later. + mozilla::Telemetry::ProcessID mProcessType; +}; + +// Dynamic scalars support. +struct DynamicScalarDefinition { + uint32_t type; + uint32_t dataset; + bool expired; + bool keyed; + bool builtin; + nsCString name; + + bool operator==(const DynamicScalarDefinition& rhs) const { + return type == rhs.type && dataset == rhs.dataset && + expired == rhs.expired && keyed == rhs.keyed && + builtin == rhs.builtin && name.Equals(rhs.name); + } +}; + +struct ChildEventData { + mozilla::TimeStamp timestamp; + nsCString category; + nsCString method; + nsCString object; + mozilla::Maybe<nsCString> value; + CopyableTArray<EventExtraEntry> extra; +}; + +struct DiscardedData { + uint32_t mDiscardedHistogramAccumulations; + uint32_t mDiscardedKeyedHistogramAccumulations; + uint32_t mDiscardedScalarActions; + uint32_t mDiscardedKeyedScalarActions; + uint32_t mDiscardedChildEvents; +}; + +} // namespace Telemetry +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::Telemetry::HistogramAccumulation> { + typedef mozilla::Telemetry::HistogramAccumulation paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aWriter->WriteUInt32(aParam.mId); + WriteParam(aWriter, aParam.mSample); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!aReader->ReadUInt32(reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aReader, &(aResult->mSample))) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::KeyedHistogramAccumulation> { + typedef mozilla::Telemetry::KeyedHistogramAccumulation paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aWriter->WriteUInt32(aParam.mId); + WriteParam(aWriter, aParam.mSample); + WriteParam(aWriter, aParam.mKey); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!aReader->ReadUInt32(reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aReader, &(aResult->mSample)) || + !ReadParam(aReader, &(aResult->mKey))) { + return false; + } + + return true; + } +}; + +/** + * IPC scalar data message serialization and de-serialization. + */ +template <> +struct ParamTraits<mozilla::Telemetry::ScalarAction> { + typedef mozilla::Telemetry::ScalarAction paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + // Write the message type + aWriter->WriteUInt32(aParam.mId); + WriteParam(aWriter, aParam.mDynamic); + WriteParam(aWriter, static_cast<uint32_t>(aParam.mActionType)); + + if (aParam.mData.isNothing()) { + MOZ_CRASH("There is no data in the ScalarAction."); + return; + } + + if (aParam.mData->is<uint32_t>()) { + // That's a nsITelemetry::SCALAR_TYPE_COUNT. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_COUNT)); + WriteParam(aWriter, aParam.mData->as<uint32_t>()); + } else if (aParam.mData->is<nsString>()) { + // That's a nsITelemetry::SCALAR_TYPE_STRING. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_STRING)); + WriteParam(aWriter, aParam.mData->as<nsString>()); + } else if (aParam.mData->is<bool>()) { + // That's a nsITelemetry::SCALAR_TYPE_BOOLEAN. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_BOOLEAN)); + WriteParam(aWriter, aParam.mData->as<bool>()); + } else { + MOZ_CRASH("Unknown scalar type."); + } + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + // Read the scalar ID and the scalar type. + uint32_t scalarType = 0; + if (!aReader->ReadUInt32(reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->mDynamic))) || + !ReadParam(aReader, + reinterpret_cast<uint32_t*>(&(aResult->mActionType))) || + !ReadParam(aReader, &scalarType)) { + return false; + } + + // De-serialize the data based on the scalar type. + switch (scalarType) { + case nsITelemetry::SCALAR_TYPE_COUNT: { + uint32_t data = 0; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + case nsITelemetry::SCALAR_TYPE_STRING: { + nsString data; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + case nsITelemetry::SCALAR_TYPE_BOOLEAN: { + bool data = false; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + default: + MOZ_ASSERT(false, "Unknown scalar type."); + return false; + } + + return true; + } +}; + +/** + * IPC keyed scalar data message serialization and de-serialization. + */ +template <> +struct ParamTraits<mozilla::Telemetry::KeyedScalarAction> { + typedef mozilla::Telemetry::KeyedScalarAction paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + // Write the message type + aWriter->WriteUInt32(static_cast<uint32_t>(aParam.mId)); + WriteParam(aWriter, aParam.mDynamic); + WriteParam(aWriter, static_cast<uint32_t>(aParam.mActionType)); + WriteParam(aWriter, aParam.mKey); + + if (aParam.mData.isNothing()) { + MOZ_CRASH("There is no data in the KeyedScalarAction."); + return; + } + + if (aParam.mData->is<uint32_t>()) { + // That's a nsITelemetry::SCALAR_TYPE_COUNT. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_COUNT)); + WriteParam(aWriter, aParam.mData->as<uint32_t>()); + } else if (aParam.mData->is<nsString>()) { + // That's a nsITelemetry::SCALAR_TYPE_STRING. + // Keyed string scalars are not supported. + MOZ_ASSERT(false, + "Keyed String Scalar unable to be write from child process. " + "Not supported."); + } else if (aParam.mData->is<bool>()) { + // That's a nsITelemetry::SCALAR_TYPE_BOOLEAN. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_BOOLEAN)); + WriteParam(aWriter, aParam.mData->as<bool>()); + } else { + MOZ_CRASH("Unknown keyed scalar type."); + } + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + // Read the scalar ID and the scalar type. + uint32_t scalarType = 0; + if (!aReader->ReadUInt32(reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->mDynamic))) || + !ReadParam(aReader, + reinterpret_cast<uint32_t*>(&(aResult->mActionType))) || + !ReadParam(aReader, &(aResult->mKey)) || + !ReadParam(aReader, &scalarType)) { + return false; + } + + // De-serialize the data based on the scalar type. + switch (scalarType) { + case nsITelemetry::SCALAR_TYPE_COUNT: { + uint32_t data = 0; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + case nsITelemetry::SCALAR_TYPE_STRING: { + // Keyed string scalars are not supported. + MOZ_ASSERT(false, + "Keyed String Scalar unable to be read from child process. " + "Not supported."); + return false; + } + case nsITelemetry::SCALAR_TYPE_BOOLEAN: { + bool data = false; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + default: + MOZ_ASSERT(false, "Unknown keyed scalar type."); + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::DynamicScalarDefinition> { + typedef mozilla::Telemetry::DynamicScalarDefinition paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + nsCString name; + WriteParam(aWriter, aParam.type); + WriteParam(aWriter, aParam.dataset); + WriteParam(aWriter, aParam.expired); + WriteParam(aWriter, aParam.keyed); + WriteParam(aWriter, aParam.builtin); + WriteParam(aWriter, aParam.name); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, reinterpret_cast<uint32_t*>(&(aResult->type))) || + !ReadParam(aReader, reinterpret_cast<uint32_t*>(&(aResult->dataset))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->expired))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->keyed))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->builtin))) || + !ReadParam(aReader, &(aResult->name))) { + return false; + } + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::ChildEventData> { + typedef mozilla::Telemetry::ChildEventData paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.timestamp); + WriteParam(aWriter, aParam.category); + WriteParam(aWriter, aParam.method); + WriteParam(aWriter, aParam.object); + WriteParam(aWriter, aParam.value); + WriteParam(aWriter, aParam.extra); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->timestamp)) || + !ReadParam(aReader, &(aResult->category)) || + !ReadParam(aReader, &(aResult->method)) || + !ReadParam(aReader, &(aResult->object)) || + !ReadParam(aReader, &(aResult->value)) || + !ReadParam(aReader, &(aResult->extra))) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::EventExtraEntry> { + typedef mozilla::Telemetry::EventExtraEntry paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.key); + WriteParam(aWriter, aParam.value); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->key)) || + !ReadParam(aReader, &(aResult->value))) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::DiscardedData> + : public PlainOldDataSerializer<mozilla::Telemetry::DiscardedData> {}; + +} // namespace IPC + +#endif // Telemetry_Comms_h__ diff --git a/toolkit/components/telemetry/core/ipc/TelemetryIPC.cpp b/toolkit/components/telemetry/core/ipc/TelemetryIPC.cpp new file mode 100644 index 0000000000..daeaeece65 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryIPC.cpp @@ -0,0 +1,59 @@ +/* -*- 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 "TelemetryIPC.h" +#include "../TelemetryScalar.h" +#include "../TelemetryHistogram.h" +#include "../TelemetryEvent.h" + +namespace mozilla { + +void TelemetryIPC::AccumulateChildHistograms( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::HistogramAccumulation>& aAccumulations) { + TelemetryHistogram::AccumulateChild(aProcessType, aAccumulations); +} + +void TelemetryIPC::AccumulateChildKeyedHistograms( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::KeyedHistogramAccumulation>& aAccumulations) { + TelemetryHistogram::AccumulateChildKeyed(aProcessType, aAccumulations); +} + +void TelemetryIPC::UpdateChildScalars( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::ScalarAction>& aScalarActions) { + TelemetryScalar::UpdateChildData(aProcessType, aScalarActions); +} + +void TelemetryIPC::UpdateChildKeyedScalars( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::KeyedScalarAction>& aScalarActions) { + TelemetryScalar::UpdateChildKeyedData(aProcessType, aScalarActions); +} + +void TelemetryIPC::GetDynamicScalarDefinitions( + nsTArray<mozilla::Telemetry::DynamicScalarDefinition>& aDefs) { + TelemetryScalar::GetDynamicScalarDefinitions(aDefs); +} + +void TelemetryIPC::AddDynamicScalarDefinitions( + const nsTArray<mozilla::Telemetry::DynamicScalarDefinition>& aDefs) { + TelemetryScalar::AddDynamicScalarDefinitions(aDefs); +} + +void TelemetryIPC::RecordChildEvents( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::ChildEventData>& aEvents) { + TelemetryEvent::RecordChildEvents(aProcessType, aEvents); +} + +void TelemetryIPC::RecordDiscardedData( + Telemetry::ProcessID aProcessType, + const Telemetry::DiscardedData& aDiscardedData) { + TelemetryScalar::RecordDiscardedData(aProcessType, aDiscardedData); +} +} // namespace mozilla diff --git a/toolkit/components/telemetry/core/ipc/TelemetryIPC.h b/toolkit/components/telemetry/core/ipc/TelemetryIPC.h new file mode 100644 index 0000000000..bf45280059 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryIPC.h @@ -0,0 +1,114 @@ +/* -*- 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 TelemetryIPC_h__ +#define TelemetryIPC_h__ + +#include "mozilla/TelemetryProcessEnums.h" +#include "nsTArray.h" + +// This module provides the interface to accumulate Telemetry from child +// processes. Top-level actors for different child processes types +// (ContentParent, GPUChild) will call this for messages from their respective +// processes. + +namespace mozilla { + +namespace Telemetry { + +struct HistogramAccumulation; +struct KeyedHistogramAccumulation; +struct ScalarAction; +struct KeyedScalarAction; +struct DynamicScalarDefinition; +struct ChildEventData; +struct DiscardedData; + +} // namespace Telemetry + +namespace TelemetryIPC { + +/** + * Accumulate child process data into histograms for the given process type. + * + * @param aProcessType - the process type to accumulate the histograms for + * @param aAccumulations - accumulation actions to perform + */ +void AccumulateChildHistograms( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::HistogramAccumulation>& aAccumulations); + +/** + * Accumulate child process data into keyed histograms for the given process + * type. + * + * @param aProcessType - the process type to accumulate the keyed histograms for + * @param aAccumulations - accumulation actions to perform + */ +void AccumulateChildKeyedHistograms( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::KeyedHistogramAccumulation>& aAccumulations); + +/** + * Update scalars for the given process type with the data coming from child + * process. + * + * @param aProcessType - the process type to process the scalar actions for + * @param aScalarActions - actions to update the scalar data + */ +void UpdateChildScalars( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::ScalarAction>& aScalarActions); + +/** + * Update keyed scalars for the given process type with the data coming from + * child process. + * + * @param aProcessType - the process type to process the keyed scalar actions + * for + * @param aScalarActions - actions to update the keyed scalar data + */ +void UpdateChildKeyedScalars( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::KeyedScalarAction>& aScalarActions); + +/** + * Record events for the given process type with the data coming from child + * process. + * + * @param aProcessType - the process type to record the events for + * @param aEvents - events to record + */ +void RecordChildEvents(Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::ChildEventData>& aEvents); + +/** + * Record the counts of data the child process had to discard + * + * @param aProcessType - the process reporting the discarded data + * @param aDiscardedData - stats about the discarded data + */ +void RecordDiscardedData(Telemetry::ProcessID aProcessType, + const Telemetry::DiscardedData& aDiscardedData); + +/** + * Get the dynamic scalar definitions from the parent process. + * @param aDefs - The array that will contain the scalar definitions. + */ +void GetDynamicScalarDefinitions( + nsTArray<mozilla::Telemetry::DynamicScalarDefinition>& aDefs); + +/** + * Add the dynamic scalar definitions coming from the parent process + * to the current child process. + * @param aDefs - The array that contains the scalar definitions. + */ +void AddDynamicScalarDefinitions( + const nsTArray<mozilla::Telemetry::DynamicScalarDefinition>& aDefs); + +} // namespace TelemetryIPC +} // namespace mozilla + +#endif // TelemetryIPC_h__ diff --git a/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.cpp b/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.cpp new file mode 100644 index 0000000000..f645edd27d --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.cpp @@ -0,0 +1,352 @@ +/* -*- 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 "TelemetryIPCAccumulator.h" + +#include "core/TelemetryScalar.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/RDDParent.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/ipc/UtilityProcessChild.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_toolkit.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" + +using mozilla::StaticAutoPtr; +using mozilla::StaticMutex; +using mozilla::StaticMutexAutoLock; +using mozilla::TaskCategory; +using mozilla::Telemetry::ChildEventData; +using mozilla::Telemetry::DiscardedData; +using mozilla::Telemetry::HistogramAccumulation; +using mozilla::Telemetry::KeyedHistogramAccumulation; +using mozilla::Telemetry::KeyedScalarAction; +using mozilla::Telemetry::ScalarAction; +using mozilla::Telemetry::ScalarActionType; +using mozilla::Telemetry::ScalarVariant; + +namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator; + +// To stop growing unbounded in memory while waiting for +// StaticPrefs::toolkit_telemetry_ipcBatchTimeout() milliseconds to drain the +// probe accumulation arrays, we request an immediate flush if the arrays +// manage to reach certain high water mark of elements. +const size_t kHistogramAccumulationsArrayHighWaterMark = 5 * 1024; +const size_t kScalarActionsArrayHighWaterMark = 10000; +// With the current limits, events cost us about 1100 bytes each. +// This limits memory use to about 10MB. +const size_t kEventsArrayHighWaterMark = 10000; +// If we are starved we can overshoot the watermark. +// This is the multiplier over which we will discard data. +const size_t kWaterMarkDiscardFactor = 5; + +// Counts of how many pieces of data we have discarded. +DiscardedData gDiscardedData = {0}; + +// This timer is used for batching and sending child process accumulations to +// the parent. +nsITimer* gIPCTimer = nullptr; +mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArmed(false); +mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArming(false); + +// This batches child process accumulations that should be sent to the parent. +StaticAutoPtr<nsTArray<HistogramAccumulation>> gHistogramAccumulations; +StaticAutoPtr<nsTArray<KeyedHistogramAccumulation>> + gKeyedHistogramAccumulations; +StaticAutoPtr<nsTArray<ScalarAction>> gChildScalarsActions; +StaticAutoPtr<nsTArray<KeyedScalarAction>> gChildKeyedScalarsActions; +StaticAutoPtr<nsTArray<ChildEventData>> gChildEvents; + +// This is a StaticMutex rather than a plain Mutex so that (1) +// it gets initialised in a thread-safe manner the first time +// it is used, and (2) because it is never de-initialised, and +// a normal Mutex would show up as a leak in BloatView. StaticMutex +// also has the "OffTheBooks" property, so it won't show as a leak +// in BloatView. +static StaticMutex gTelemetryIPCAccumulatorMutex MOZ_UNANNOTATED; + +namespace { + +void DoArmIPCTimerMainThread(const StaticMutexAutoLock& lock) { + MOZ_ASSERT(NS_IsMainThread()); + gIPCTimerArming = false; + if (gIPCTimerArmed) { + return; + } + if (!gIPCTimer) { + gIPCTimer = NS_NewTimer().take(); + } + if (gIPCTimer) { + gIPCTimer->InitWithNamedFuncCallback( + TelemetryIPCAccumulator::IPCTimerFired, nullptr, + mozilla::StaticPrefs::toolkit_telemetry_ipcBatchTimeout(), + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, + "TelemetryIPCAccumulator::IPCTimerFired"); + gIPCTimerArmed = true; + } +} + +void ArmIPCTimer(const StaticMutexAutoLock& lock) { + if (gIPCTimerArmed || gIPCTimerArming) { + return; + } + gIPCTimerArming = true; + if (NS_IsMainThread()) { + DoArmIPCTimerMainThread(lock); + } else { + TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction( + "TelemetryIPCAccumulator::ArmIPCTimer", []() -> void { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + DoArmIPCTimerMainThread(locker); + })); + } +} + +void DispatchIPCTimerFired() { + TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction( + "TelemetryIPCAccumulator::IPCTimerFired", []() -> void { + TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr); + })); +} + +} // anonymous namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryIPCAccumulator:: + +void TelemetryIPCAccumulator::AccumulateChildHistogram( + mozilla::Telemetry::HistogramID aId, uint32_t aSample) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + if (!gHistogramAccumulations) { + gHistogramAccumulations = new nsTArray<HistogramAccumulation>(); + } + if (gHistogramAccumulations->Length() >= + kWaterMarkDiscardFactor * kHistogramAccumulationsArrayHighWaterMark) { + gDiscardedData.mDiscardedHistogramAccumulations++; + return; + } + if (gHistogramAccumulations->Length() == + kHistogramAccumulationsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + gHistogramAccumulations->AppendElement(HistogramAccumulation{aId, aSample}); + ArmIPCTimer(locker); +} + +void TelemetryIPCAccumulator::AccumulateChildKeyedHistogram( + mozilla::Telemetry::HistogramID aId, const nsCString& aKey, + uint32_t aSample) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + if (!gKeyedHistogramAccumulations) { + gKeyedHistogramAccumulations = new nsTArray<KeyedHistogramAccumulation>(); + } + if (gKeyedHistogramAccumulations->Length() >= + kWaterMarkDiscardFactor * kHistogramAccumulationsArrayHighWaterMark) { + gDiscardedData.mDiscardedKeyedHistogramAccumulations++; + return; + } + if (gKeyedHistogramAccumulations->Length() == + kHistogramAccumulationsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + gKeyedHistogramAccumulations->AppendElement( + KeyedHistogramAccumulation{aId, aSample, aKey}); + ArmIPCTimer(locker); +} + +void TelemetryIPCAccumulator::RecordChildScalarAction( + uint32_t aId, bool aDynamic, ScalarActionType aAction, + const ScalarVariant& aValue) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + // Make sure to have the storage. + if (!gChildScalarsActions) { + gChildScalarsActions = new nsTArray<ScalarAction>(); + } + if (gChildScalarsActions->Length() >= + kWaterMarkDiscardFactor * kScalarActionsArrayHighWaterMark) { + gDiscardedData.mDiscardedScalarActions++; + return; + } + if (gChildScalarsActions->Length() == kScalarActionsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + // Store the action. The ProcessID will be determined by the receiver. + gChildScalarsActions->AppendElement(ScalarAction{ + aId, aDynamic, aAction, Some(aValue), Telemetry::ProcessID::Count}); + ArmIPCTimer(locker); +} + +void TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + uint32_t aId, bool aDynamic, const nsAString& aKey, + ScalarActionType aAction, const ScalarVariant& aValue) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + // Make sure to have the storage. + if (!gChildKeyedScalarsActions) { + gChildKeyedScalarsActions = new nsTArray<KeyedScalarAction>(); + } + if (gChildKeyedScalarsActions->Length() >= + kWaterMarkDiscardFactor * kScalarActionsArrayHighWaterMark) { + gDiscardedData.mDiscardedKeyedScalarActions++; + return; + } + if (gChildKeyedScalarsActions->Length() == kScalarActionsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + // Store the action. The ProcessID will be determined by the receiver. + gChildKeyedScalarsActions->AppendElement( + KeyedScalarAction{aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey), + Some(aValue), Telemetry::ProcessID::Count}); + ArmIPCTimer(locker); +} + +void TelemetryIPCAccumulator::RecordChildEvent( + const mozilla::TimeStamp& timestamp, const nsACString& category, + const nsACString& method, const nsACString& object, + const mozilla::Maybe<nsCString>& value, + const nsTArray<mozilla::Telemetry::EventExtraEntry>& extra) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + + if (!gChildEvents) { + gChildEvents = new nsTArray<ChildEventData>(); + } + + if (gChildEvents->Length() >= + kWaterMarkDiscardFactor * kEventsArrayHighWaterMark) { + gDiscardedData.mDiscardedChildEvents++; + return; + } + + if (gChildEvents->Length() == kEventsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + + // Store the event. + gChildEvents->AppendElement( + ChildEventData{timestamp, nsCString(category), nsCString(method), + nsCString(object), value, extra.Clone()}); + ArmIPCTimer(locker); +} + +// This method takes the lock only to double-buffer the batched telemetry. +// It releases the lock before calling out to IPC code which can (and does) +// Accumulate (which would deadlock) +template <class TActor> +static void SendAccumulatedData(TActor* ipcActor) { + // Get the accumulated data and free the storage buffers. + nsTArray<HistogramAccumulation> histogramsToSend; + nsTArray<KeyedHistogramAccumulation> keyedHistogramsToSend; + nsTArray<ScalarAction> scalarsToSend; + nsTArray<KeyedScalarAction> keyedScalarsToSend; + nsTArray<ChildEventData> eventsToSend; + DiscardedData discardedData; + + { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + if (gHistogramAccumulations) { + histogramsToSend = std::move(*gHistogramAccumulations); + } + if (gKeyedHistogramAccumulations) { + keyedHistogramsToSend = std::move(*gKeyedHistogramAccumulations); + } + if (gChildScalarsActions) { + scalarsToSend = std::move(*gChildScalarsActions); + } + if (gChildKeyedScalarsActions) { + keyedScalarsToSend = std::move(*gChildKeyedScalarsActions); + } + if (gChildEvents) { + eventsToSend = std::move(*gChildEvents); + } + discardedData = gDiscardedData; + gDiscardedData = {0}; + } + + // Send the accumulated data to the parent process. + MOZ_ASSERT(ipcActor); + if (histogramsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendAccumulateChildHistograms(histogramsToSend)); + } + if (keyedHistogramsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendAccumulateChildKeyedHistograms(keyedHistogramsToSend)); + } + if (scalarsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendUpdateChildScalars(scalarsToSend)); + } + if (keyedScalarsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendUpdateChildKeyedScalars(keyedScalarsToSend)); + } + if (eventsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendRecordChildEvents(eventsToSend)); + } + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendRecordDiscardedData(discardedData)); +} + +// To ensure we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't +// unset gIPCTimerArmed until the IPC completes +// +// This function must be called on the main thread, otherwise IPC will fail. +void TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure) { + MOZ_ASSERT(NS_IsMainThread()); + + // Send accumulated data to the correct parent process. + switch (XRE_GetProcessType()) { + case GeckoProcessType_Content: + SendAccumulatedData(mozilla::dom::ContentChild::GetSingleton()); + break; + case GeckoProcessType_GPU: + SendAccumulatedData(mozilla::gfx::GPUParent::GetSingleton()); + break; + case GeckoProcessType_RDD: + SendAccumulatedData(mozilla::RDDParent::GetSingleton()); + break; + case GeckoProcessType_Socket: + SendAccumulatedData(mozilla::net::SocketProcessChild::GetSingleton()); + break; + case GeckoProcessType_Utility: + SendAccumulatedData( + mozilla::ipc::UtilityProcessChild::GetSingleton().get()); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported process type"); + break; + } + + gIPCTimerArmed = false; +} + +void TelemetryIPCAccumulator::DeInitializeGlobalState() { + MOZ_ASSERT(NS_IsMainThread()); + + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + if (gIPCTimer) { + NS_RELEASE(gIPCTimer); + } + + gHistogramAccumulations = nullptr; + gKeyedHistogramAccumulations = nullptr; + gChildScalarsActions = nullptr; + gChildKeyedScalarsActions = nullptr; + gChildEvents = nullptr; +} + +void TelemetryIPCAccumulator::DispatchToMainThread( + already_AddRefed<nsIRunnable>&& aEvent) { + SchedulerGroup::Dispatch(TaskCategory::Other, std::move(aEvent)); +} diff --git a/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.h b/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.h new file mode 100644 index 0000000000..e77ca95391 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.h @@ -0,0 +1,56 @@ +/* -*- 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 TelemetryIPCAccumulator_h__ +#define TelemetryIPCAccumulator_h__ + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Maybe.h" +#include "nsStringFwd.h" +#include "mozilla/Telemetry.h" // for EventExtraEntry +#include "mozilla/TelemetryComms.h" // for ScalarActionType, Scala... +#include "mozilla/TelemetryHistogramEnums.h" // for HistogramID + +class nsIRunnable; +class nsITimer; + +namespace mozilla { + +class TimeStamp; + +namespace TelemetryIPCAccumulator { + +// Histogram accumulation functions. +void AccumulateChildHistogram(mozilla::Telemetry::HistogramID aId, + uint32_t aSample); +void AccumulateChildKeyedHistogram(mozilla::Telemetry::HistogramID aId, + const nsCString& aKey, uint32_t aSample); + +// Scalar accumulation functions. +void RecordChildScalarAction(uint32_t aId, bool aDynamic, + mozilla::Telemetry::ScalarActionType aAction, + const mozilla::Telemetry::ScalarVariant& aValue); + +void RecordChildKeyedScalarAction( + uint32_t aId, bool aDynamic, const nsAString& aKey, + mozilla::Telemetry::ScalarActionType aAction, + const mozilla::Telemetry::ScalarVariant& aValue); + +void RecordChildEvent( + const mozilla::TimeStamp& timestamp, const nsACString& category, + const nsACString& method, const nsACString& object, + const mozilla::Maybe<nsCString>& value, + const nsTArray<mozilla::Telemetry::EventExtraEntry>& extra); + +void IPCTimerFired(nsITimer* aTimer, void* aClosure); + +void DeInitializeGlobalState(); + +void DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent); + +} // namespace TelemetryIPCAccumulator +} // namespace mozilla + +#endif // TelemetryIPCAccumulator_h__ |