diff options
Diffstat (limited to 'toolkit/components/glean/ipc/FOGIPC.cpp')
-rw-r--r-- | toolkit/components/glean/ipc/FOGIPC.cpp | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/toolkit/components/glean/ipc/FOGIPC.cpp b/toolkit/components/glean/ipc/FOGIPC.cpp new file mode 100644 index 0000000000..9a1b10c9c2 --- /dev/null +++ b/toolkit/components/glean/ipc/FOGIPC.cpp @@ -0,0 +1,575 @@ +/* -*- 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/. */ + +#include "FOGIPC.h" + +#include <limits> +#include "mozilla/glean/fog_ffi_generated.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/gfx/GPUChild.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/Hal.h" +#include "mozilla/MozPromise.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/ProcInfo.h" +#include "mozilla/RDDChild.h" +#include "mozilla/RDDParent.h" +#include "mozilla/RDDProcessManager.h" +#include "mozilla/ipc/UtilityProcessChild.h" +#include "mozilla/ipc/UtilityProcessManager.h" +#include "mozilla/ipc/UtilityProcessParent.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" +#include "mozilla/Unused.h" +#include "GMPPlatform.h" +#include "GMPServiceParent.h" +#include "nsIClassifiedChannel.h" +#include "nsIXULRuntime.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" + +using mozilla::dom::ContentParent; +using mozilla::gfx::GPUChild; +using mozilla::gfx::GPUProcessManager; +using mozilla::ipc::ByteBuf; +using mozilla::ipc::UtilityProcessChild; +using mozilla::ipc::UtilityProcessManager; +using mozilla::ipc::UtilityProcessParent; +using FlushFOGDataPromise = mozilla::dom::ContentParent::FlushFOGDataPromise; + +namespace geckoprofiler::markers { + +using namespace mozilla; + +struct ProcessingTimeMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("ProcessingTime"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + int64_t aDiffMs, + const ProfilerString8View& aType, + const ProfilerString8View& aTrackerType) { + aWriter.IntProperty("time", aDiffMs); + aWriter.StringProperty("label", aType); + if (aTrackerType.Length() > 0) { + aWriter.StringProperty("tracker", aTrackerType); + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormat("time", "Recorded Time", MS::Format::Milliseconds); + schema.AddKeyLabelFormat("tracker", "Tracker Type", MS::Format::String); + schema.SetTooltipLabel("{marker.name} - {marker.data.label}"); + schema.SetTableLabel( + "{marker.name} - {marker.data.label}: {marker.data.time}"); + return schema; + } +}; + +} // namespace geckoprofiler::markers + +namespace mozilla::glean { + +// Echoes processtools/metrics.yaml's power.wakeups_per_thread +enum ProcessType { + eParentActive, + eParentInactive, + eContentForeground, + eContentBackground, + eGpuProcess, + eUnknown, +}; + +// This static global is set within RecordPowerMetrics on the main thread, +// using information only gettable on the main thread, and is read within +// RecordThreadCpuUse on any thread. +static Atomic<ProcessType> gThisProcessType(eUnknown); + +#ifdef NIGHTLY_BUILD +// It is fine to call RecordThreadCpuUse during startup before the first +// RecordPowerMetrics call. In that case the parent process will be recorded +// as inactive, and other processes will be ignored (content processes start +// in the 'prealloc' type for which we don't record per-thread CPU use data). + +void RecordThreadCpuUse(const nsACString& aThreadName, uint64_t aCpuTimeMs, + uint64_t aWakeCount) { + ProcessType processType = gThisProcessType; + + if (processType == ProcessType::eUnknown) { + if (XRE_IsParentProcess()) { + // During startup we might not have gotten a RecordPowerMetrics call. + // That's fine. Default to eParentInactive. + processType = eParentInactive; + } else { + // We are not interested in per-thread CPU use data for the current + // process type. + return; + } + } + + nsAutoCString threadName(aThreadName); + for (size_t i = 0; i < threadName.Length(); ++i) { + const char c = threadName.CharAt(i); + + // Valid characters. + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-' || + c == '_') { + continue; + } + + // Should only use lower case characters + if (c >= 'A' && c <= 'Z') { + threadName.SetCharAt(c + ('a' - 'A'), i); + continue; + } + + // Replace everything else with _ + threadName.SetCharAt('_', i); + } + + if (aCpuTimeMs != 0 && + MOZ_LIKELY(aCpuTimeMs < std::numeric_limits<int32_t>::max())) { + switch (processType) { + case eParentActive: + power_cpu_ms_per_thread::parent_active.Get(threadName) + .Add(int32_t(aCpuTimeMs)); + break; + case eParentInactive: + power_cpu_ms_per_thread::parent_inactive.Get(threadName) + .Add(int32_t(aCpuTimeMs)); + break; + case eContentForeground: + power_cpu_ms_per_thread::content_foreground.Get(threadName) + .Add(int32_t(aCpuTimeMs)); + break; + case eContentBackground: + power_cpu_ms_per_thread::content_background.Get(threadName) + .Add(int32_t(aCpuTimeMs)); + break; + case eGpuProcess: + power_cpu_ms_per_thread::gpu_process.Get(threadName) + .Add(int32_t(aCpuTimeMs)); + break; + case eUnknown: + // Nothing to do. + break; + } + } + + if (aWakeCount != 0 && + MOZ_LIKELY(aWakeCount < std::numeric_limits<int32_t>::max())) { + switch (processType) { + case eParentActive: + power_wakeups_per_thread::parent_active.Get(threadName) + .Add(int32_t(aWakeCount)); + break; + case eParentInactive: + power_wakeups_per_thread::parent_inactive.Get(threadName) + .Add(int32_t(aWakeCount)); + break; + case eContentForeground: + power_wakeups_per_thread::content_foreground.Get(threadName) + .Add(int32_t(aWakeCount)); + break; + case eContentBackground: + power_wakeups_per_thread::content_background.Get(threadName) + .Add(int32_t(aWakeCount)); + break; + case eGpuProcess: + power_wakeups_per_thread::gpu_process.Get(threadName) + .Add(int32_t(aWakeCount)); + break; + case eUnknown: + // Nothing to do. + break; + } + } +} +#endif + +void GetTrackerType(nsAutoCString& aTrackerType) { + using namespace mozilla::dom; + uint32_t trackingFlags = + (nsIClassifiedChannel::CLASSIFIED_CRYPTOMINING | + nsIClassifiedChannel::CLASSIFIED_FINGERPRINTING | + nsIClassifiedChannel::CLASSIFIED_TRACKING | + nsIClassifiedChannel::CLASSIFIED_TRACKING_AD | + nsIClassifiedChannel::CLASSIFIED_TRACKING_ANALYTICS | + nsIClassifiedChannel::CLASSIFIED_TRACKING_SOCIAL); + AutoTArray<RefPtr<BrowsingContextGroup>, 5> bcGroups; + BrowsingContextGroup::GetAllGroups(bcGroups); + for (auto& bcGroup : bcGroups) { + AutoTArray<DocGroup*, 5> docGroups; + bcGroup->GetDocGroups(docGroups); + for (auto* docGroup : docGroups) { + for (Document* doc : *docGroup) { + nsCOMPtr<nsIClassifiedChannel> classifiedChannel = + do_QueryInterface(doc->GetChannel()); + if (classifiedChannel) { + uint32_t classificationFlags = + classifiedChannel->GetThirdPartyClassificationFlags(); + trackingFlags &= classificationFlags; + if (!trackingFlags) { + return; + } + } + } + } + } + + // The if-elseif-else chain works because the tracker types listed here are + // currently mutually exclusive and should be maintained that way by policy. + if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_TRACKING_AD) { + aTrackerType = "ad"; + } else if (trackingFlags == + nsIClassifiedChannel::CLASSIFIED_TRACKING_ANALYTICS) { + aTrackerType = "analytics"; + } else if (trackingFlags == + nsIClassifiedChannel::CLASSIFIED_TRACKING_SOCIAL) { + aTrackerType = "social"; + } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_CRYPTOMINING) { + aTrackerType = "cryptomining"; + } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_FINGERPRINTING) { + aTrackerType = "fingerprinting"; + } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_TRACKING) { + // CLASSIFIED_TRACKING means we were not able to identify the type of + // classification. + aTrackerType = "unknown"; + } +} + +void RecordPowerMetrics() { + static uint64_t previousCpuTime = 0, previousGpuTime = 0; + + uint64_t cpuTime, newCpuTime = 0; + if (NS_SUCCEEDED(GetCpuTimeSinceProcessStartInMs(&cpuTime)) && + cpuTime > previousCpuTime) { + newCpuTime = cpuTime - previousCpuTime; + } + + uint64_t gpuTime, newGpuTime = 0; + // Avoid loading gdi32.dll for the Socket process where the GPU is never used. + if (!XRE_IsSocketProcess() && + NS_SUCCEEDED(GetGpuTimeSinceProcessStartInMs(&gpuTime)) && + gpuTime > previousGpuTime) { + newGpuTime = gpuTime - previousGpuTime; + } + + if (!newCpuTime && !newGpuTime) { + // Nothing to record. + return; + } + + // Compute the process type string. + nsAutoCString type(XRE_GetProcessTypeString()); + nsAutoCString trackerType; + if (XRE_IsContentProcess()) { + auto* cc = dom::ContentChild::GetSingleton(); + if (cc) { + type.Assign(dom::RemoteTypePrefix(cc->GetRemoteType())); + if (StringBeginsWith(type, WEB_REMOTE_TYPE)) { + type.AssignLiteral("web"); + switch (cc->GetProcessPriority()) { + case hal::PROCESS_PRIORITY_BACKGROUND: + type.AppendLiteral(".background"); + gThisProcessType = ProcessType::eContentBackground; + break; + case hal::PROCESS_PRIORITY_FOREGROUND: + type.AppendLiteral(".foreground"); + gThisProcessType = ProcessType::eContentForeground; + break; + case hal::PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE: + type.AppendLiteral(".background-perceivable"); + gThisProcessType = ProcessType::eUnknown; + break; + default: + gThisProcessType = ProcessType::eUnknown; + break; + } + } + GetTrackerType(trackerType); + } else { + gThisProcessType = ProcessType::eUnknown; + } + } else if (XRE_IsParentProcess()) { + if (nsContentUtils::GetUserIsInteracting()) { + type.AssignLiteral("parent.active"); + gThisProcessType = ProcessType::eParentActive; + } else { + type.AssignLiteral("parent.inactive"); + gThisProcessType = ProcessType::eParentInactive; + } + hal::WakeLockInformation info; + GetWakeLockInfo(u"video-playing"_ns, &info); + if (info.numLocks() != 0 && info.numHidden() < info.numLocks()) { + type.AppendLiteral(".playing-video"); + } else { + GetWakeLockInfo(u"audio-playing"_ns, &info); + if (info.numLocks()) { + type.AppendLiteral(".playing-audio"); + } + } + } else if (XRE_IsGPUProcess()) { + gThisProcessType = ProcessType::eGpuProcess; + } else { + gThisProcessType = ProcessType::eUnknown; + } + + if (newCpuTime) { + // The counters are reset at least once a day. Assuming all cores are used + // continuously, an int32 can hold the data for 24.85 cores. + // This should be fine for now, but may overflow in the future. + // Bug 1751277 tracks a newer, bigger counter. + int32_t nNewCpuTime = int32_t(newCpuTime); + if (newCpuTime < std::numeric_limits<int32_t>::max()) { + power::total_cpu_time_ms.Add(nNewCpuTime); + power::cpu_time_per_process_type_ms.Get(type).Add(nNewCpuTime); + if (!trackerType.IsEmpty()) { + power::cpu_time_per_tracker_type_ms.Get(trackerType).Add(nNewCpuTime); + } + } else { + power::cpu_time_bogus_values.Add(1); + } + PROFILER_MARKER("Process CPU Time", OTHER, {}, ProcessingTimeMarker, + nNewCpuTime, type, trackerType); + previousCpuTime += newCpuTime; + } + + if (newGpuTime) { + int32_t nNewGpuTime = int32_t(newGpuTime); + if (newGpuTime < std::numeric_limits<int32_t>::max()) { + power::total_gpu_time_ms.Add(nNewGpuTime); + power::gpu_time_per_process_type_ms.Get(type).Add(nNewGpuTime); + } else { + power::gpu_time_bogus_values.Add(1); + } + PROFILER_MARKER("Process GPU Time", OTHER, {}, ProcessingTimeMarker, + nNewGpuTime, type, trackerType); + previousGpuTime += newGpuTime; + } + + profiler_record_wakeup_count(type); +} + +/** + * Flush your data ASAP, either because the parent process is asking you to + * or because the process is about to shutdown. + * + * @param aResolver - The function you need to call with the bincoded, + * serialized payload that the Rust impl hands you. + */ +void FlushFOGData(std::function<void(ipc::ByteBuf&&)>&& aResolver) { + // Record power metrics right before data is sent to the parent. + RecordPowerMetrics(); + + ByteBuf buf; + uint32_t ipcBufferSize = impl::fog_serialize_ipc_buf(); + bool ok = buf.Allocate(ipcBufferSize); + if (!ok) { + return; + } + uint32_t writtenLen = impl::fog_give_ipc_buf(buf.mData, buf.mLen); + if (writtenLen != ipcBufferSize) { + return; + } + aResolver(std::move(buf)); +} + +/** + * Called by FOG on the parent process when it wants to flush all its + * children's data. + * @param aResolver - The function that'll be called with the results. + */ +void FlushAllChildData( + std::function<void(nsTArray<ipc::ByteBuf>&&)>&& aResolver) { + auto timerId = fog_ipc::flush_durations.Start(); + + nsTArray<ContentParent*> parents; + ContentParent::GetAll(parents); + nsTArray<RefPtr<FlushFOGDataPromise>> promises; + for (auto* parent : parents) { + promises.EmplaceBack(parent->SendFlushFOGData()); + } + + if (GPUProcessManager* gpuManager = GPUProcessManager::Get()) { + if (GPUChild* gpuChild = gpuManager->GetGPUChild()) { + promises.EmplaceBack(gpuChild->SendFlushFOGData()); + } + } + + if (RDDProcessManager* rddManager = RDDProcessManager::Get()) { + if (RDDChild* rddChild = rddManager->GetRDDChild()) { + promises.EmplaceBack(rddChild->SendFlushFOGData()); + } + } + + if (net::SocketProcessParent* socketParent = + net::SocketProcessParent::GetSingleton()) { + promises.EmplaceBack(socketParent->SendFlushFOGData()); + } + + if (RefPtr<mozilla::gmp::GeckoMediaPluginServiceParent> gmps = + mozilla::gmp::GeckoMediaPluginServiceParent::GetSingleton()) { + // There can be multiple Gecko Media Plugin processes, but iterating + // through them requires locking a mutex and the IPCs need to be sent + // from a different thread, so it's better to let the + // GeckoMediaPluginServiceParent code do it for us. + gmps->SendFlushFOGData(promises); + } + + if (RefPtr<UtilityProcessManager> utilityManager = + UtilityProcessManager::GetIfExists()) { + for (RefPtr<UtilityProcessParent>& parent : + utilityManager->GetAllProcessesProcessParent()) { + promises.EmplaceBack(parent->SendFlushFOGData()); + } + } + + if (promises.Length() == 0) { + // No child processes at the moment. Resolve synchronously. + fog_ipc::flush_durations.Cancel(std::move(timerId)); + nsTArray<ipc::ByteBuf> results; + aResolver(std::move(results)); + return; + } + + // If fog.ipc.flush_failures ever gets too high: + // TODO: Don't throw away resolved data if some of the promises reject. + // (not sure how, but it'll mean not using ::All... maybe a custom copy of + // AllPromiseHolder? Might be impossible outside MozPromise.h) + FlushFOGDataPromise::All(GetCurrentSerialEventTarget(), promises) + ->Then(GetCurrentSerialEventTarget(), __func__, + [aResolver = std::move(aResolver), timerId]( + FlushFOGDataPromise::AllPromiseType::ResolveOrRejectValue&& + aValue) { + fog_ipc::flush_durations.StopAndAccumulate(std::move(timerId)); + if (aValue.IsResolve()) { + aResolver(std::move(aValue.ResolveValue())); + } else { + fog_ipc::flush_failures.Add(1); + nsTArray<ipc::ByteBuf> results; + aResolver(std::move(results)); + } + }); +} + +/** + * A child process has sent you this buf as a treat. + * @param buf - a bincoded serialized payload that the Rust impl understands. + */ +void FOGData(ipc::ByteBuf&& buf) { + JOG::EnsureRuntimeMetricsRegistered(); + fog_ipc::buffer_sizes.Accumulate(buf.mLen); + impl::fog_use_ipc_buf(buf.mData, buf.mLen); +} + +/** + * Called by FOG on a child process when it wants to send a buf to the parent. + * @param buf - a bincoded serialized payload that the Rust impl understands. + */ +void SendFOGData(ipc::ByteBuf&& buf) { + switch (XRE_GetProcessType()) { + case GeckoProcessType_Content: + mozilla::dom::ContentChild::GetSingleton()->SendFOGData(std::move(buf)); + break; + case GeckoProcessType_GMPlugin: { + mozilla::gmp::SendFOGData(std::move(buf)); + } break; + case GeckoProcessType_GPU: + Unused << mozilla::gfx::GPUParent::GetSingleton()->SendFOGData( + std::move(buf)); + break; + case GeckoProcessType_RDD: + Unused << mozilla::RDDParent::GetSingleton()->SendFOGData(std::move(buf)); + break; + case GeckoProcessType_Socket: + Unused << net::SocketProcessChild::GetSingleton()->SendFOGData( + std::move(buf)); + break; + case GeckoProcessType_Utility: + Unused << ipc::UtilityProcessChild::GetSingleton()->SendFOGData( + std::move(buf)); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsuppored process type"); + } +} + +/** + * Called on the parent process to ask all child processes for data, + * sending it all down into Rust to be used. + */ +RefPtr<GenericPromise> FlushAndUseFOGData() { + // Record power metrics on the parent before sending requests to child + // processes. + RecordPowerMetrics(); + + RefPtr<GenericPromise::Private> ret = new GenericPromise::Private(__func__); + std::function<void(nsTArray<ByteBuf>&&)> resolver = + [ret](nsTArray<ByteBuf>&& bufs) { + for (ByteBuf& buf : bufs) { + FOGData(std::move(buf)); + } + ret->Resolve(true, __func__); + }; + FlushAllChildData(std::move(resolver)); + return ret; +} + +void TestTriggerMetrics(uint32_t aProcessType, + const RefPtr<dom::Promise>& promise) { + switch (aProcessType) { + case nsIXULRuntime::PROCESS_TYPE_GMPLUGIN: { + RefPtr<mozilla::gmp::GeckoMediaPluginServiceParent> gmps( + mozilla::gmp::GeckoMediaPluginServiceParent::GetSingleton()); + gmps->TestTriggerMetrics()->Then( + GetCurrentSerialEventTarget(), __func__, + [promise]() { promise->MaybeResolveWithUndefined(); }, + [promise]() { promise->MaybeRejectWithUndefined(); }); + } break; + case nsIXULRuntime::PROCESS_TYPE_GPU: + gfx::GPUProcessManager::Get()->TestTriggerMetrics()->Then( + GetCurrentSerialEventTarget(), __func__, + [promise]() { promise->MaybeResolveWithUndefined(); }, + [promise]() { promise->MaybeRejectWithUndefined(); }); + break; + case nsIXULRuntime::PROCESS_TYPE_RDD: + RDDProcessManager::Get()->TestTriggerMetrics()->Then( + GetCurrentSerialEventTarget(), __func__, + [promise]() { promise->MaybeResolveWithUndefined(); }, + [promise]() { promise->MaybeRejectWithUndefined(); }); + break; + case nsIXULRuntime::PROCESS_TYPE_SOCKET: + Unused << net::SocketProcessParent::GetSingleton() + ->SendTestTriggerMetrics() + ->Then( + GetCurrentSerialEventTarget(), __func__, + [promise]() { promise->MaybeResolveWithUndefined(); }, + [promise]() { promise->MaybeRejectWithUndefined(); }); + break; + case nsIXULRuntime::PROCESS_TYPE_UTILITY: + Unused << ipc::UtilityProcessManager::GetSingleton() + ->GetProcessParent(ipc::SandboxingKind::GENERIC_UTILITY) + ->SendTestTriggerMetrics() + ->Then( + GetCurrentSerialEventTarget(), __func__, + [promise]() { promise->MaybeResolveWithUndefined(); }, + [promise]() { promise->MaybeRejectWithUndefined(); }); + break; + default: + promise->MaybeRejectWithUndefined(); + break; + } +} + +} // namespace mozilla::glean |