Files
tubestation/toolkit/components/perfmonitoring/nsPerformanceStats.cpp

998 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()
#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);
// 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");
}
// 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();
// 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);
}
// 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);
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);
Dispose();
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
{
JSRuntime *runtime = JS_GetRuntime(cx);
*aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(runtime);
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive)
{
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)
{
JSRuntime *runtime = JS_GetRuntime(cx);
*aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(runtime);
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive)
{
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)
{
*aIsMonitoringPerCompartment = mIsMonitoringPerCompartment;
return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext*, bool aIsMonitoringPerCompartment)
{
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)
{
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;
}