Files
palemoon27/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
T
roytam1 86a3aa0b54 import changes from `dev' branch of rmottola/Arctic-Fox:
- missing part of  Bug 1165772: P1. (7311039be4)
- missing gstreamer stuff (54a80d69b2)
- Bug 1214208: Do not use MP3Decoder on B2G. r=alfredo (0a19e7946e)
- Bug 1194014 - Remove redundant includes. r=jya (ccc3753113)
- Bug 1039639 - Add support for Flac on Firefox OS. r=cajbir (7d76197e07)
- Bug 875573 - Add video/x-m4v mime type. r=kentuckyfriedtakahe (6ec8af93e6)
- cleanup (6fb3d5dd26)
- Bug 1180621 - [FxOS] Enable VP9 codec for the Android version after KK. r=sotaro (58f7c2b657)
- Bug 1187247: [MSE] P2. Enable WebM in MediaSource. r=jya (2df0ee1f7a)
- Bug 1187247: [MSE] P1. Continue parsing MediaSegment if buffer starts with SimpleBlock/Block. r=kinetik (574475ed6f)
- Bug 1217170: P1. Rename functions to explicitly reflect what they are doing. r=kentuckyfriedtakahe (70c81a8179)
- Bug 1070216 - Split DOMMediaStream::InitStreamCommon into three. r=roc (1bda71cc88)
- Bug 1215582 - Rename Blacklist to Block list in GStreamerFormatReader. r=gerald (4f08077f5e)
- Bug 1170958 - Destroy track-locked MediaInputPorts when the track ends. r=roc (ff3922a2d6)
- Bug 1070216 - constify DOMMediaStream::Get[Audio/Video]Tracks(). r=roc (ba09f6f191)
- Bug 1070216 - Guard against adding a track owned by one MSG to a stream owned by another. r=padenot (a80deb8b30)
- Bug 1070216 - Implement MediaStream constructors. r=smaug,jib,padenot (3403ef2599)
- Bug 1070216 - Guard against a null MediaInputPort in DOMMediaStream::FindPlaybackDOMTrack(). r=roc (453a9ffbc1)
- Bug 1212783 - Expose TrackPort in DOMMediaStream.h r=roc (fb61c79ae7)
- Bug 1219711 - Ensure MediaStreamTrack.enabled propagates across peer connections. r=jesup (d9d1e54dae)
- Bug 1129051 - Fix double free in Camera Control Listener. Fix webrtc memory leak. r=aosmond (3e9b3bccfd)
- Bug 1152260 - Generate focused event for drivers that do not notify us when using continuous auto focus. r=mikeh (6c7bd42fdc)
- Bug 1175656 - Implement generation of recording posters in Gecko. r=dhylands,bz (51b2c66dc7)
- Bug 1187364 - Part 1. Add ability for camera to pause/resume recording. r=dhylands,bz (c54c735e37)
- Bug 1187364 - Part 2. Ensure that recording is resumed with a key frame. r=mchiang (c1c6048982)
- Bug 1187364 - Part 3. Fix missing end comment in WebIDL. r=me,bz (7faf106cc1)
- Bug 1212783 - Add a MediaStreamTrack to DOMCameraControl. r=aosmond (91e11efd3a)
- Bug 1124338 - Fix possible camera cached parameters invalidation from underlying driver modification. r=aosmond (dea67dc155)
- Bug 1196330 - Do not restart preview if configuration is unchanged. r=dhylands (097644f5d9)
- Bug 1215372 - Filter empty camera face detected events at gonk layer. r=dhylands (733efe50eb)
- Bug 1179726 - Prefer lower resolutions than 4kuhd as the default video recording profile. r=dhylands (27c71273dc)
- Bug 1222122 - Add picture size to verified parameters when reconfiguring the camera. r=dhylands (8c1fac6a4a)
- Bug 1141267 - register CameraThread with profiler, r=aosmond (299592a024)
- Bug 1008483 - removes the RW lock in CameraControlImpl and replaces it with a standard mutex. r=aosmond (45936cb90d)
- Bug 1008483 - Part 2. Readd missing nsPrintfCString.h include which has broken some local builds. r=me (9dd84b0f19)
- Bug 1191731 - Update poster API to allow application control over when poster is saved. r=bz, r=dhylands (73f9e7e0f4)
- Bug 1155648 - Fix documentation for DOMMediaStream::OnTracksAvailable. r=jesup (702828c304)
- Bug 1217170: [MSE] P2. Enable WebM/MSE on systems with no MP4/H264 support. r=kentuckyfriedtakahe (0b814b0708)
- Bug 1213177: Enable WebM on machines where H264 HW decoding is disabled. r=kentuckyfriedtakahe (e64da2ea24)
- add back some sps telemetry (52c2c64f5b)
- missing bit of Bug 1195073: [MSE/webm] P1 (9c45e82c3d)
- Bug 1150305 - sourcebuffer.buffered returns the same object if not changed. r=roc, r=bz, r=jya (6005d56c0c)
- Bug 1215447 - move flag setting from SeekStarted() to Seek(). r=roc. (a646b744c1)
- Bug 1119936 - Audio from FM Radio or Music app ceases to play when switching between front/back camera. r=roc (1a60aa7d69)
- Bug 1186806 - Part 1: Replace nsBaseHashtable::EnumerateRead() with iterators in HTMLFormControlsCollection. r=khuey (ccb8cb180a)
- Bug 1186806 - Part 2: Use NS_IMPL_CYCLE_COLLECTION_TRAVERSE instead of manual traversal in HTMLFormElement. r=khuey (57e6eabf1b)
- Bug 1186806 - Part 3: Replace nsBaseHashtable::EnumerateRead() with iterators in HTMLMediaElement. r=khuey (243ef6e83b)
- Bug 1186806 - Part 4: Replace nsBaseHashtable::EnumerateRead() with iterators in HTMLPropertiesCollection. r=khuey (499bdef85f)
- Bug 1163958 - Reduce the allocation in MediaStreamGraph - patch 3 CLOSED TREE (a557661df1)
- Bug 1219330 - Prevent the creation of TextureClient after shutdown. r=mattwoodrow (a6c047d54f)
- Bug 1205559: Make TextureChild/TextureClient thread-safe. r=nical (307c089631)
- missing bit of 1219330 (0e351ea419)
- nsRefPtr -> RefPtr (07ba248e69)
- Bug 1215023. Part 1 - make MediaDecoder::mOwner a const member. We will check mShuttingDown before calling functions of mOwner. r=kinetik. (da7f201815)
- Bug 1215023. Part 2 - remove null check of mOwner. We check mShuttingDown to know whether it is valid to call functions of mOwner. r=kinetik. (8d28a04bbe)
- Bug 1220558. Part 2 - remove unused members. r=jya. (d3a9ed8c68)
- Bug 1223599 - Remove the throttling argument from AbstractMediaDecoder::NotifyDataArrived(). r=jya. (320323ff1d)
- Bug 1194606 - Make MediaDecoderStateMachine capable of requesting different kind (decoded/raw) of media data. r=jya (1e2b6a5c44)
- Bug 1197075: P3. Decode frames ahead of MDSM requesting them. r=edwin This makes the media.*-decode-ahead pref performs more according to its name. We decode audio and video in advance so a MediaDataPromise can be resolved almost instantly. Default is 2. (b3f56447c4)
- Bug 1189964 - Fix bustage. r=bustage CLOSED TREE (afaa49b4b5)
- Bug 1212149 - e10s support for opening notification settings. r=wchen (f0e7778fb6)
- Bug 1215644 - Use child process volume service cache for available and storage status requests. r=dhylands (dfd49f2ef3)
- bug 1215552 - nsHttpConnectionMgr::PostEvent shouldnt manually ref count r=hurley (5e2f1886e6)
- Bug 1219392 - Capitalize mozilla::unused to avoid conflicts. r=froydnj (0c8bb7f15a)
- bug 1217834 - buzzfeed packet loss r=dragana (e9a60b605f)
- Bug 1168033 - Add a comment to nsHttpConnectionMgr.cpp explaining the assignment of attemptedOptimisticPipeline. r=mcmanus (2451996350)
- bug 1189645 - remove spdy telem r=hurley (cda90abbdb)
- Bug 1148268 - fixed misspelling attribute mActorDestoryed. r=dhyland. (3615d68765)
- Bug 1216031 - Make MediaDecoder::mVideoFrameContainer const. r=kinetik. (a3feb9d6bc)
- missing bits of  Bug 1165515 - Part 13-2 (009e32281f)
- Bug 1131473 - crash in -[NativeMenuItemTarget menuItemHit:]. r=spohl (ea2da6441c)
- Bug 1216416 - Fix -Wimplicit-fallthrough warnings in widget/cocoa. r=spohl (faaa390b20)
- Bug 1181977 - Firefox app menu contains only "Quit" in certain edgecases. r=spohl (0b9d912961)
2022-12-06 13:48:22 +08:00

1034 lines
29 KiB
C++

/* -*- Mode: C++; tab-width: 8; 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 "nsPerformanceStats.h"
#include "nsMemory.h"
#include "nsLiteralString.h"
#include "nsCRTGlue.h"
#include "nsServiceManagerUtils.h"
#include "nsCOMArray.h"
#include "nsIMutableArray.h"
#include "nsReadableUtils.h"
#include "jsapi.h"
#include "nsJSUtils.h"
#include "xpcpublic.h"
#include "jspubtd.h"
#include "nsIDOMWindow.h"
#include "nsGlobalWindow.h"
#include "mozilla/unused.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#if defined(XP_WIN)
#include <processthreadsapi.h>
#include <windows.h>
#else
#include <unistd.h>
#endif // defined(XP_WIN)
#if defined(XP_MACOSX)
#include <mach/mach_init.h>
#include <mach/mach_interface.h>
#include <mach/mach_port.h>
#include <mach/mach_types.h>
#include <mach/message.h>
#include <mach/thread_info.h>
#elif defined(XP_UNIX)
#include <sys/time.h>
#include <sys/resource.h>
#endif // defined(XP_UNIX)
/* ------------------------------------------------------
*
* Utility functions.
*
*/
namespace {
/**
* Get the private window for the current compartment.
*
* @return null if the code is not executed in a window or in
* case of error, a nsPIDOMWindow otherwise.
*/
already_AddRefed<nsPIDOMWindow>
GetPrivateWindow(JSContext* cx) {
nsCOMPtr<nsPIDOMWindow> win = xpc::CurrentWindowOrNull(cx);
if (!win) {
return nullptr;
}
win = win->GetOuterWindow();
if (!win) {
return nullptr;
}
nsCOMPtr<nsPIDOMWindow> top = win->GetTop();
if (!top) {
return nullptr;
}
return top.forget();
}
bool
URLForGlobal(JSContext* cx, JS::Handle<JSObject*> global, nsAString& url) {
nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(global);
if (!principal) {
return false;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = principal->GetURI(getter_AddRefs(uri));
if (NS_FAILED(rv) || !uri) {
return false;
}
nsAutoCString spec;
rv = uri->GetSpec(spec);
if (NS_FAILED(rv)) {
return false;
}
url.Assign(NS_ConvertUTF8toUTF16(spec));
return true;
}
/**
* Extract a somewhat human-readable name from the current context.
*/
void
CompartmentName(JSContext* cx, JS::Handle<JSObject*> global, nsAString& name) {
// Attempt to use the URL as name.
if (URLForGlobal(cx, global, name)) {
return;
}
// Otherwise, fallback to XPConnect's less readable but more
// complete naming scheme.
nsAutoCString cname;
xpc::GetCurrentCompartmentName(cx, cname);
name.Assign(NS_ConvertUTF8toUTF16(cname));
}
/**
* Generate a unique-to-the-application identifier for a group.
*/
void
GenerateUniqueGroupId(const JSRuntime* rt, uint64_t uid, uint64_t processId, nsAString& groupId) {
uint64_t runtimeId = reinterpret_cast<uintptr_t>(rt);
groupId.AssignLiteral("process: ");
groupId.AppendInt(processId);
groupId.AppendLiteral(", thread: ");
groupId.AppendInt(runtimeId);
groupId.AppendLiteral(", group: ");
groupId.AppendInt(uid);
}
} // namespace
/* ------------------------------------------------------
*
* struct PerformanceData
*
*/
PerformanceData::PerformanceData()
: mTotalUserTime(0)
, mTotalSystemTime(0)
, mTotalCPOWTime(0)
, mTicks(0)
{
mozilla::PodArrayZero(mDurations);
}
/* ------------------------------------------------------
*
* class nsPerformanceGroupDetails
*
*/
const nsAString&
nsPerformanceGroupDetails::Name() const {
return mName;
}
const nsAString&
nsPerformanceGroupDetails::GroupId() const {
return mGroupId;
}
const nsAString&
nsPerformanceGroupDetails::AddonId() const {
return mAddonId;
}
uint64_t
nsPerformanceGroupDetails::WindowId() const {
return mWindowId;
}
uint64_t
nsPerformanceGroupDetails::ProcessId() const {
return mProcessId;
}
bool
nsPerformanceGroupDetails::IsSystem() const {
return mIsSystem;
}
bool
nsPerformanceGroupDetails::IsAddon() const {
return mAddonId.Length() != 0;
}
bool
nsPerformanceGroupDetails::IsWindow() const {
return mWindowId != 0;
}
/* ------------------------------------------------------
*
* struct nsPerformanceStats
*
*/
class nsPerformanceStats: public nsIPerformanceStats,
public nsPerformanceGroupDetails
{
public:
nsPerformanceStats(const nsAString& aName,
const nsAString& aGroupId,
const nsAString& aAddonId,
const uint64_t aWindowId,
const uint64_t aProcessId,
const bool aIsSystem,
const PerformanceData& aPerformanceData)
: nsPerformanceGroupDetails(aName, aGroupId, aAddonId, aWindowId, aProcessId, aIsSystem)
, mPerformanceData(aPerformanceData)
{
}
nsPerformanceStats(const nsPerformanceGroupDetails& item,
const PerformanceData& aPerformanceData)
: nsPerformanceGroupDetails(item)
, mPerformanceData(aPerformanceData)
{
}
NS_DECL_ISUPPORTS
NS_IMETHOD GetName(nsAString& aName) override {
aName.Assign(nsPerformanceGroupDetails::Name());
return NS_OK;
};
NS_IMETHOD GetGroupId(nsAString& aGroupId) override {
aGroupId.Assign(nsPerformanceGroupDetails::GroupId());
return NS_OK;
};
NS_IMETHOD GetAddonId(nsAString& aAddonId) override {
aAddonId.Assign(nsPerformanceGroupDetails::AddonId());
return NS_OK;
};
NS_IMETHOD GetWindowId(uint64_t *aWindowId) override {
*aWindowId = nsPerformanceGroupDetails::WindowId();
return NS_OK;
}
NS_IMETHOD GetIsSystem(bool *_retval) override {
*_retval = nsPerformanceGroupDetails::IsSystem();
return NS_OK;
}
NS_IMETHOD GetTotalUserTime(uint64_t *aTotalUserTime) override {
*aTotalUserTime = mPerformanceData.mTotalUserTime;
return NS_OK;
};
NS_IMETHOD GetTotalSystemTime(uint64_t *aTotalSystemTime) override {
*aTotalSystemTime = mPerformanceData.mTotalSystemTime;
return NS_OK;
};
NS_IMETHOD GetTotalCPOWTime(uint64_t *aCpowTime) override {
*aCpowTime = mPerformanceData.mTotalCPOWTime;
return NS_OK;
};
NS_IMETHOD GetTicks(uint64_t *aTicks) override {
*aTicks = mPerformanceData.mTicks;
return NS_OK;
};
NS_IMETHOD GetDurations(uint32_t *aCount, uint64_t **aNumberOfOccurrences) override {
const size_t length = mozilla::ArrayLength(mPerformanceData.mDurations);
if (aCount) {
*aCount = length;
}
*aNumberOfOccurrences = new uint64_t[length];
for (size_t i = 0; i < length; ++i) {
(*aNumberOfOccurrences)[i] = mPerformanceData.mDurations[i];
}
return NS_OK;
};
NS_IMETHODIMP GetProcessId(uint64_t* processId) override {
*processId = nsPerformanceGroupDetails::ProcessId();
return NS_OK;
}
private:
PerformanceData mPerformanceData;
virtual ~nsPerformanceStats() {}
};
NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats)
/* ------------------------------------------------------
*
* struct nsPerformanceSnapshot
*
*/
class nsPerformanceSnapshot : public nsIPerformanceSnapshot
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPERFORMANCESNAPSHOT
nsPerformanceSnapshot() {}
/**
* Append statistics to the list of components data.
*/
void AppendComponentsStats(nsIPerformanceStats* stats);
/**
* Set the statistics attached to process data.
*/
void SetProcessStats(nsIPerformanceStats* group);
private:
virtual ~nsPerformanceSnapshot() {}
private:
/**
* The data for all components.
*/
nsCOMArray<nsIPerformanceStats> mComponentsData;
/**
* The data for the process.
*/
nsCOMPtr<nsIPerformanceStats> mProcessData;
};
NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot)
NS_IMETHODIMP
nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents)
{
const size_t length = mComponentsData.Length();
nsCOMPtr<nsIMutableArray> components = do_CreateInstance(NS_ARRAY_CONTRACTID);
for (size_t i = 0; i < length; ++i) {
nsCOMPtr<nsIPerformanceStats> stats = mComponentsData[i];
mozilla::DebugOnly<nsresult> rv = components->AppendElement(stats, false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
components.forget(aComponents);
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess)
{
NS_IF_ADDREF(*aProcess = mProcessData);
return NS_OK;
}
void
nsPerformanceSnapshot::AppendComponentsStats(nsIPerformanceStats* stats)
{
mComponentsData.AppendElement(stats);
}
void
nsPerformanceSnapshot::SetProcessStats(nsIPerformanceStats* stats)
{
mProcessData = stats;
}
/* ------------------------------------------------------
*
* class nsPerformanceStatsService
*
*/
NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver)
nsPerformanceStatsService::nsPerformanceStatsService()
: mIsAvailable(false)
#if defined(XP_WIN)
, mProcessId(GetCurrentProcessId())
#else
, mProcessId(getpid())
#endif
, mRuntime(xpc::GetJSRuntime())
, mUIdCounter(0)
, mTopGroup(nsPerformanceGroup::Make(mRuntime,
this,
NS_LITERAL_STRING("<process>"), // name
NS_LITERAL_STRING(""), // addonid
0, // windowId
mProcessId,
true, // isSystem
nsPerformanceGroup::GroupScope::RUNTIME // scope
))
, mProcessStayed(0)
, mProcessMoved(0)
, mProcessUpdateCounter(0)
, mIsMonitoringPerCompartment(false)
{ }
nsPerformanceStatsService::~nsPerformanceStatsService()
{ }
/**
* Clean up the service.
*
* Called during shutdown. Idempotent.
*/
void
nsPerformanceStatsService::Dispose()
{
// Make sure that we do not accidentally destroy `this` while we are
// cleaning up back references.
RefPtr<nsPerformanceStatsService> kungFuDeathGrip(this);
mIsAvailable = false;
// Disconnect from nsIObserverService.
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, "profile-before-change");
obs->RemoveObserver(this, "quit-application");
obs->RemoveObserver(this, "quit-application-granted");
obs->RemoveObserver(this, "content-child-shutdown");
obs->RemoveObserver(this, "xpcom-will-shutdown");
}
// Clear up and disconnect from JSAPI.
js::DisposePerformanceMonitoring(mRuntime);
mozilla::Unused << js::SetStopwatchIsMonitoringCPOW(mRuntime, false);
mozilla::Unused << js::SetStopwatchIsMonitoringJank(mRuntime, false);
mozilla::Unused << js::SetStopwatchStartCallback(mRuntime, nullptr, nullptr);
mozilla::Unused << js::SetStopwatchCommitCallback(mRuntime, nullptr, nullptr);
mozilla::Unused << js::SetGetPerformanceGroupsCallback(mRuntime, nullptr, nullptr);
// At this stage, the JS VM may still be holding references to
// instances of PerformanceGroup on the stack. To let the service be
// collected, we need to break the references from these groups to
// `this`.
mTopGroup->Dispose();
mTopGroup = nullptr;
// Copy references to the groups to a vector to ensure that we do
// not modify the hashtable while iterating it.
GroupVector groups;
for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
groups.append(iter.Get()->GetKey());
}
for (auto iter = groups.begin(); iter < groups.end(); iter++) {
RefPtr<nsPerformanceGroup> group = *iter;
group->Dispose();
}
// Any remaining references to PerformanceGroup will be released as
// the VM unrolls the stack. If there are any nested event loops,
// this may take time.
}
nsresult
nsPerformanceStatsService::Init()
{
nsresult rv = InitInternal();
if (NS_FAILED(rv)) {
// Attempt to clean up.
Dispose();
}
return rv;
}
nsresult
nsPerformanceStatsService::InitInternal()
{
// Make sure that we release everything during shutdown.
// We are a bit defensive here, as we know that some strange behavior can break the
// regular shutdown order.
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->AddObserver(this, "profile-before-change", false);
obs->AddObserver(this, "quit-application-granted", false);
obs->AddObserver(this, "quit-application", false);
obs->AddObserver(this, "content-child-shutdown", false);
obs->AddObserver(this, "xpcom-will-shutdown", false);
}
// Connect to JSAPI.
if (!js::SetStopwatchStartCallback(mRuntime, StopwatchStartCallback, this)) {
return NS_ERROR_UNEXPECTED;
}
if (!js::SetStopwatchCommitCallback(mRuntime, StopwatchCommitCallback, this)) {
return NS_ERROR_UNEXPECTED;
}
if (!js::SetGetPerformanceGroupsCallback(mRuntime, GetPerformanceGroupsCallback, this)) {
return NS_ERROR_UNEXPECTED;
}
mTopGroup->setIsActive(true);
mIsAvailable = true;
return NS_OK;
}
// Observe shutdown events.
NS_IMETHODIMP
nsPerformanceStatsService::Observe(nsISupports *aSubject, const char *aTopic,
const char16_t *aData)
{
MOZ_ASSERT(strcmp(aTopic, "profile-before-change") == 0
|| strcmp(aTopic, "quit-application") == 0
|| strcmp(aTopic, "quit-application-granted") == 0
|| strcmp(aTopic, "content-child-shutdown") == 0
|| strcmp(aTopic, "xpcom-will-shutdown") == 0);
Dispose();
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
{
if (!mIsAvailable) {
return NS_ERROR_NOT_AVAILABLE;
}
JSRuntime *runtime = JS_GetRuntime(cx);
*aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(runtime);
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive)
{
if (!mIsAvailable) {
return NS_ERROR_NOT_AVAILABLE;
}
JSRuntime *runtime = JS_GetRuntime(cx);
if (!js::SetStopwatchIsMonitoringCPOW(runtime, aIsStopwatchActive)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive)
{
if (!mIsAvailable) {
return NS_ERROR_NOT_AVAILABLE;
}
JSRuntime *runtime = JS_GetRuntime(cx);
*aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(runtime);
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive)
{
if (!mIsAvailable) {
return NS_ERROR_NOT_AVAILABLE;
}
JSRuntime *runtime = JS_GetRuntime(cx);
if (!js::SetStopwatchIsMonitoringJank(runtime, aIsStopwatchActive)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext*, bool *aIsMonitoringPerCompartment)
{
if (!mIsAvailable) {
return NS_ERROR_NOT_AVAILABLE;
}
*aIsMonitoringPerCompartment = mIsMonitoringPerCompartment;
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext*, bool aIsMonitoringPerCompartment)
{
if (!mIsAvailable) {
return NS_ERROR_NOT_AVAILABLE;
}
if (aIsMonitoringPerCompartment == mIsMonitoringPerCompartment) {
return NS_OK;
}
// Relatively slow update: walk the entire lost of performance groups,
// update the active flag of those that have changed.
//
// Alternative strategies could be envisioned to make the update
// much faster, at the expense of the speed of calling `isActive()`,
// (e.g. deferring `isActive()` to the nsPerformanceStatsService),
// but we expect that `isActive()` can be called thousands of times
// per second, while `SetIsMonitoringPerCompartment` is not called
// at all during most Firefox runs.
for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
RefPtr<nsPerformanceGroup> group = iter.Get()->GetKey();
if (group->Scope() == nsPerformanceGroup::GroupScope::COMPARTMENT) {
group->setIsActive(aIsMonitoringPerCompartment);
}
}
mIsMonitoringPerCompartment = aIsMonitoringPerCompartment;
return NS_OK;
}
nsresult
nsPerformanceStatsService::UpdateTelemetry()
{
// Promote everything to floating-point explicitly before dividing.
const double processStayed = mProcessStayed;
const double processMoved = mProcessMoved;
if (processStayed <= 0 || processMoved <= 0 || processStayed + processMoved <= 0) {
// Overflow/underflow/nothing to report
return NS_OK;
}
const double proportion = (100 * processStayed) / (processStayed + processMoved);
if (proportion < 0 || proportion > 100) {
// Overflow/underflow
return NS_OK;
}
mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED, (uint32_t)proportion);
return NS_OK;
}
/* static */ nsIPerformanceStats*
nsPerformanceStatsService::GetStatsForGroup(const js::PerformanceGroup* group)
{
return GetStatsForGroup(nsPerformanceGroup::Get(group));
}
/* static */ nsIPerformanceStats*
nsPerformanceStatsService::GetStatsForGroup(const nsPerformanceGroup* group)
{
return new nsPerformanceStats(*group, group->data);
}
NS_IMETHODIMP
nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
{
if (!mIsAvailable) {
return NS_ERROR_NOT_AVAILABLE;
}
RefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
snapshot->SetProcessStats(GetStatsForGroup(mTopGroup));
for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
auto* entry = iter.Get();
nsPerformanceGroup* group = entry->GetKey();
if (group->isActive()) {
snapshot->AppendComponentsStats(GetStatsForGroup(group));
}
}
js::GetPerfMonitoringTestCpuRescheduling(JS_GetRuntime(cx), &mProcessStayed, &mProcessMoved);
if (++mProcessUpdateCounter % 10 == 0) {
mozilla::Unused << UpdateTelemetry();
}
snapshot.forget(aSnapshot);
return NS_OK;
}
uint64_t
nsPerformanceStatsService::GetNextId() {
return ++mUIdCounter;
}
/* static*/ bool
nsPerformanceStatsService::GetPerformanceGroupsCallback(JSContext* cx, JSGroupVector& out, void* closure) {
RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
return self->GetPerformanceGroups(cx, out);
}
bool
nsPerformanceStatsService::GetPerformanceGroups(JSContext* cx, JSGroupVector& out) {
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
// While it is possible for a compartment to have no global
// (e.g. atoms), this compartment is not very interesting for us.
return true;
}
// All compartments belong to the top group.
out.append(mTopGroup);
nsAutoString name;
CompartmentName(cx, global, name);
bool isSystem = nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
// Find out if the compartment is executed by an add-on. If so, its
// duration should count towards the total duration of the add-on.
JSAddonId* jsaddonId = AddonIdOfObject(global);
nsString addonId;
if (jsaddonId) {
AssignJSFlatString(addonId, (JSFlatString*)jsaddonId);
auto entry = mAddonIdToGroup.PutEntry(addonId);
if (!entry->mGroup) {
nsString addonName = name;
addonName.AppendLiteral(" (as addon ");
addonName.Append(addonId);
addonName.AppendLiteral(")");
entry->mGroup =
nsPerformanceGroup::Make(mRuntime, this,
addonName, addonId, 0,
mProcessId, isSystem,
nsPerformanceGroup::GroupScope::ADDON);
}
out.append(entry->mGroup);
}
// Find out if the compartment is executed by a window. If so, its
// duration should count towards the total duration of the window.
nsCOMPtr<nsPIDOMWindow> ptop = GetPrivateWindow(cx);
uint64_t windowId = 0;
if (ptop) {
windowId = ptop->WindowID();
auto entry = mWindowIdToGroup.PutEntry(windowId);
if (!entry->mGroup) {
nsString windowName = name;
windowName.AppendLiteral(" (as window ");
windowName.AppendInt(windowId);
windowName.AppendLiteral(")");
entry->mGroup =
nsPerformanceGroup::Make(mRuntime, this,
windowName, EmptyString(), windowId,
mProcessId, isSystem,
nsPerformanceGroup::GroupScope::WINDOW);
}
out.append(entry->mGroup);
}
// All compartments have their own group.
auto group =
nsPerformanceGroup::Make(mRuntime, this,
name, addonId, windowId,
mProcessId, isSystem,
nsPerformanceGroup::GroupScope::COMPARTMENT);
out.append(group);
return true;
}
/*static*/ bool
nsPerformanceStatsService::StopwatchStartCallback(uint64_t iteration, void* closure) {
RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
return self->StopwatchStart(iteration);
}
bool
nsPerformanceStatsService::StopwatchStart(uint64_t iteration) {
mIteration = iteration;
nsresult rv = GetResources(&mUserTimeStart, &mSystemTimeStart);
if (NS_FAILED(rv)) {
return false;
}
return true;
}
/*static*/ bool
nsPerformanceStatsService::StopwatchCommitCallback(uint64_t iteration, JSGroupVector& recentGroups, void* closure) {
RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
return self->StopwatchCommit(iteration, recentGroups);
}
bool
nsPerformanceStatsService::StopwatchCommit(uint64_t iteration, JSGroupVector& recentGroups)
{
MOZ_ASSERT(iteration == mIteration);
MOZ_ASSERT(recentGroups.length() > 0);
uint64_t userTimeStop, systemTimeStop;
nsresult rv = GetResources(&userTimeStop, &systemTimeStop);
if (NS_FAILED(rv)) {
return false;
}
// `GetResources` is not guaranteed to be monotonic, so round up
// any negative result to 0 milliseconds.
uint64_t userTimeDelta = 0;
if (userTimeStop > mUserTimeStart)
userTimeDelta = userTimeStop - mUserTimeStart;
uint64_t systemTimeDelta = 0;
if (systemTimeStop > mSystemTimeStart)
systemTimeDelta = systemTimeStop - mSystemTimeStart;
MOZ_ASSERT(mTopGroup->isUsedInThisIteration());
const uint64_t totalRecentCycles = mTopGroup->recentCycles(iteration);
// We should only reach this stage if `group` has had some activity.
MOZ_ASSERT(mTopGroup->recentTicks(iteration) > 0);
for (auto iter = recentGroups.begin(); iter != recentGroups.end(); ++iter) {
RefPtr<nsPerformanceGroup> group = nsPerformanceGroup::Get(*iter);
CommitGroup(iteration, userTimeDelta, systemTimeDelta, totalRecentCycles, group);
}
// Make sure that `group` was treated along with the other items of `recentGroups`.
MOZ_ASSERT(!mTopGroup->isUsedInThisIteration());
MOZ_ASSERT(mTopGroup->recentTicks(iteration) == 0);
return true;
}
void
nsPerformanceStatsService::CommitGroup(uint64_t iteration,
uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta,
uint64_t totalCyclesDelta, nsPerformanceGroup* group) {
MOZ_ASSERT(group->isUsedInThisIteration());
const uint64_t ticksDelta = group->recentTicks(iteration);
const uint64_t cpowTimeDelta = group->recentCPOW(iteration);
const uint64_t cyclesDelta = group->recentCycles(iteration);
group->resetRecentData();
// We have now performed all cleanup and may `return` at any time without fear of leaks.
if (group->iteration() != iteration) {
// Stale data, don't commit.
return;
}
// When we add a group as changed, we immediately set its
// `recentTicks` from 0 to 1. If we have `ticksDelta == 0` at
// this stage, we have already called `resetRecentData` but we
// haven't removed it from the list.
MOZ_ASSERT(ticksDelta != 0);
MOZ_ASSERT(cyclesDelta <= totalCyclesDelta);
if (cyclesDelta == 0 || totalCyclesDelta == 0) {
// Nothing useful, don't commit.
return;
}
double proportion = (double)cyclesDelta / (double)totalCyclesDelta;
MOZ_ASSERT(proportion <= 1);
const uint64_t userTimeDelta = proportion * totalUserTimeDelta;
const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta;
group->data.mTotalUserTime += userTimeDelta;
group->data.mTotalSystemTime += systemTimeDelta;
group->data.mTotalCPOWTime += cpowTimeDelta;
group->data.mTicks += ticksDelta;
const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta + cpowTimeDelta;
uint64_t duration = 1000; // 1ms in µs
for (size_t i = 0;
i < mozilla::ArrayLength(group->data.mDurations) && duration < totalTimeDelta;
++i, duration *= 2) {
group->data.mDurations[i]++;
}
}
nsresult
nsPerformanceStatsService::GetResources(uint64_t* userTime,
uint64_t* systemTime) const {
MOZ_ASSERT(userTime);
MOZ_ASSERT(systemTime);
#if defined(XP_MACOSX)
// On MacOS X, to get we per-thread data, we need to
// reach into the kernel.
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
thread_basic_info_data_t info;
mach_port_t port = mach_thread_self();
kern_return_t err =
thread_info(/* [in] targeted thread*/ port,
/* [in] nature of information*/ THREAD_BASIC_INFO,
/* [out] thread information */ (thread_info_t)&info,
/* [inout] number of items */ &count);
// We do not need ability to communicate with the thread, so
// let's release the port.
mach_port_deallocate(mach_task_self(), port);
if (err != KERN_SUCCESS)
return NS_ERROR_FAILURE;
*userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
*systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;
#elif defined(XP_UNIX)
struct rusage rusage;
#if defined(RUSAGE_THREAD)
// Under Linux, we can obtain per-thread statistics
int err = getrusage(RUSAGE_THREAD, &rusage);
#else
// Under other Unices, we need to do with more noisy
// per-process statistics.
int err = getrusage(RUSAGE_SELF, &rusage);
#endif // defined(RUSAGE_THREAD)
if (err)
return NS_ERROR_FAILURE;
*userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
*systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;
#elif defined(XP_WIN)
// Under Windows, we can obtain per-thread statistics. Experience
// seems to suggest that they are not very accurate under Windows
// XP, though.
FILETIME creationFileTime; // Ignored
FILETIME exitFileTime; // Ignored
FILETIME kernelFileTime;
FILETIME userFileTime;
BOOL success = GetThreadTimes(GetCurrentThread(),
&creationFileTime, &exitFileTime,
&kernelFileTime, &userFileTime);
if (!success)
return NS_ERROR_FAILURE;
ULARGE_INTEGER kernelTimeInt;
kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
// Convert 100 ns to 1 us.
*systemTime = kernelTimeInt.QuadPart / 10;
ULARGE_INTEGER userTimeInt;
userTimeInt.LowPart = userFileTime.dwLowDateTime;
userTimeInt.HighPart = userFileTime.dwHighDateTime;
// Convert 100 ns to 1 us.
*userTime = userTimeInt.QuadPart / 10;
#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
return NS_OK;
}
/* ------------------------------------------------------
*
* Class nsPerformanceGroup
*
*/
/*static*/ nsPerformanceGroup*
nsPerformanceGroup::Make(JSRuntime* rt,
nsPerformanceStatsService* service,
const nsAString& name,
const nsAString& addonId,
uint64_t windowId,
uint64_t processId,
bool isSystem,
GroupScope scope)
{
nsString groupId;
::GenerateUniqueGroupId(rt, service->GetNextId(), processId, groupId);
return new nsPerformanceGroup(service, name, groupId, addonId, windowId, processId, isSystem, scope);
}
nsPerformanceGroup::nsPerformanceGroup(nsPerformanceStatsService* service,
const nsAString& name,
const nsAString& groupId,
const nsAString& addonId,
uint64_t windowId,
uint64_t processId,
bool isSystem,
GroupScope scope)
: nsPerformanceGroupDetails(name, groupId, addonId, windowId, processId, isSystem)
, mService(service)
, mScope(scope)
{
mozilla::Unused << mService->mGroups.PutEntry(this);
#if defined(DEBUG)
if (scope == GroupScope::ADDON) {
MOZ_ASSERT(IsAddon());
MOZ_ASSERT(!IsWindow());
} else if (scope == GroupScope::WINDOW) {
MOZ_ASSERT(IsWindow());
MOZ_ASSERT(!IsAddon());
} else if (scope == GroupScope::RUNTIME) {
MOZ_ASSERT(!IsWindow());
MOZ_ASSERT(!IsAddon());
}
#endif // defined(DEBUG)
setIsActive(mScope != GroupScope::COMPARTMENT || mService->mIsMonitoringPerCompartment);
}
void
nsPerformanceGroup::Dispose() {
if (!mService) {
// We have already called `Dispose()`.
return;
}
// Remove any reference to the service
RefPtr<nsPerformanceStatsService> service;
service.swap(mService);
service->mGroups.RemoveEntry(this);
if (mScope == GroupScope::ADDON) {
MOZ_ASSERT(IsAddon());
service->mAddonIdToGroup.RemoveEntry(AddonId());
} else if (mScope == GroupScope::WINDOW) {
MOZ_ASSERT(IsWindow());
service->mWindowIdToGroup.RemoveEntry(WindowId());
}
}
nsPerformanceGroup::~nsPerformanceGroup() {
Dispose();
}
nsPerformanceGroup::GroupScope
nsPerformanceGroup::Scope() const {
return mScope;
}