From 932126d970464835a0e60a07af08d73a312e5bd6 Mon Sep 17 00:00:00 2001 From: Alessio Placitelli Date: Mon, 16 Jan 2017 05:12:00 +0100 Subject: [PATCH] Bug 1278556 - Enable child process scalar recording. r=chutten, f=gfritzsche,froydnj MozReview-Commit-ID: IddrszBX0f5 --- dom/ipc/ContentParent.cpp | 16 + dom/ipc/ContentParent.h | 4 + dom/ipc/PContent.ipdl | 4 + gfx/ipc/GPUChild.cpp | 14 + gfx/ipc/GPUChild.h | 2 + gfx/ipc/PGPU.ipdl | 4 + toolkit/components/telemetry/Telemetry.cpp | 18 +- toolkit/components/telemetry/Telemetry.h | 16 + toolkit/components/telemetry/TelemetryComms.h | 240 ++++++ .../telemetry/TelemetryIPCAccumulator.cpp | 61 ++ .../telemetry/TelemetryIPCAccumulator.h | 12 + .../components/telemetry/TelemetryScalar.cpp | 795 ++++++++++++++---- .../components/telemetry/TelemetryScalar.h | 6 + toolkit/components/telemetry/nsITelemetry.idl | 4 +- 14 files changed, 1017 insertions(+), 179 deletions(-) diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index d1afbc382490..3238954db3a4 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -4765,6 +4765,22 @@ ContentParent::RecvAccumulateChildKeyedHistogram( return IPC_OK(); } +mozilla::ipc::IPCResult +ContentParent::RecvUpdateChildScalars( + InfallibleTArray&& aScalarActions) +{ + Telemetry::UpdateChildScalars(GeckoProcessType_Content, aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvUpdateChildKeyedScalars( + InfallibleTArray&& aScalarActions) +{ + Telemetry::UpdateChildKeyedScalars(GeckoProcessType_Content, aScalarActions); + return IPC_OK(); +} + PURLClassifierParent* ContentParent::AllocPURLClassifierParent(const Principal& aPrincipal, const bool& aUseTrackingProtection, diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 370268cfe677..94cb84fb01c9 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -1082,6 +1082,10 @@ private: InfallibleTArray&& aAccumulations) override; virtual mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistogram( InfallibleTArray&& aAccumulations) override; + virtual mozilla::ipc::IPCResult RecvUpdateChildScalars( + InfallibleTArray&& aScalarActions) override; + virtual mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars( + InfallibleTArray&& aScalarActions) override; public: void SendGetFilesResponseAndForget(const nsID& aID, const GetFilesResponseResult& aResult); diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index b8f7f3a413a8..f918b609402b 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -95,6 +95,8 @@ using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/Comp using struct mozilla::dom::FlyWebPublishOptions from "mozilla/dom/FlyWebPublishOptionsIPCSerializer.h"; using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h"; using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h"; union ChromeRegistryItem { @@ -1150,6 +1152,8 @@ parent: */ async AccumulateChildHistogram(Accumulation[] accumulations); async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations); + async UpdateChildScalars(ScalarAction[] updates); + async UpdateChildKeyedScalars(KeyedScalarAction[] updates); sync GetA11yContentId() returns (uint32_t aContentId); diff --git a/gfx/ipc/GPUChild.cpp b/gfx/ipc/GPUChild.cpp index 549016352473..049b0324f7d3 100644 --- a/gfx/ipc/GPUChild.cpp +++ b/gfx/ipc/GPUChild.cpp @@ -153,6 +153,20 @@ GPUChild::RecvAccumulateChildKeyedHistogram(InfallibleTArray& return IPC_OK(); } +mozilla::ipc::IPCResult +GPUChild::RecvUpdateChildScalars(InfallibleTArray&& aScalarActions) +{ + Telemetry::UpdateChildScalars(GeckoProcessType_GPU, aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +GPUChild::RecvUpdateChildKeyedScalars(InfallibleTArray&& aScalarActions) +{ + Telemetry::UpdateChildKeyedScalars(GeckoProcessType_GPU, aScalarActions); + return IPC_OK(); +} + mozilla::ipc::IPCResult GPUChild::RecvNotifyDeviceReset() { diff --git a/gfx/ipc/GPUChild.h b/gfx/ipc/GPUChild.h index 572520333520..eba92f5aed27 100644 --- a/gfx/ipc/GPUChild.h +++ b/gfx/ipc/GPUChild.h @@ -40,6 +40,8 @@ public: mozilla::ipc::IPCResult RecvInitCrashReporter(Shmem&& shmem) override; mozilla::ipc::IPCResult RecvAccumulateChildHistogram(InfallibleTArray&& aAccumulations) override; mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistogram(InfallibleTArray&& aAccumulations) override; + mozilla::ipc::IPCResult RecvUpdateChildScalars(InfallibleTArray&& aScalarActions) override; + mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(InfallibleTArray&& aScalarActions) override; void ActorDestroy(ActorDestroyReason aWhy) override; mozilla::ipc::IPCResult RecvGraphicsError(const nsCString& aError) override; mozilla::ipc::IPCResult RecvNotifyUiObservers(const nsCString& aTopic) override; diff --git a/gfx/ipc/PGPU.ipdl b/gfx/ipc/PGPU.ipdl index a0c440c20b64..8032eebfb91a 100644 --- a/gfx/ipc/PGPU.ipdl +++ b/gfx/ipc/PGPU.ipdl @@ -17,6 +17,8 @@ using mozilla::gfx::IntSize from "mozilla/gfx/2D.h"; using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h"; using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h"; using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h"; namespace mozilla { namespace gfx { @@ -101,6 +103,8 @@ child: // Messages for reporting telemetry to the UI process. async AccumulateChildHistogram(Accumulation[] accumulations); async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations); + async UpdateChildScalars(ScalarAction[] actions); + async UpdateChildKeyedScalars(KeyedScalarAction[] actions); async NotifyDeviceReset(); }; diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index 9372f03e5d93..aab78903e8b6 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -2132,9 +2132,7 @@ TelemetryImpl::CreateTelemetryInstance() // First, initialize the TelemetryHistogram and TelemetryScalar global states. TelemetryHistogram::InitializeGlobalState(useTelemetry, useTelemetry); - - // Only record scalars from the parent process. - TelemetryScalar::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess()); + TelemetryScalar::InitializeGlobalState(useTelemetry, useTelemetry); // Only record events from the parent process. TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess()); @@ -3107,6 +3105,20 @@ AccumulateChildKeyed(GeckoProcessType aProcessType, TelemetryHistogram::AccumulateChildKeyed(aProcessType, aAccumulations); } +void +UpdateChildScalars(GeckoProcessType aProcessType, + const nsTArray& aScalarActions) +{ + TelemetryScalar::UpdateChildData(aProcessType, aScalarActions); +} + +void +UpdateChildKeyedScalars(GeckoProcessType aProcessType, + const nsTArray& aScalarActions) +{ + TelemetryScalar::UpdateChildKeyedData(aProcessType, aScalarActions); +} + const char* GetHistogramName(ID id) { diff --git a/toolkit/components/telemetry/Telemetry.h b/toolkit/components/telemetry/Telemetry.h index bc9ed751368b..5d2dca82f4f1 100644 --- a/toolkit/components/telemetry/Telemetry.h +++ b/toolkit/components/telemetry/Telemetry.h @@ -36,6 +36,8 @@ namespace Telemetry { struct Accumulation; struct KeyedAccumulation; +struct ScalarAction; +struct KeyedScalarAction; enum TimerResolution { Millisecond, @@ -143,6 +145,20 @@ void AccumulateChild(GeckoProcessType aProcessType, const nsTArray */ void AccumulateChildKeyed(GeckoProcessType aProcessType, const nsTArray& aAccumulations); +/** + * Update scalars for the given process type with the data coming from child process. + * + * @param aScalarActions - actions to update the scalar data + */ +void UpdateChildScalars(GeckoProcessType aProcessType, const nsTArray& aScalarActions); + +/** + * Update keyed scalars for the given process type with the data coming from child process. + * + * @param aScalarActions - actions to update the keyed scalar data + */ +void UpdateChildKeyedScalars(GeckoProcessType aProcessType, const nsTArray& aScalarActions); + /** * Enable/disable recording for this histogram at runtime. * Recording is enabled by default, unless listed at kRecordingInitiallyDisabledIDs[]. diff --git a/toolkit/components/telemetry/TelemetryComms.h b/toolkit/components/telemetry/TelemetryComms.h index 0f2d888e3198..7a3953415be4 100644 --- a/toolkit/components/telemetry/TelemetryComms.h +++ b/toolkit/components/telemetry/TelemetryComms.h @@ -7,10 +7,13 @@ #define Telemetry_Comms_h__ #include "ipc/IPCMessageUtils.h" +#include "nsITelemetry.h" +#include "nsVariant.h" namespace mozilla { namespace Telemetry { +// Histogram accumulation types. enum ID : uint32_t; struct Accumulation @@ -26,6 +29,32 @@ struct KeyedAccumulation nsCString mKey; }; +// Scalar accumulation types. +enum class ScalarID : uint32_t; + +enum class ScalarActionType : uint32_t { + eSet = 0, + eAdd = 1, + eSetMaximum = 2 +}; + +struct ScalarAction +{ + ScalarID mId; + uint32_t mScalarType; + ScalarActionType mActionType; + nsCOMPtr mData; +}; + +struct KeyedScalarAction +{ + ScalarID mId; + uint32_t mScalarType; + ScalarActionType mActionType; + nsCString mKey; + nsCOMPtr mData; +}; + } // namespace Telemetry } // namespace mozilla @@ -79,6 +108,217 @@ ParamTraits } }; +/** + * IPC scalar data message serialization and de-serialization. + */ +template<> +struct +ParamTraits +{ + typedef mozilla::Telemetry::ScalarAction paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + // Write the message type + aMsg->WriteUInt32(static_cast(aParam.mId)); + WriteParam(aMsg, aParam.mScalarType); + WriteParam(aMsg, static_cast(aParam.mActionType)); + + switch(aParam.mScalarType) { + case nsITelemetry::SCALAR_COUNT: + { + uint32_t val = 0; + nsresult rv = aParam.mData->GetAsUint32(&val); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Count Scalar unable to convert variant to bool from child process."); + return; + } + WriteParam(aMsg, val); + break; + } + case nsITelemetry::SCALAR_STRING: + { + nsString val; + nsresult rv = aParam.mData->GetAsAString(val); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Conversion failed."); + return; + } + WriteParam(aMsg, val); + break; + } + case nsITelemetry::SCALAR_BOOLEAN: + { + bool val = 0; + nsresult rv = aParam.mData->GetAsBool(&val); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Boolean Scalar unable to convert variant to bool from child process."); + return; + } + WriteParam(aMsg, val); + break; + } + default: + MOZ_ASSERT(false, "Unknown scalar type."); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + // Read the scalar ID and the scalar type. + if (!aMsg->ReadUInt32(aIter, reinterpret_cast(&(aResult->mId))) || + !ReadParam(aMsg, aIter, &(aResult->mScalarType)) || + !ReadParam(aMsg, aIter, reinterpret_cast(&(aResult->mActionType)))) { + return false; + } + + // De-serialize the data based on the scalar type. + nsCOMPtr outVar(new nsVariant()); + + switch (aResult->mScalarType) + { + case nsITelemetry::SCALAR_COUNT: + { + uint32_t data = 0; + // De-serialize the data. + if (!ReadParam(aMsg, aIter, &data) || + NS_FAILED(outVar->SetAsUint32(data))) { + return false; + } + break; + } + case nsITelemetry::SCALAR_STRING: + { + nsString data; + // De-serialize the data. + if (!ReadParam(aMsg, aIter, &data) || + NS_FAILED(outVar->SetAsAString(data))) { + return false; + } + break; + } + case nsITelemetry::SCALAR_BOOLEAN: + { + bool data = false; + // De-serialize the data. + if (!ReadParam(aMsg, aIter, &data) || + NS_FAILED(outVar->SetAsBool(data))) { + return false; + } + break; + } + default: + MOZ_ASSERT(false, "Unknown scalar type."); + return false; + } + + aResult->mData = outVar.forget(); + return true; + } +}; + +/** + * IPC keyed scalar data message serialization and de-serialization. + */ +template<> +struct +ParamTraits +{ + typedef mozilla::Telemetry::KeyedScalarAction paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + // Write the message type + aMsg->WriteUInt32(static_cast(aParam.mId)); + WriteParam(aMsg, aParam.mScalarType); + WriteParam(aMsg, static_cast(aParam.mActionType)); + WriteParam(aMsg, aParam.mKey); + + switch(aParam.mScalarType) { + case nsITelemetry::SCALAR_COUNT: + { + uint32_t val = 0; + nsresult rv = aParam.mData->GetAsUint32(&val); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Keyed Count Scalar unable to convert variant to uint from child process."); + return; + } + WriteParam(aMsg, val); + break; + } + case nsITelemetry::SCALAR_STRING: + { + // Keyed string scalars are not supported. + MOZ_ASSERT(false, "Keyed String Scalar unable to be write from child process. Not supported."); + break; + } + case nsITelemetry::SCALAR_BOOLEAN: + { + bool val = 0; + nsresult rv = aParam.mData->GetAsBool(&val); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Keyed Boolean Scalar unable to convert variant to bool from child process."); + return; + } + WriteParam(aMsg, val); + break; + } + default: + MOZ_ASSERT(false, "Unknown keyed scalar type."); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + // Read the scalar ID and the scalar type. + if (!aMsg->ReadUInt32(aIter, reinterpret_cast(&(aResult->mId))) || + !ReadParam(aMsg, aIter, &(aResult->mScalarType)) || + !ReadParam(aMsg, aIter, reinterpret_cast(&(aResult->mActionType))) || + !ReadParam(aMsg, aIter, &(aResult->mKey))) { + return false; + } + + // De-serialize the data based on the scalar type. + nsCOMPtr outVar(new nsVariant()); + + switch (aResult->mScalarType) + { + case nsITelemetry::SCALAR_COUNT: + { + uint32_t data = 0; + // De-serialize the data. + if (!ReadParam(aMsg, aIter, &data) || + NS_FAILED(outVar->SetAsUint32(data))) { + return false; + } + break; + } + case nsITelemetry::SCALAR_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_BOOLEAN: + { + bool data = false; + // De-serialize the data. + if (!ReadParam(aMsg, aIter, &data) || + NS_FAILED(outVar->SetAsBool(data))) { + return false; + } + break; + } + default: + MOZ_ASSERT(false, "Unknown keyed scalar type."); + return false; + } + + aResult->mData = outVar.forget(); + return true; + } +}; + } // namespace IPC #endif // Telemetry_Comms_h__ diff --git a/toolkit/components/telemetry/TelemetryIPCAccumulator.cpp b/toolkit/components/telemetry/TelemetryIPCAccumulator.cpp index 81e49842854b..e46ddaf9a1f0 100644 --- a/toolkit/components/telemetry/TelemetryIPCAccumulator.cpp +++ b/toolkit/components/telemetry/TelemetryIPCAccumulator.cpp @@ -16,12 +16,16 @@ #include "nsITimer.h" #include "nsThreadUtils.h" #include "TelemetryHistogram.h" +#include "TelemetryScalar.h" using mozilla::StaticMutex; using mozilla::StaticMutexAutoLock; using mozilla::StaticAutoPtr; using mozilla::Telemetry::Accumulation; using mozilla::Telemetry::KeyedAccumulation; +using mozilla::Telemetry::ScalarActionType; +using mozilla::Telemetry::ScalarAction; +using mozilla::Telemetry::KeyedScalarAction; // Sending each remote accumulation immediately places undue strain on the // IPC subsystem. Batch the remote accumulations for a period of time before @@ -42,6 +46,8 @@ mozilla::Atomic gIPCTimerArming(false); // For batching and sending child process accumulations to the parent StaticAutoPtr> gHistogramAccumulations; StaticAutoPtr> gKeyedHistogramAccumulations; +StaticAutoPtr> gChildScalarsActions; +StaticAutoPtr> gChildKeyedScalarsActions; // This is a StaticMutex rather than a plain Mutex so that (1) // it gets initialised in a thread-safe manner the first time @@ -127,6 +133,36 @@ TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(mozilla::Telemetry::ID aI ArmIPCTimer(locker); } +void +TelemetryIPCAccumulator::RecordChildScalarAction(mozilla::Telemetry::ScalarID aId, uint32_t aKind, + ScalarActionType aAction, nsIVariant* aValue) +{ + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + // Make sure to have the storage. + if (!gChildScalarsActions) { + gChildScalarsActions = new nsTArray(); + } + // Store the action. + gChildScalarsActions->AppendElement(ScalarAction{aId, aKind, aAction, aValue}); + ArmIPCTimer(locker); +} + +void +TelemetryIPCAccumulator::RecordChildKeyedScalarAction(mozilla::Telemetry::ScalarID aId, + const nsAString& aKey, uint32_t aKind, + ScalarActionType aAction, nsIVariant* aValue) +{ + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + // Make sure to have the storage. + if (!gChildKeyedScalarsActions) { + gChildKeyedScalarsActions = new nsTArray(); + } + // Store the action. + gChildKeyedScalarsActions->AppendElement( + KeyedScalarAction{aId, aKind, aAction, NS_ConvertUTF16toUTF8(aKey), aValue}); + 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) @@ -143,6 +179,8 @@ TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure) // Get the accumulated data and free the storage buffer. nsTArray accumulationsToSend; nsTArray keyedAccumulationsToSend; + nsTArray scalarsToSend; + nsTArray keyedScalarsToSend; { StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); if (gHistogramAccumulations) { @@ -151,6 +189,13 @@ TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure) if (gKeyedHistogramAccumulations) { keyedAccumulationsToSend.SwapElements(*gKeyedHistogramAccumulations); } + // Copy the scalar actions. + if (gChildScalarsActions) { + scalarsToSend.SwapElements(*gChildScalarsActions); + } + if (gChildKeyedScalarsActions) { + keyedScalarsToSend.SwapElements(*gChildKeyedScalarsActions); + } } // Send the accumulated data to the parent process. @@ -167,6 +212,14 @@ TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure) mozilla::Unused << NS_WARN_IF(!contentChild->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend)); } + if (scalarsToSend.Length()) { + mozilla::Unused << + NS_WARN_IF(!contentChild->SendUpdateChildScalars(scalarsToSend)); + } + if (keyedScalarsToSend.Length()) { + mozilla::Unused << + NS_WARN_IF(!contentChild->SendUpdateChildKeyedScalars(keyedScalarsToSend)); + } } break; } @@ -178,6 +231,12 @@ TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure) if (keyedAccumulationsToSend.Length()) { mozilla::Unused << gpu->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend); } + if (scalarsToSend.Length()) { + mozilla::Unused << gpu->SendUpdateChildScalars(scalarsToSend); + } + if (keyedScalarsToSend.Length()) { + mozilla::Unused << gpu->SendUpdateChildKeyedScalars(keyedScalarsToSend); + } } break; } @@ -199,6 +258,8 @@ TelemetryIPCAccumulator::DeInitializeGlobalState() gHistogramAccumulations = nullptr; gKeyedHistogramAccumulations = nullptr; + gChildScalarsActions = nullptr; + gChildKeyedScalarsActions = nullptr; } void diff --git a/toolkit/components/telemetry/TelemetryIPCAccumulator.h b/toolkit/components/telemetry/TelemetryIPCAccumulator.h index 52dd78b46f0d..492a626841f8 100644 --- a/toolkit/components/telemetry/TelemetryIPCAccumulator.h +++ b/toolkit/components/telemetry/TelemetryIPCAccumulator.h @@ -10,12 +10,16 @@ class nsIRunnable; class nsITimer; +class nsAString; class nsCString; +class nsIVariant; namespace mozilla { namespace Telemetry { enum ID : uint32_t; +enum class ScalarID : uint32_t; +enum class ScalarActionType : uint32_t; } // Telemetry } // mozilla @@ -27,6 +31,14 @@ void AccumulateChildHistogram(mozilla::Telemetry::ID aId, uint32_t aSample); void AccumulateChildKeyedHistogram(mozilla::Telemetry::ID aId, const nsCString& aKey, uint32_t aSample); +// Scalar accumulation functions. +void RecordChildScalarAction(mozilla::Telemetry::ScalarID aId, uint32_t aKind, + mozilla::Telemetry::ScalarActionType aAction, nsIVariant* aValue); + +void RecordChildKeyedScalarAction(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aKind, mozilla::Telemetry::ScalarActionType aAction, + nsIVariant* aValue); + void IPCTimerFired(nsITimer* aTimer, void* aClosure); void DeInitializeGlobalState(); diff --git a/toolkit/components/telemetry/TelemetryScalar.cpp b/toolkit/components/telemetry/TelemetryScalar.cpp index 47c17f3c4013..826b4ed3484a 100644 --- a/toolkit/components/telemetry/TelemetryScalar.cpp +++ b/toolkit/components/telemetry/TelemetryScalar.cpp @@ -10,13 +10,16 @@ #include "nsHashKeys.h" #include "nsBaseHashtable.h" #include "nsClassHashtable.h" +#include "nsDataHashtable.h" #include "nsIXPConnect.h" #include "nsContentUtils.h" #include "nsThreadUtils.h" #include "mozilla/StaticMutex.h" #include "mozilla/Unused.h" +#include "TelemetryComms.h" #include "TelemetryCommon.h" +#include "TelemetryIPCAccumulator.h" #include "TelemetryScalar.h" #include "TelemetryScalarData.h" @@ -27,6 +30,7 @@ using mozilla::Telemetry::Common::IsExpiredVersion; using mozilla::Telemetry::Common::CanRecordDataset; using mozilla::Telemetry::Common::IsInDataset; using mozilla::Telemetry::Common::LogToBrowserConsole; +using mozilla::Telemetry::ScalarActionType; //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// @@ -79,6 +83,7 @@ enum class ScalarResult : uint8_t { // Nothing went wrong. Ok, // General Scalar Errors + CannotRecordInProcess, OperationNotSupported, InvalidType, InvalidValue, @@ -107,6 +112,7 @@ MapToNsResult(ScalarResult aSr) { switch (aSr) { case ScalarResult::Ok: + case ScalarResult::CannotRecordInProcess: return NS_OK; case ScalarResult::OperationNotSupported: return NS_ERROR_NOT_AVAILABLE; @@ -134,6 +140,46 @@ IsValidEnumId(mozilla::Telemetry::ScalarID aID) return aID < mozilla::Telemetry::ScalarID::ScalarCount; } +/** + * The following helpers are used to get a nsIVariant from a uint32_t, + * nsAString or bool data. + */ +nsresult +GetVariant(uint32_t aValue, nsCOMPtr& aResult) +{ + nsCOMPtr outVar(new nsVariant()); + nsresult rv = outVar->SetAsUint32(aValue); + if (NS_FAILED(rv)) { + return rv; + } + aResult = outVar.forget(); + return NS_OK; +} + +nsresult +GetVariant(const nsAString& aValue, nsCOMPtr& aResult) +{ + nsCOMPtr outVar(new nsVariant()); + nsresult rv = outVar->SetAsAString(aValue); + if (NS_FAILED(rv)) { + return rv; + } + aResult = outVar.forget(); + return NS_OK; +} + +nsresult +GetVariant(bool aValue, nsCOMPtr& aResult) +{ + nsCOMPtr outVar(new nsVariant()); + nsresult rv = outVar->SetAsBool(aValue); + if (NS_FAILED(rv)) { + return rv; + } + aResult = outVar.forget(); + return NS_OK; +} + // Implements the methods for ScalarInfo. const char * ScalarInfo::name() const @@ -288,13 +334,7 @@ ScalarUnsigned::SetMaximum(uint32_t aValue) nsresult ScalarUnsigned::GetValue(nsCOMPtr& aResult) const { - nsCOMPtr outVar(new nsVariant()); - nsresult rv = outVar->SetAsUint32(mStorage); - if (NS_FAILED(rv)) { - return rv; - } - aResult = outVar.forget(); - return NS_OK; + return GetVariant(mStorage, aResult); } size_t @@ -389,13 +429,7 @@ ScalarString::SetValue(const nsAString& aValue) nsresult ScalarString::GetValue(nsCOMPtr& aResult) const { - nsCOMPtr outVar(new nsVariant()); - nsresult rv = outVar->SetAsAString(mStorage); - if (NS_FAILED(rv)) { - return rv; - } - aResult = outVar.forget(); - return NS_OK; + return GetVariant(mStorage, aResult); } size_t @@ -463,13 +497,7 @@ ScalarBoolean::SetValue(bool aValue) nsresult ScalarBoolean::GetValue(nsCOMPtr& aResult) const { - nsCOMPtr outVar(new nsVariant()); - nsresult rv = outVar->SetAsBool(mStorage); - if (NS_FAILED(rv)) { - return rv; - } - aResult = outVar.forget(); - return NS_OK; + return GetVariant(mStorage, aResult); } size_t @@ -706,8 +734,11 @@ KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) } typedef nsUint32HashKey ScalarIDHashKey; +typedef nsUint32HashKey ProcessIDHashKey; typedef nsClassHashtable ScalarStorageMapType; typedef nsClassHashtable KeyedScalarStorageMapType; +typedef nsClassHashtable ProcessesScalarsMapType; +typedef nsClassHashtable ProcessesKeyedScalarsMapType; } // namespace @@ -726,13 +757,13 @@ bool gCanRecordExtended; // The Name -> ID cache map. ScalarMapType gScalarNameIDMap(kScalarCount); -// The ID -> Scalar Object map. This is a nsClassHashtable, it owns -// the scalar instance and takes care of deallocating them when they -// get removed from the map. -ScalarStorageMapType gScalarStorageMap; -// The ID -> Keyed Scalar Object map. As for plain scalars, this is -// nsClassHashtable. See above. -KeyedScalarStorageMapType gKeyedScalarStorageMap; + +// The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a nsClassHashtable, +// it owns the scalar instances and takes care of deallocating them when they are +// removed from the map. +ProcessesScalarsMapType gScalarStorageMap; +// As above, for the keyed scalars. +ProcessesKeyedScalarsMapType gKeyedScalarStorageMap; } // namespace @@ -761,6 +792,7 @@ bool internal_ShouldLogError(ScalarResult aSr) { switch (aSr) { + case ScalarResult::CannotRecordInProcess: MOZ_FALLTHROUGH; case ScalarResult::StringTooLong: MOZ_FALLTHROUGH; case ScalarResult::KeyTooLong: MOZ_FALLTHROUGH; case ScalarResult::TooManyKeys: MOZ_FALLTHROUGH; @@ -791,6 +823,9 @@ internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr) AppendUTF8toUTF16(aScalarName, errorMessage); switch (aSr) { + case ScalarResult::CannotRecordInProcess: + errorMessage.Append(NS_LITERAL_STRING(" - Cannot record the scalar in the current process.")); + break; case ScalarResult::StringTooLong: errorMessage.Append(NS_LITERAL_STRING(" - Truncating scalar value to 50 characters.")); break; @@ -853,6 +888,29 @@ internal_IsKeyedScalar(mozilla::Telemetry::ScalarID aId) return internal_InfoForScalarID(aId).keyed; } +/** + * Check if we're allowed to record the given scalar in the current + * process. + * + * @param aId The id of the scalar to check. + * @return true if the scalar is allowed to be recorded in the current process, false + * otherwise. + */ +bool +internal_CanRecordProcess(mozilla::Telemetry::ScalarID aId) +{ + // Get the scalar info from the id. + const ScalarInfo &info = internal_InfoForScalarID(aId); + + bool recordAllChild = !!(info.record_in_processes & RecordedProcessType::AllChilds); + // We can use (1 << ProcessType) due to the way RecordedProcessType is defined + // in ScalarInfo.h + bool canRecordProcess = + !!(info.record_in_processes & static_cast(1 << XRE_GetProcessType())); + + return canRecordProcess || (!XRE_IsParentProcess() && recordAllChild); +} + bool internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId) { @@ -905,6 +963,11 @@ internal_GetEnumByScalarName(const nsACString& aName, mozilla::Telemetry::Scalar * object in the storage if it wasn't previously allocated. * * @param aId The scalar id. + * @param aProcessStorage This drives the selection of the map to use to store + * the scalar data coming from child processes. This is only meaningful when + * this function is called in parent process. If that's the case, if + * this is not |GeckoProcessType_Default|, the process id is used to + * allocate and store the scalars. * @param aRes The output variable that stores scalar object. * @return * NS_ERROR_INVALID_ARG if the scalar id is unknown. @@ -913,7 +976,8 @@ internal_GetEnumByScalarName(const nsACString& aName, mozilla::Telemetry::Scalar * valid pointer to a scalar type. */ nsresult -internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet) +internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, GeckoProcessType aProcessStorage, + ScalarBase** aRet) { if (!IsValidEnumId(aId)) { MOZ_ASSERT(false, "Requested a scalar with an invalid id."); @@ -921,15 +985,27 @@ internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet) } const uint32_t id = static_cast(aId); + const ScalarInfo &info = gScalars[id]; ScalarBase* scalar = nullptr; - if (gScalarStorageMap.Get(id, &scalar)) { + ScalarStorageMapType* scalarStorage = nullptr; + // Initialize the scalar storage to the parent storage. This will get + // set to the child storage if needed. + uint32_t storageId = static_cast(aProcessStorage); + + // Get the process-specific storage or create one if it's not + // available. + if (!gScalarStorageMap.Get(storageId, &scalarStorage)) { + scalarStorage = new ScalarStorageMapType(); + gScalarStorageMap.Put(storageId, scalarStorage); + } + + // Check if the scalar is already allocated in the parent or in the child storage. + if (scalarStorage->Get(id, &scalar)) { *aRet = scalar; return NS_OK; } - const ScalarInfo &info = gScalars[id]; - if (IsExpiredVersion(info.expiration())) { return NS_ERROR_NOT_AVAILABLE; } @@ -939,8 +1015,7 @@ internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet) return NS_ERROR_INVALID_ARG; } - gScalarStorageMap.Put(id, scalar); - + scalarStorage->Put(id, scalar); *aRet = scalar; return NS_OK; } @@ -957,7 +1032,7 @@ internal_GetRecordableScalar(mozilla::Telemetry::ScalarID aId) { // Get the scalar by the enum (it also internally checks for aId validity). ScalarBase* scalar = nullptr; - nsresult rv = internal_GetScalarByEnum(aId, &scalar); + nsresult rv = internal_GetScalarByEnum(aId, GeckoProcessType_Default, &scalar); if (NS_FAILED(rv)) { return nullptr; } @@ -967,7 +1042,7 @@ internal_GetRecordableScalar(mozilla::Telemetry::ScalarID aId) } // Are we allowed to record this scalar? - if (!internal_CanRecordForScalarID(aId)) { + if (!internal_CanRecordForScalarID(aId) || !internal_CanRecordProcess(aId)) { return nullptr; } @@ -990,7 +1065,12 @@ namespace { * scalar object in the storage if it wasn't previously allocated. * * @param aId The scalar id. - * @param aRes The output variable that stores scalar object. + * @param aProcessStorage This drives the selection of the map to use to store + * the scalar data coming from child processes. This is only meaningful when + * this function is called in parent process. If that's the case, if + * this is not |GeckoProcessType_Default|, the process id is used to + * allocate and store the scalars. + * @param aRet The output variable that stores scalar object. * @return * NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed string * scalar. @@ -999,7 +1079,8 @@ namespace { * valid pointer to a scalar type. */ nsresult -internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, KeyedScalar** aRet) +internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, GeckoProcessType aProcessStorage, + KeyedScalar** aRet) { if (!IsValidEnumId(aId)) { MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id."); @@ -1007,15 +1088,26 @@ internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, KeyedScalar** aR } const uint32_t id = static_cast(aId); + const ScalarInfo &info = gScalars[id]; KeyedScalar* scalar = nullptr; - if (gKeyedScalarStorageMap.Get(id, &scalar)) { + KeyedScalarStorageMapType* scalarStorage = nullptr; + // Initialize the scalar storage to the parent storage. This will get + // set to the child storage if needed. + uint32_t storageId = static_cast(aProcessStorage); + + // Get the process-specific storage or create one if it's not + // available. + if (!gKeyedScalarStorageMap.Get(storageId, &scalarStorage)) { + scalarStorage = new KeyedScalarStorageMapType(); + gKeyedScalarStorageMap.Put(storageId, scalarStorage); + } + + if (scalarStorage->Get(id, &scalar)) { *aRet = scalar; return NS_OK; } - const ScalarInfo &info = gScalars[id]; - if (IsExpiredVersion(info.expiration())) { return NS_ERROR_NOT_AVAILABLE; } @@ -1031,8 +1123,7 @@ internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, KeyedScalar** aR return NS_ERROR_INVALID_ARG; } - gKeyedScalarStorageMap.Put(id, scalar); - + scalarStorage->Put(id, scalar); *aRet = scalar; return NS_OK; } @@ -1049,7 +1140,7 @@ internal_GetRecordableKeyedScalar(mozilla::Telemetry::ScalarID aId) { // Get the scalar by the enum (it also internally checks for aId validity). KeyedScalar* scalar = nullptr; - nsresult rv = internal_GetKeyedScalarByEnum(aId, &scalar); + nsresult rv = internal_GetKeyedScalarByEnum(aId, GeckoProcessType_Default, &scalar); if (NS_FAILED(rv)) { return nullptr; } @@ -1059,7 +1150,7 @@ internal_GetRecordableKeyedScalar(mozilla::Telemetry::ScalarID aId) } // Are we allowed to record this scalar? - if (!internal_CanRecordForScalarID(aId)) { + if (!internal_CanRecordForScalarID(aId) || !internal_CanRecordProcess(aId)) { return nullptr; } @@ -1174,18 +1265,30 @@ TelemetryScalar::Add(const nsACString& aName, JS::HandleValue aVal, JSContext* a return NS_OK; } - // Finally get the scalar. - ScalarBase* scalar = nullptr; - rv = internal_GetScalarByEnum(id, &scalar); - if (NS_FAILED(rv)) { - // Don't throw on expired scalars. - if (rv == NS_ERROR_NOT_AVAILABLE) { + if (internal_CanRecordProcess(id)) { + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + const ScalarInfo &info = gScalars[static_cast(id)]; + TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eAdd, + unpackedVal); return NS_OK; } - return rv; - } - sr = scalar->AddValue(unpackedVal); + // Finally get the scalar. + ScalarBase* scalar = nullptr; + rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar); + if (NS_FAILED(rv)) { + // Don't throw on expired scalars. + if (rv == NS_ERROR_NOT_AVAILABLE) { + return NS_OK; + } + return rv; + } + + sr = scalar->AddValue(unpackedVal); + } else { + sr = ScalarResult::CannotRecordInProcess; + } } // Warn the user about the error if we need to. @@ -1238,18 +1341,30 @@ TelemetryScalar::Add(const nsACString& aName, const nsAString& aKey, JS::HandleV return NS_OK; } - // Finally get the scalar. - KeyedScalar* scalar = nullptr; - rv = internal_GetKeyedScalarByEnum(id, &scalar); - if (NS_FAILED(rv)) { - // Don't throw on expired scalars. - if (rv == NS_ERROR_NOT_AVAILABLE) { + if (internal_CanRecordProcess(id)) { + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + const ScalarInfo &info = gScalars[static_cast(id)]; + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + id, aKey, info.kind, ScalarActionType::eAdd, unpackedVal); return NS_OK; } - return rv; - } - sr = scalar->AddValue(aKey, unpackedVal); + // Finally get the scalar. + KeyedScalar* scalar = nullptr; + rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar); + if (NS_FAILED(rv)) { + // Don't throw on expired scalars. + if (rv == NS_ERROR_NOT_AVAILABLE) { + return NS_OK; + } + return rv; + } + + sr = scalar->AddValue(aKey, unpackedVal); + } else { + sr = ScalarResult::CannotRecordInProcess; + } } // Warn the user about the error if we need to. @@ -1271,6 +1386,19 @@ TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eAdd, + scalarValue); + return; + } + ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; @@ -1292,6 +1420,19 @@ TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + aId, aKey, info.kind, ScalarActionType::eAdd, scalarValue); + return; + } + KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId); if (!scalar) { return; @@ -1340,18 +1481,30 @@ TelemetryScalar::Set(const nsACString& aName, JS::HandleValue aVal, JSContext* a return NS_OK; } - // Finally get the scalar. - ScalarBase* scalar = nullptr; - rv = internal_GetScalarByEnum(id, &scalar); - if (NS_FAILED(rv)) { - // Don't throw on expired scalars. - if (rv == NS_ERROR_NOT_AVAILABLE) { + if (internal_CanRecordProcess(id)) { + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + const ScalarInfo &info = gScalars[static_cast(id)]; + TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eSet, + unpackedVal); return NS_OK; } - return rv; - } - sr = scalar->SetValue(unpackedVal); + // Finally get the scalar. + ScalarBase* scalar = nullptr; + rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar); + if (NS_FAILED(rv)) { + // Don't throw on expired scalars. + if (rv == NS_ERROR_NOT_AVAILABLE) { + return NS_OK; + } + return rv; + } + + sr = scalar->SetValue(unpackedVal); + } else { + sr = ScalarResult::CannotRecordInProcess; + } } // Warn the user about the error if we need to. @@ -1404,18 +1557,30 @@ TelemetryScalar::Set(const nsACString& aName, const nsAString& aKey, JS::HandleV return NS_OK; } - // Finally get the scalar. - KeyedScalar* scalar = nullptr; - rv = internal_GetKeyedScalarByEnum(id, &scalar); - if (NS_FAILED(rv)) { - // Don't throw on expired scalars. - if (rv == NS_ERROR_NOT_AVAILABLE) { + if (internal_CanRecordProcess(id)) { + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + const ScalarInfo &info = gScalars[static_cast(id)]; + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + id, aKey, info.kind, ScalarActionType::eSet, unpackedVal); return NS_OK; } - return rv; - } - sr = scalar->SetValue(aKey, unpackedVal); + // Finally get the scalar. + KeyedScalar* scalar = nullptr; + rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar); + if (NS_FAILED(rv)) { + // Don't throw on expired scalars. + if (rv == NS_ERROR_NOT_AVAILABLE) { + return NS_OK; + } + return rv; + } + + sr = scalar->SetValue(aKey, unpackedVal); + } else { + sr = ScalarResult::CannotRecordInProcess; + } } // Warn the user about the error if we need to. @@ -1437,6 +1602,19 @@ TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet, + scalarValue); + return; + } + ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; @@ -1456,6 +1634,19 @@ TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet, + scalarValue); + return; + } + ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; @@ -1475,6 +1666,19 @@ TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet, + scalarValue); + return; + } + ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; @@ -1496,6 +1700,19 @@ TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + aId, aKey, info.kind, ScalarActionType::eSet, scalarValue); + return; + } + KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId); if (!scalar) { return; @@ -1517,6 +1734,19 @@ TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + aId, aKey, info.kind, ScalarActionType::eSet, scalarValue); + return; + } + KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId); if (!scalar) { return; @@ -1565,18 +1795,30 @@ TelemetryScalar::SetMaximum(const nsACString& aName, JS::HandleValue aVal, JSCon return NS_OK; } - // Finally get the scalar. - ScalarBase* scalar = nullptr; - rv = internal_GetScalarByEnum(id, &scalar); - if (NS_FAILED(rv)) { - // Don't throw on expired scalars. - if (rv == NS_ERROR_NOT_AVAILABLE) { + if (internal_CanRecordProcess(id)) { + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + const ScalarInfo &info = gScalars[static_cast(id)]; + TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eSetMaximum, + unpackedVal); return NS_OK; } - return rv; - } - sr = scalar->SetMaximum(unpackedVal); + // Finally get the scalar. + ScalarBase* scalar = nullptr; + rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar); + if (NS_FAILED(rv)) { + // Don't throw on expired scalars. + if (rv == NS_ERROR_NOT_AVAILABLE) { + return NS_OK; + } + return rv; + } + + sr = scalar->SetMaximum(unpackedVal); + } else { + sr = ScalarResult::CannotRecordInProcess; + } } // Warn the user about the error if we need to. @@ -1629,18 +1871,30 @@ TelemetryScalar::SetMaximum(const nsACString& aName, const nsAString& aKey, JS:: return NS_OK; } - // Finally get the scalar. - KeyedScalar* scalar = nullptr; - rv = internal_GetKeyedScalarByEnum(id, &scalar); - if (NS_FAILED(rv)) { - // Don't throw on expired scalars. - if (rv == NS_ERROR_NOT_AVAILABLE) { + if (internal_CanRecordProcess(id)) { + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + const ScalarInfo &info = gScalars[static_cast(id)]; + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + id, aKey, info.kind, ScalarActionType::eSetMaximum, unpackedVal); return NS_OK; } - return rv; - } - sr = scalar->SetMaximum(aKey, unpackedVal); + // Finally get the scalar. + KeyedScalar* scalar = nullptr; + rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar); + if (NS_FAILED(rv)) { + // Don't throw on expired scalars. + if (rv == NS_ERROR_NOT_AVAILABLE) { + return NS_OK; + } + return rv; + } + + sr = scalar->SetMaximum(aKey, unpackedVal); + } else { + sr = ScalarResult::CannotRecordInProcess; + } } // Warn the user about the error if we need to. @@ -1662,6 +1916,19 @@ TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue) { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSetMaximum, + scalarValue); + return; + } + ScalarBase* scalar = internal_GetRecordableScalar(aId); if (!scalar) { return; @@ -1683,6 +1950,19 @@ TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& a { StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + nsCOMPtr scalarValue; + nsresult rv = GetVariant(aValue, scalarValue); + if (NS_FAILED(rv)) { + return; + } + const ScalarInfo &info = gScalars[static_cast(aId)]; + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + aId, aKey, info.kind, ScalarActionType::eSetMaximum, scalarValue); + return; + } + KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId); if (!scalar) { return; @@ -1693,7 +1973,8 @@ TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& a /** * Serializes the scalars from the given dataset to a json-style object and resets them. - * The returned structure looks like {"group1.probe":1,"group1.other_probe":false,...}. + * The returned structure looks like: + * {"process": {"group1.probe":1,"group1.other_probe":false,...}, ... }. * * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN. * @param aClear Whether to clear out the scalars after snapshotting. @@ -1702,6 +1983,8 @@ nsresult TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx, uint8_t optional_argc, JS::MutableHandle aResult) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Snapshotting scalars should only happen in the parent processes."); // If no arguments were passed in, apply the default value. if (!optional_argc) { aClearScalars = false; @@ -1713,29 +1996,41 @@ TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSCo } aResult.setObject(*root_obj); + // Return `{}` in child processes. + if (!XRE_IsParentProcess()) { + return NS_OK; + } + // Only lock the mutex while accessing our data, without locking any JS related code. typedef mozilla::Pair> DataPair; - nsTArray scalarsToReflect; + typedef nsTArray ScalarArray; + nsDataHashtable scalarsToReflect; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); // Iterate the scalars in gScalarStorageMap. The storage may contain empty or yet to be - // initialized scalars. + // initialized scalars from all the supported processes. for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) { - ScalarBase* scalar = static_cast(iter.Data()); + ScalarStorageMapType* scalarStorage = static_cast(iter.Data()); + ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key()); - // Get the informations for this scalar. - const ScalarInfo& info = gScalars[iter.Key()]; + // Iterate each available child storage. + for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) { + ScalarBase* scalar = static_cast(childIter.Data()); - // Serialize the scalar if it's in the desired dataset. - if (IsInDataset(info.dataset, aDataset)) { - // Get the scalar value. - nsCOMPtr scalarValue; - nsresult rv = scalar->GetValue(scalarValue); - if (NS_FAILED(rv)) { - return rv; + // Get the informations for this scalar. + const ScalarInfo& info = gScalars[childIter.Key()]; + + // Serialize the scalar if it's in the desired dataset. + if (IsInDataset(info.dataset, aDataset)) { + // Get the scalar value. + nsCOMPtr scalarValue; + nsresult rv = scalar->GetValue(scalarValue); + if (NS_FAILED(rv)) { + return rv; + } + // Append it to our list. + processScalars.AppendElement(mozilla::MakePair(info.name(), scalarValue)); } - // Append it to our list. - scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarValue)); } } @@ -1746,20 +2041,34 @@ TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSCo } // Reflect it to JS. - for (nsTArray::size_type i = 0; i < scalarsToReflect.Length(); i++) { - const DataPair& scalar = scalarsToReflect[i]; + for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) { + ScalarArray& processScalars = iter.Data(); + const char* processName = + XRE_ChildProcessTypeToString(static_cast(iter.Key())); - // Convert it to a JS Val. - JS::Rooted scalarJsValue(aCx); - nsresult rv = - nsContentUtils::XPConnect()->VariantToJS(aCx, root_obj, scalar.second(), &scalarJsValue); - if (NS_FAILED(rv)) { - return rv; + // Create the object that will hold the scalars for this process and add it + // to the returned root object. + JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx)); + if (!processObj || + !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; } - // Add it to the scalar object. - if (!JS_DefineProperty(aCx, root_obj, scalar.first(), scalarJsValue, JSPROP_ENUMERATE)) { - return NS_ERROR_FAILURE; + for (nsTArray::size_type i = 0; i < processScalars.Length(); i++) { + const DataPair& scalar = processScalars[i]; + + // Convert it to a JS Val. + JS::Rooted scalarJsValue(aCx); + nsresult rv = + nsContentUtils::XPConnect()->VariantToJS(aCx, processObj, scalar.second(), &scalarJsValue); + if (NS_FAILED(rv)) { + return rv; + } + + // Add it to the scalar object. + if (!JS_DefineProperty(aCx, processObj, scalar.first(), scalarJsValue, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } } } @@ -1769,7 +2078,7 @@ TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSCo /** * Serializes the scalars from the given dataset to a json-style object and resets them. * The returned structure looks like: - * { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... } + * { "process": { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... } * * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN. * @param aClear Whether to clear out the keyed scalars after snapshotting. @@ -1778,6 +2087,8 @@ nsresult TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx, uint8_t optional_argc, JS::MutableHandle aResult) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Snapshotting scalars should only happen in the parent processes."); // If no arguments were passed in, apply the default value. if (!optional_argc) { aClearScalars = false; @@ -1789,29 +2100,41 @@ TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, } aResult.setObject(*root_obj); + // Return `{}` in child processes. + if (!XRE_IsParentProcess()) { + return NS_OK; + } + // Only lock the mutex while accessing our data, without locking any JS related code. typedef mozilla::Pair> DataPair; - nsTArray scalarsToReflect; + typedef nsTArray ScalarArray; + nsDataHashtable scalarsToReflect; { StaticMutexAutoLock locker(gTelemetryScalarsMutex); // Iterate the scalars in gKeyedScalarStorageMap. The storage may contain empty or yet - // to be initialized scalars. + // to be initialized scalars from all the supported processes. for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) { - KeyedScalar* scalar = static_cast(iter.Data()); + KeyedScalarStorageMapType* scalarStorage = + static_cast(iter.Data()); + ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key()); - // Get the informations for this scalar. - const ScalarInfo& info = gScalars[iter.Key()]; + for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) { + KeyedScalar* scalar = static_cast(childIter.Data()); - // Serialize the scalar if it's in the desired dataset. - if (IsInDataset(info.dataset, aDataset)) { - // Get the keys for this scalar. - nsTArray scalarKeyedData; - nsresult rv = scalar->GetValue(scalarKeyedData); - if (NS_FAILED(rv)) { - return rv; + // Get the informations for this scalar. + const ScalarInfo& info = gScalars[childIter.Key()]; + + // Serialize the scalar if it's in the desired dataset. + if (IsInDataset(info.dataset, aDataset)) { + // Get the keys for this scalar. + nsTArray scalarKeyedData; + nsresult rv = scalar->GetValue(scalarKeyedData); + if (NS_FAILED(rv)) { + return rv; + } + // Append it to our list. + processScalars.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData)); } - // Append it to our list. - scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData)); } } @@ -1822,37 +2145,51 @@ TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, } // Reflect it to JS. - for (nsTArray::size_type i = 0; i < scalarsToReflect.Length(); i++) { - const DataPair& keyedScalarData = scalarsToReflect[i]; + for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) { + ScalarArray& processScalars = iter.Data(); + const char* processName = + XRE_ChildProcessTypeToString(static_cast(iter.Key())); - // Go through each keyed scalar and create a keyed scalar object. - // This object will hold the values for all the keyed scalar keys. - JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx)); - - // Define a property for each scalar key, then add it to the keyed scalar - // object. - const nsTArray& keyProps = keyedScalarData.second(); - for (uint32_t i = 0; i < keyProps.Length(); i++) { - const KeyedScalar::KeyValuePair& keyData = keyProps[i]; - - // Convert the value for the key to a JSValue. - JS::Rooted keyJsValue(aCx); - nsresult rv = - nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue); - if (NS_FAILED(rv)) { - return rv; - } - - // Add the key to the scalar representation. - const NS_ConvertUTF8toUTF16 key(keyData.first()); - if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) { - return NS_ERROR_FAILURE; - } + // Create the object that will hold the scalars for this process and add it + // to the returned root object. + JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx)); + if (!processObj || + !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; } - // Add the scalar to the root object. - if (!JS_DefineProperty(aCx, root_obj, keyedScalarData.first(), keyedScalarObj, JSPROP_ENUMERATE)) { - return NS_ERROR_FAILURE; + for (nsTArray::size_type i = 0; i < processScalars.Length(); i++) { + const DataPair& keyedScalarData = processScalars[i]; + + // Go through each keyed scalar and create a keyed scalar object. + // This object will hold the values for all the keyed scalar keys. + JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx)); + + // Define a property for each scalar key, then add it to the keyed scalar + // object. + const nsTArray& keyProps = keyedScalarData.second(); + for (uint32_t i = 0; i < keyProps.Length(); i++) { + const KeyedScalar::KeyValuePair& keyData = keyProps[i]; + + // Convert the value for the key to a JSValue. + JS::Rooted keyJsValue(aCx); + nsresult rv = + nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue); + if (NS_FAILED(rv)) { + return rv; + } + + // Add the key to the scalar representation. + const NS_ConvertUTF8toUTF16 key(keyData.first()); + if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + // Add the scalar to the root object. + if (!JS_DefineProperty(aCx, processObj, keyedScalarData.first(), keyedScalarObj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } } } @@ -1865,6 +2202,11 @@ TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, void TelemetryScalar::ClearScalars() { + MOZ_ASSERT(XRE_IsParentProcess(), "Scalars should only be cleared in the parent process."); + if (!XRE_IsParentProcess()) { + return; + } + StaticMutexAutoLock locker(gTelemetryScalarsMutex); gScalarStorageMap.Clear(); gKeyedScalarStorageMap.Clear(); @@ -1882,15 +2224,120 @@ TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSize { StaticMutexAutoLock locker(gTelemetryScalarsMutex); size_t n = 0; - // For the plain scalars... + // Account for scalar data coming from parent and child processes. for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) { - ScalarBase* scalar = static_cast(iter.Data()); - n += scalar->SizeOfIncludingThis(aMallocSizeOf); + ScalarStorageMapType* scalarStorage = static_cast(iter.Data()); + for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) { + ScalarBase* scalar = static_cast(childIter.Data()); + n += scalar->SizeOfIncludingThis(aMallocSizeOf); + } } - // ...and for the keyed scalars. + // Also account for keyed scalar data coming from parent and child processes. for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) { - KeyedScalar* scalar = static_cast(iter.Data()); - n += scalar->SizeOfIncludingThis(aMallocSizeOf); + KeyedScalarStorageMapType* scalarStorage = + static_cast(iter.Data()); + for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) { + KeyedScalar* scalar = static_cast(childIter.Data()); + n += scalar->SizeOfIncludingThis(aMallocSizeOf); + } } return n; } + +void +TelemetryScalar::UpdateChildData(GeckoProcessType aProcessType, + const nsTArray& aScalarActions) +{ + MOZ_ASSERT(XRE_IsParentProcess(), + "The stored child processes scalar data must be updated from the parent process."); + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + if (!internal_CanRecordBase()) { + return; + } + + for (auto& upd : aScalarActions) { + if (internal_IsKeyedScalar(upd.mId)) { + continue; + } + + // Are we allowed to record this scalar? We don't need to check for + // allowed processes here, that's taken care of when recording + // in child processes. + if (!internal_CanRecordForScalarID(upd.mId)) { + continue; + } + + // Refresh the data in the parent process with the data coming from the child + // processes. + ScalarBase* scalar = nullptr; + nsresult rv = internal_GetScalarByEnum(upd.mId, aProcessType, &scalar); + if (NS_FAILED(rv)) { + NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD"); + continue; + } + + switch (upd.mActionType) + { + case ScalarActionType::eSet: + scalar->SetValue(upd.mData); + break; + case ScalarActionType::eAdd: + scalar->AddValue(upd.mData); + break; + case ScalarActionType::eSetMaximum: + scalar->SetMaximum(upd.mData); + break; + default: + NS_WARNING("Unsupported action coming from scalar child updates."); + } + } +} + +void +TelemetryScalar::UpdateChildKeyedData(GeckoProcessType aProcessType, + const nsTArray& aScalarActions) +{ + MOZ_ASSERT(XRE_IsParentProcess(), + "The stored child processes keyed scalar data must be updated from the parent process."); + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + if (!internal_CanRecordBase()) { + return; + } + + for (auto& upd : aScalarActions) { + if (!internal_IsKeyedScalar(upd.mId)) { + continue; + } + + // Are we allowed to record this scalar? We don't need to check for + // allowed processes here, that's taken care of when recording + // in child processes. + if (!internal_CanRecordForScalarID(upd.mId)) { + continue; + } + + // Refresh the data in the parent process with the data coming from the child + // processes. + KeyedScalar* scalar = nullptr; + nsresult rv = internal_GetKeyedScalarByEnum(upd.mId, aProcessType, &scalar); + if (NS_FAILED(rv)) { + NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD"); + continue; + } + + switch (upd.mActionType) + { + case ScalarActionType::eSet: + scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData); + break; + case ScalarActionType::eAdd: + scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData); + break; + case ScalarActionType::eSetMaximum: + scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData); + break; + default: + NS_WARNING("Unsupported action coming from keyed scalar child updates."); + } + } +} diff --git a/toolkit/components/telemetry/TelemetryScalar.h b/toolkit/components/telemetry/TelemetryScalar.h index b20a8dace2bd..c87da34612cf 100644 --- a/toolkit/components/telemetry/TelemetryScalar.h +++ b/toolkit/components/telemetry/TelemetryScalar.h @@ -59,6 +59,12 @@ void ClearScalars(); size_t GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); size_t GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); +void UpdateChildData(GeckoProcessType aProcessType, + const nsTArray& aScalarActions); + +void UpdateChildKeyedData(GeckoProcessType aProcessType, + const nsTArray& aScalarActions); + } // namespace TelemetryScalar #endif // TelemetryScalar_h__ \ No newline at end of file diff --git a/toolkit/components/telemetry/nsITelemetry.idl b/toolkit/components/telemetry/nsITelemetry.idl index a356eb47e9f6..cb1162548f8d 100644 --- a/toolkit/components/telemetry/nsITelemetry.idl +++ b/toolkit/components/telemetry/nsITelemetry.idl @@ -410,7 +410,7 @@ interface nsITelemetry : nsISupports /** * Serializes the scalars from the given dataset to a JSON-style object and resets them. * The returned structure looks like: - * { "group1.probe": 1, "group1.other_probe": false, ... } + * {"process": {"group1.probe":1,"group1.other_probe":false,...}, ... }. * * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN. * @param [aClear=false] Whether to clear out the scalars after snapshotting. @@ -453,7 +453,7 @@ interface nsITelemetry : nsISupports * Serializes the keyed scalars from the given dataset to a JSON-style object and * resets them. * The returned structure looks like: - * { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... } + * { "process": { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... } * * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN. * @param [aClear=false] Whether to clear out the scalars after snapshotting.