326 lines
14 KiB
C++
326 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2017 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "PerformanceMonitor.h"
|
|
|
|
#include "Chrome.h"
|
|
#include "ChromeClient.h"
|
|
#include "DiagnosticLoggingClient.h"
|
|
#include "DiagnosticLoggingKeys.h"
|
|
#include "Frame.h"
|
|
#include "Logging.h"
|
|
#include "Page.h"
|
|
#include "PerformanceLogging.h"
|
|
#include "RegistrableDomain.h"
|
|
#include "Settings.h"
|
|
|
|
namespace WebCore {
|
|
|
|
#define PERFMONITOR_RELEASE_LOG(channel, fmt, ...) RELEASE_LOG(channel, "%p - PerformanceMonitor::" fmt, this, ##__VA_ARGS__)
|
|
|
|
static constexpr const Seconds cpuUsageMeasurementDelay { 5_s };
|
|
static constexpr const Seconds postLoadCPUUsageMeasurementDuration { 10_s };
|
|
static constexpr const Seconds backgroundCPUUsageMeasurementDuration { 5_min };
|
|
static constexpr const Seconds cpuUsageSamplingInterval { 10_min };
|
|
|
|
static constexpr const Seconds memoryUsageMeasurementDelay { 10_s };
|
|
|
|
static constexpr const Seconds delayBeforeProcessMayBecomeInactive { 8_min };
|
|
|
|
static constexpr const double postPageLoadCPUUsageDomainReportingThreshold { 20.0 }; // Reporting pages using over 20% CPU is roughly equivalent to reporting the 10% worst pages.
|
|
#if !PLATFORM(IOS_FAMILY)
|
|
static constexpr const uint64_t postPageLoadMemoryUsageDomainReportingThreshold { 2048 * MB };
|
|
#endif
|
|
|
|
static inline ActivityStateForCPUSampling activityStateForCPUSampling(OptionSet<ActivityState::Flag> state)
|
|
{
|
|
if (!(state & ActivityState::IsVisible))
|
|
return ActivityStateForCPUSampling::NonVisible;
|
|
if (state & ActivityState::WindowIsActive)
|
|
return ActivityStateForCPUSampling::VisibleAndActive;
|
|
return ActivityStateForCPUSampling::VisibleNonActive;
|
|
}
|
|
|
|
PerformanceMonitor::PerformanceMonitor(Page& page)
|
|
: m_page(page)
|
|
, m_postPageLoadCPUUsageTimer(*this, &PerformanceMonitor::measurePostLoadCPUUsage)
|
|
, m_postBackgroundingCPUUsageTimer(*this, &PerformanceMonitor::measurePostBackgroundingCPUUsage)
|
|
, m_perActivityStateCPUUsageTimer(*this, &PerformanceMonitor::measurePerActivityStateCPUUsage)
|
|
, m_postPageLoadMemoryUsageTimer(*this, &PerformanceMonitor::measurePostLoadMemoryUsage)
|
|
, m_postBackgroundingMemoryUsageTimer(*this, &PerformanceMonitor::measurePostBackgroundingMemoryUsage)
|
|
, m_processMayBecomeInactiveTimer(*this, &PerformanceMonitor::processMayBecomeInactiveTimerFired)
|
|
{
|
|
ASSERT(!page.isUtilityPage());
|
|
|
|
if (page.settings().isPerActivityStateCPUUsageMeasurementEnabled()) {
|
|
m_perActivityStateCPUTime = CPUTime::get();
|
|
m_perActivityStateCPUUsageTimer.startRepeating(cpuUsageSamplingInterval);
|
|
}
|
|
}
|
|
|
|
void PerformanceMonitor::didStartProvisionalLoad()
|
|
{
|
|
m_postLoadCPUTime = std::nullopt;
|
|
m_postPageLoadCPUUsageTimer.stop();
|
|
m_postPageLoadMemoryUsageTimer.stop();
|
|
}
|
|
|
|
void PerformanceMonitor::didFinishLoad()
|
|
{
|
|
// Only do post-load CPU usage measurement if there is a single Page in the process in order to reduce noise.
|
|
if (m_page.settings().isPostLoadCPUUsageMeasurementEnabled() && m_page.isOnlyNonUtilityPage()) {
|
|
m_postLoadCPUTime = std::nullopt;
|
|
m_postPageLoadCPUUsageTimer.startOneShot(cpuUsageMeasurementDelay);
|
|
}
|
|
|
|
// Likewise for post-load memory usage measurement.
|
|
if (m_page.settings().isPostLoadMemoryUsageMeasurementEnabled() && m_page.isOnlyNonUtilityPage())
|
|
m_postPageLoadMemoryUsageTimer.startOneShot(memoryUsageMeasurementDelay);
|
|
}
|
|
|
|
void PerformanceMonitor::activityStateChanged(OptionSet<ActivityState::Flag> oldState, OptionSet<ActivityState::Flag> newState)
|
|
{
|
|
auto changed = oldState ^ newState;
|
|
bool visibilityChanged = changed.contains(ActivityState::IsVisible);
|
|
|
|
// Measure CPU usage of pages when they are no longer visible.
|
|
if (m_page.settings().isPostBackgroundingCPUUsageMeasurementEnabled() && visibilityChanged) {
|
|
m_postBackgroundingCPUTime = std::nullopt;
|
|
if (newState & ActivityState::IsVisible)
|
|
m_postBackgroundingCPUUsageTimer.stop();
|
|
else if (m_page.isOnlyNonUtilityPage())
|
|
m_postBackgroundingCPUUsageTimer.startOneShot(cpuUsageMeasurementDelay);
|
|
}
|
|
|
|
if (m_page.settings().isPerActivityStateCPUUsageMeasurementEnabled()) {
|
|
// If visibility changed then report CPU usage right away because CPU usage is connected to visibility state.
|
|
auto oldActivityStateForCPUSampling = activityStateForCPUSampling(oldState);
|
|
if (oldActivityStateForCPUSampling != activityStateForCPUSampling(newState)) {
|
|
measureCPUUsageInActivityState(oldActivityStateForCPUSampling);
|
|
m_perActivityStateCPUUsageTimer.startRepeating(cpuUsageSamplingInterval);
|
|
}
|
|
}
|
|
|
|
if (m_page.settings().isPostBackgroundingMemoryUsageMeasurementEnabled() && visibilityChanged) {
|
|
if (newState & ActivityState::IsVisible)
|
|
m_postBackgroundingMemoryUsageTimer.stop();
|
|
else if (m_page.isOnlyNonUtilityPage())
|
|
m_postBackgroundingMemoryUsageTimer.startOneShot(memoryUsageMeasurementDelay);
|
|
}
|
|
|
|
if (newState.contains(ActivityState::IsVisible)) {
|
|
m_processMayBecomeInactive = false;
|
|
m_processMayBecomeInactiveTimer.stop();
|
|
} else if (!m_processMayBecomeInactive && !m_processMayBecomeInactiveTimer.isActive())
|
|
m_processMayBecomeInactiveTimer.startOneShot(delayBeforeProcessMayBecomeInactive);
|
|
|
|
updateProcessStateForMemoryPressure();
|
|
}
|
|
|
|
enum class ReportingReason { HighCPUUsage, HighMemoryUsage };
|
|
static void reportPageOverPostLoadResourceThreshold(Page& page, ReportingReason reason)
|
|
{
|
|
#if ENABLE(PUBLIC_SUFFIX_LIST)
|
|
auto* document = page.mainFrame().document();
|
|
if (!document)
|
|
return;
|
|
|
|
RegistrableDomain registrableDomain { document->url() };
|
|
if (registrableDomain.isEmpty())
|
|
return;
|
|
|
|
switch (reason) {
|
|
case ReportingReason::HighCPUUsage:
|
|
page.diagnosticLoggingClient().logDiagnosticMessageWithEnhancedPrivacy(DiagnosticLoggingKeys::domainCausingEnergyDrainKey(), registrableDomain.string(), ShouldSample::No);
|
|
break;
|
|
case ReportingReason::HighMemoryUsage:
|
|
page.diagnosticLoggingClient().logDiagnosticMessageWithEnhancedPrivacy(DiagnosticLoggingKeys::domainCausingJetsamKey(), registrableDomain.string(), ShouldSample::No);
|
|
break;
|
|
}
|
|
#else
|
|
UNUSED_PARAM(page);
|
|
UNUSED_PARAM(reason);
|
|
#endif
|
|
}
|
|
|
|
void PerformanceMonitor::measurePostLoadCPUUsage()
|
|
{
|
|
if (!m_page.isOnlyNonUtilityPage()) {
|
|
m_postLoadCPUTime = std::nullopt;
|
|
return;
|
|
}
|
|
|
|
if (!m_postLoadCPUTime) {
|
|
m_postLoadCPUTime = CPUTime::get();
|
|
if (m_postLoadCPUTime)
|
|
m_postPageLoadCPUUsageTimer.startOneShot(postLoadCPUUsageMeasurementDuration);
|
|
return;
|
|
}
|
|
std::optional<CPUTime> cpuTime = CPUTime::get();
|
|
if (!cpuTime)
|
|
return;
|
|
|
|
double cpuUsage = cpuTime.value().percentageCPUUsageSince(*m_postLoadCPUTime);
|
|
PERFMONITOR_RELEASE_LOG(PerformanceLogging, "measurePostLoadCPUUsage: Process was using %.1f%% CPU after the page load.", cpuUsage);
|
|
m_page.diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::postPageLoadCPUUsageKey(), DiagnosticLoggingKeys::foregroundCPUUsageToDiagnosticLoggingKey(cpuUsage), ShouldSample::No);
|
|
|
|
if (cpuUsage > postPageLoadCPUUsageDomainReportingThreshold)
|
|
reportPageOverPostLoadResourceThreshold(m_page, ReportingReason::HighCPUUsage);
|
|
}
|
|
|
|
void PerformanceMonitor::measurePostLoadMemoryUsage()
|
|
{
|
|
if (!m_page.isOnlyNonUtilityPage())
|
|
return;
|
|
|
|
std::optional<uint64_t> memoryUsage = PerformanceLogging::physicalFootprint();
|
|
if (!memoryUsage)
|
|
return;
|
|
|
|
PERFMONITOR_RELEASE_LOG(PerformanceLogging, "measurePostLoadMemoryUsage: Process was using %llu bytes of memory after the page load.", memoryUsage.value());
|
|
m_page.diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::postPageLoadMemoryUsageKey(), DiagnosticLoggingKeys::memoryUsageToDiagnosticLoggingKey(memoryUsage.value()), ShouldSample::No);
|
|
|
|
// On iOS, we report actual Jetsams instead.
|
|
#if !PLATFORM(IOS_FAMILY)
|
|
if (memoryUsage.value() > postPageLoadMemoryUsageDomainReportingThreshold)
|
|
reportPageOverPostLoadResourceThreshold(m_page, ReportingReason::HighMemoryUsage);
|
|
#endif
|
|
}
|
|
|
|
void PerformanceMonitor::measurePostBackgroundingMemoryUsage()
|
|
{
|
|
if (!m_page.isOnlyNonUtilityPage())
|
|
return;
|
|
|
|
std::optional<uint64_t> memoryUsage = PerformanceLogging::physicalFootprint();
|
|
if (!memoryUsage)
|
|
return;
|
|
|
|
PERFMONITOR_RELEASE_LOG(PerformanceLogging, "measurePostBackgroundingMemoryUsage: Process was using %llu bytes of memory after becoming non visible.", memoryUsage.value());
|
|
m_page.diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::postPageBackgroundingMemoryUsageKey(), DiagnosticLoggingKeys::memoryUsageToDiagnosticLoggingKey(memoryUsage.value()), ShouldSample::No);
|
|
}
|
|
|
|
void PerformanceMonitor::measurePostBackgroundingCPUUsage()
|
|
{
|
|
if (!m_page.isOnlyNonUtilityPage()) {
|
|
m_postBackgroundingCPUTime = std::nullopt;
|
|
return;
|
|
}
|
|
|
|
if (!m_postBackgroundingCPUTime) {
|
|
m_postBackgroundingCPUTime = CPUTime::get();
|
|
if (m_postBackgroundingCPUTime)
|
|
m_postBackgroundingCPUUsageTimer.startOneShot(backgroundCPUUsageMeasurementDuration);
|
|
return;
|
|
}
|
|
std::optional<CPUTime> cpuTime = CPUTime::get();
|
|
if (!cpuTime)
|
|
return;
|
|
|
|
double cpuUsage = cpuTime.value().percentageCPUUsageSince(*m_postBackgroundingCPUTime);
|
|
PERFMONITOR_RELEASE_LOG(PerformanceLogging, "measurePostBackgroundingCPUUsage: Process was using %.1f%% CPU after becoming non visible.", cpuUsage);
|
|
m_page.diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::postPageBackgroundingCPUUsageKey(), DiagnosticLoggingKeys::backgroundCPUUsageToDiagnosticLoggingKey(cpuUsage), ShouldSample::No);
|
|
}
|
|
|
|
void PerformanceMonitor::measurePerActivityStateCPUUsage()
|
|
{
|
|
measureCPUUsageInActivityState(activityStateForCPUSampling(m_page.activityState()));
|
|
}
|
|
|
|
#if !RELEASE_LOG_DISABLED
|
|
|
|
static inline const char* stringForCPUSamplingActivityState(ActivityStateForCPUSampling activityState)
|
|
{
|
|
switch (activityState) {
|
|
case ActivityStateForCPUSampling::NonVisible:
|
|
return "NonVisible";
|
|
case ActivityStateForCPUSampling::VisibleNonActive:
|
|
return "VisibleNonActive";
|
|
case ActivityStateForCPUSampling::VisibleAndActive:
|
|
return "VisibleAndActive";
|
|
}
|
|
RELEASE_ASSERT_NOT_REACHED();
|
|
return "";
|
|
}
|
|
|
|
#endif
|
|
|
|
void PerformanceMonitor::measureCPUUsageInActivityState(ActivityStateForCPUSampling activityState)
|
|
{
|
|
if (!m_page.isOnlyNonUtilityPage()) {
|
|
m_perActivityStateCPUTime = std::nullopt;
|
|
return;
|
|
}
|
|
|
|
if (!m_perActivityStateCPUTime) {
|
|
m_perActivityStateCPUTime = CPUTime::get();
|
|
return;
|
|
}
|
|
|
|
std::optional<CPUTime> cpuTime = CPUTime::get();
|
|
if (!cpuTime) {
|
|
m_perActivityStateCPUTime = std::nullopt;
|
|
return;
|
|
}
|
|
|
|
#if !RELEASE_LOG_DISABLED
|
|
double cpuUsage = cpuTime.value().percentageCPUUsageSince(*m_perActivityStateCPUTime);
|
|
PERFMONITOR_RELEASE_LOG(PerformanceLogging, "measureCPUUsageInActivityState: Process is using %.1f%% CPU in state: %s", cpuUsage, stringForCPUSamplingActivityState(activityState));
|
|
#endif
|
|
m_page.chrome().client().reportProcessCPUTime((cpuTime.value().systemTime + cpuTime.value().userTime) - (m_perActivityStateCPUTime.value().systemTime + m_perActivityStateCPUTime.value().userTime), activityState);
|
|
|
|
m_perActivityStateCPUTime = WTFMove(cpuTime);
|
|
}
|
|
|
|
void PerformanceMonitor::processMayBecomeInactiveTimerFired()
|
|
{
|
|
m_processMayBecomeInactive = true;
|
|
updateProcessStateForMemoryPressure();
|
|
}
|
|
|
|
void PerformanceMonitor::updateProcessStateForMemoryPressure()
|
|
{
|
|
bool hasAudiblePages = false;
|
|
bool hasCapturingPages = false;
|
|
bool mayBecomeInactive = true;
|
|
|
|
Page::forEachPage([&] (Page& page) {
|
|
if (!page.performanceMonitor())
|
|
return;
|
|
if (!page.performanceMonitor()->m_processMayBecomeInactive)
|
|
mayBecomeInactive = false;
|
|
if (page.activityState() & ActivityState::IsAudible)
|
|
hasAudiblePages = true;
|
|
if (page.activityState() & ActivityState::IsCapturingMedia)
|
|
hasCapturingPages = true;
|
|
});
|
|
|
|
bool isActiveProcess = !mayBecomeInactive || hasAudiblePages || hasCapturingPages;
|
|
MemoryPressureHandler::singleton().setProcessState(isActiveProcess ? WebsamProcessState::Active : WebsamProcessState::Inactive);
|
|
}
|
|
|
|
} // namespace WebCore
|