/* -*- 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 #include #else #include #endif // defined(XP_WIN) #if defined(XP_MACOSX) #include #include #include #include #include #include #elif defined(XP_UNIX) #include #include #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 GetPrivateWindow(JSContext* cx) { nsCOMPtr win = xpc::CurrentWindowOrNull(cx); if (!win) { return nullptr; } win = win->GetOuterWindow(); if (!win) { return nullptr; } nsCOMPtr top = win->GetTop(); if (!top) { return nullptr; } return top.forget(); } bool URLForGlobal(JSContext* cx, JS::Handle global, nsAString& url) { nsCOMPtr principal = nsContentUtils::ObjectPrincipal(global); if (!principal) { return false; } nsCOMPtr 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 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(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 mComponentsData; /** * The data for the process. */ nsCOMPtr mProcessData; }; NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot) NS_IMETHODIMP nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents) { const size_t length = mComponentsData.Length(); nsCOMPtr components = do_CreateInstance(NS_ARRAY_CONTRACTID); for (size_t i = 0; i < length; ++i) { nsCOMPtr stats = mComponentsData[i]; mozilla::DebugOnly 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(""), // 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 kungFuDeathGrip(this); // Disconnect from nsIObserverService. nsCOMPtr 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 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 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 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 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 self = reinterpret_cast(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 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 self = reinterpret_cast(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 self = reinterpret_cast(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 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 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; }