Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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"
#if defined(__APPLE__) && defined(__aarch64__)
# define HAS_PROCESS_ENERGY
#endif
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;
}
};
#ifdef HAS_PROCESS_ENERGY
struct ProcessEnergyMarker {
static constexpr Span<const char> MarkerTypeName() {
return MakeStringSpan("ProcessEnergy");
}
static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
int64_t aUWh,
const ProfilerString8View& aType) {
aWriter.IntProperty("energy", aUWh);
aWriter.StringProperty("label", aType);
}
static MarkerSchema MarkerTypeDisplay() {
using MS = MarkerSchema;
MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
schema.AddKeyLabelFormat("energy", "Energy (µWh)", MS::Format::Integer);
schema.SetTooltipLabel("{marker.name} - {marker.data.label}");
schema.SetTableLabel(
"{marker.name} - {marker.data.label}: {marker.data.energy}µWh");
return schema;
}
};
#endif
} // 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";
}
}
#ifdef HAS_PROCESS_ENERGY
static int64_t GetTaskEnergy() {
task_power_info_v2_data_t task_power_info;
mach_msg_type_number_t count = TASK_POWER_INFO_V2_COUNT;
kern_return_t kr = task_info(mach_task_self(), TASK_POWER_INFO_V2,
(task_info_t)&task_power_info, &count);
if (kr != KERN_SUCCESS) {
return 0;
}
// task_energy is in nanojoules. We want microwatt-hours.
return task_power_info.task_energy / 3.6 / 1e6;
}
#endif
void RecordPowerMetrics() {
static uint64_t previousCpuTime = 0, previousGpuTime = 0;
#ifdef HAS_PROCESS_ENERGY
static int64_t previousProcessEnergy = 0;
#endif
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;
}
#ifdef HAS_PROCESS_ENERGY
int64_t processEnergy = GetTaskEnergy();
int64_t newProcessEnergy = processEnergy - previousProcessEnergy;
#endif
if (!newCpuTime && !newGpuTime
#ifdef HAS_PROCESS_ENERGY
&& newProcessEnergy <= 0
#endif
) {
// 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;
case hal::PROCESS_PRIORITY_PREALLOC:
type.Assign("prealloc");
gThisProcessType = ProcessType::eUnknown;
break;
default:
MOZ_ASSERT_UNREACHABLE("Unsuppored process type for cpu time");
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;
}
#ifdef HAS_PROCESS_ENERGY
if (newProcessEnergy) {
power::energy_per_process_type.Get(type).Add(newProcessEnergy);
PROFILER_MARKER("Process Energy", OTHER, {}, ProcessEnergyMarker,
newProcessEnergy, type);
previousProcessEnergy += newProcessEnergy;
}
#endif
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 (RefPtr<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:
mozilla::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: {
RefPtr<net::SocketProcessParent> socketParent(
net::SocketProcessParent::GetSingleton());
Unused << socketParent->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