[JSC] Generator should have internal fields
[WebKit-https.git] / Source / WebCore / page / PerformanceMonitor.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "PerformanceMonitor.h"
28
29 #include "Chrome.h"
30 #include "ChromeClient.h"
31 #include "DeprecatedGlobalSettings.h"
32 #include "DiagnosticLoggingClient.h"
33 #include "DiagnosticLoggingKeys.h"
34 #include "Frame.h"
35 #include "Logging.h"
36 #include "Page.h"
37 #include "PerformanceLogging.h"
38 #include "RegistrableDomain.h"
39
40 namespace WebCore {
41
42 #define RELEASE_LOG_IF_ALLOWED(channel, fmt, ...) RELEASE_LOG_IF(m_page.isAlwaysOnLoggingAllowed(), channel, "%p - PerformanceMonitor::" fmt, this, ##__VA_ARGS__)
43
44 static const Seconds cpuUsageMeasurementDelay { 5_s };
45 static const Seconds postLoadCPUUsageMeasurementDuration { 10_s };
46 static const Seconds backgroundCPUUsageMeasurementDuration { 5_min };
47 static const Seconds cpuUsageSamplingInterval { 10_min };
48
49 static const Seconds memoryUsageMeasurementDelay { 10_s };
50
51 static const Seconds delayBeforeProcessMayBecomeInactive { 8_min };
52
53 static const double postPageLoadCPUUsageDomainReportingThreshold { 20.0 }; // Reporting pages using over 20% CPU is roughly equivalent to reporting the 10% worst pages.
54 #if !PLATFORM(IOS_FAMILY)
55 static const uint64_t postPageLoadMemoryUsageDomainReportingThreshold { 2048 * MB };
56 #endif
57
58 static inline ActivityStateForCPUSampling activityStateForCPUSampling(OptionSet<ActivityState::Flag> state)
59 {
60     if (!(state & ActivityState::IsVisible))
61         return ActivityStateForCPUSampling::NonVisible;
62     if (state & ActivityState::WindowIsActive)
63         return ActivityStateForCPUSampling::VisibleAndActive;
64     return ActivityStateForCPUSampling::VisibleNonActive;
65 }
66
67 PerformanceMonitor::PerformanceMonitor(Page& page)
68     : m_page(page)
69     , m_postPageLoadCPUUsageTimer(*this, &PerformanceMonitor::measurePostLoadCPUUsage)
70     , m_postBackgroundingCPUUsageTimer(*this, &PerformanceMonitor::measurePostBackgroundingCPUUsage)
71     , m_perActivityStateCPUUsageTimer(*this, &PerformanceMonitor::measurePerActivityStateCPUUsage)
72     , m_postPageLoadMemoryUsageTimer(*this, &PerformanceMonitor::measurePostLoadMemoryUsage)
73     , m_postBackgroundingMemoryUsageTimer(*this, &PerformanceMonitor::measurePostBackgroundingMemoryUsage)
74     , m_processMayBecomeInactiveTimer(*this, &PerformanceMonitor::processMayBecomeInactiveTimerFired)
75 {
76     ASSERT(!page.isUtilityPage());
77
78     if (DeprecatedGlobalSettings::isPerActivityStateCPUUsageMeasurementEnabled()) {
79         m_perActivityStateCPUTime = CPUTime::get();
80         m_perActivityStateCPUUsageTimer.startRepeating(cpuUsageSamplingInterval);
81     }
82 }
83
84 void PerformanceMonitor::didStartProvisionalLoad()
85 {
86     m_postLoadCPUTime = WTF::nullopt;
87     m_postPageLoadCPUUsageTimer.stop();
88     m_postPageLoadMemoryUsageTimer.stop();
89 }
90
91 void PerformanceMonitor::didFinishLoad()
92 {
93     // Only do post-load CPU usage measurement if there is a single Page in the process in order to reduce noise.
94     if (DeprecatedGlobalSettings::isPostLoadCPUUsageMeasurementEnabled() && m_page.isOnlyNonUtilityPage()) {
95         m_postLoadCPUTime = WTF::nullopt;
96         m_postPageLoadCPUUsageTimer.startOneShot(cpuUsageMeasurementDelay);
97     }
98
99     // Likewise for post-load memory usage measurement.
100     if (DeprecatedGlobalSettings::isPostLoadMemoryUsageMeasurementEnabled() && m_page.isOnlyNonUtilityPage())
101         m_postPageLoadMemoryUsageTimer.startOneShot(memoryUsageMeasurementDelay);
102 }
103
104 void PerformanceMonitor::activityStateChanged(OptionSet<ActivityState::Flag> oldState, OptionSet<ActivityState::Flag> newState)
105 {
106     auto changed = oldState ^ newState;
107     bool visibilityChanged = changed.contains(ActivityState::IsVisible);
108
109     // Measure CPU usage of pages when they are no longer visible.
110     if (DeprecatedGlobalSettings::isPostBackgroundingCPUUsageMeasurementEnabled() && visibilityChanged) {
111         m_postBackgroundingCPUTime = WTF::nullopt;
112         if (newState & ActivityState::IsVisible)
113             m_postBackgroundingCPUUsageTimer.stop();
114         else if (m_page.isOnlyNonUtilityPage())
115             m_postBackgroundingCPUUsageTimer.startOneShot(cpuUsageMeasurementDelay);
116     }
117
118     if (DeprecatedGlobalSettings::isPerActivityStateCPUUsageMeasurementEnabled()) {
119         // If visibility changed then report CPU usage right away because CPU usage is connected to visibility state.
120         auto oldActivityStateForCPUSampling = activityStateForCPUSampling(oldState);
121         if (oldActivityStateForCPUSampling != activityStateForCPUSampling(newState)) {
122             measureCPUUsageInActivityState(oldActivityStateForCPUSampling);
123             m_perActivityStateCPUUsageTimer.startRepeating(cpuUsageSamplingInterval);
124         }
125     }
126
127     if (DeprecatedGlobalSettings::isPostBackgroundingMemoryUsageMeasurementEnabled() && visibilityChanged) {
128         if (newState & ActivityState::IsVisible)
129             m_postBackgroundingMemoryUsageTimer.stop();
130         else if (m_page.isOnlyNonUtilityPage())
131             m_postBackgroundingMemoryUsageTimer.startOneShot(memoryUsageMeasurementDelay);
132     }
133
134     if (newState.containsAll({ ActivityState::IsVisible, ActivityState::WindowIsActive })) {
135         m_processMayBecomeInactive = false;
136         m_processMayBecomeInactiveTimer.stop();
137     } else if (!m_processMayBecomeInactive && !m_processMayBecomeInactiveTimer.isActive())
138         m_processMayBecomeInactiveTimer.startOneShot(delayBeforeProcessMayBecomeInactive);
139
140     updateProcessStateForMemoryPressure();
141 }
142
143 enum class ReportingReason { HighCPUUsage, HighMemoryUsage };
144 static void reportPageOverPostLoadResourceThreshold(Page& page, ReportingReason reason)
145 {
146 #if ENABLE(PUBLIC_SUFFIX_LIST)
147     auto* document = page.mainFrame().document();
148     if (!document)
149         return;
150
151     RegistrableDomain registrableDomain { document->url() };
152     if (registrableDomain.isEmpty())
153         return;
154
155     switch (reason) {
156     case ReportingReason::HighCPUUsage:
157         page.diagnosticLoggingClient().logDiagnosticMessageWithEnhancedPrivacy(DiagnosticLoggingKeys::domainCausingEnergyDrainKey(), registrableDomain.string(), ShouldSample::No);
158         break;
159     case ReportingReason::HighMemoryUsage:
160         page.diagnosticLoggingClient().logDiagnosticMessageWithEnhancedPrivacy(DiagnosticLoggingKeys::domainCausingJetsamKey(), registrableDomain.string(), ShouldSample::No);
161         break;
162     }
163 #else
164     UNUSED_PARAM(page);
165     UNUSED_PARAM(reason);
166 #endif
167 }
168
169 void PerformanceMonitor::measurePostLoadCPUUsage()
170 {
171     if (!m_page.isOnlyNonUtilityPage()) {
172         m_postLoadCPUTime = WTF::nullopt;
173         return;
174     }
175
176     if (!m_postLoadCPUTime) {
177         m_postLoadCPUTime = CPUTime::get();
178         if (m_postLoadCPUTime)
179             m_postPageLoadCPUUsageTimer.startOneShot(postLoadCPUUsageMeasurementDuration);
180         return;
181     }
182     Optional<CPUTime> cpuTime = CPUTime::get();
183     if (!cpuTime)
184         return;
185
186     double cpuUsage = cpuTime.value().percentageCPUUsageSince(*m_postLoadCPUTime);
187     RELEASE_LOG_IF_ALLOWED(PerformanceLogging, "measurePostLoadCPUUsage: Process was using %.1f%% CPU after the page load.", cpuUsage);
188     m_page.diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::postPageLoadCPUUsageKey(), DiagnosticLoggingKeys::foregroundCPUUsageToDiagnosticLoggingKey(cpuUsage), ShouldSample::No);
189
190     if (cpuUsage > postPageLoadCPUUsageDomainReportingThreshold)
191         reportPageOverPostLoadResourceThreshold(m_page, ReportingReason::HighCPUUsage);
192 }
193
194 void PerformanceMonitor::measurePostLoadMemoryUsage()
195 {
196     if (!m_page.isOnlyNonUtilityPage())
197         return;
198
199     Optional<uint64_t> memoryUsage = PerformanceLogging::physicalFootprint();
200     if (!memoryUsage)
201         return;
202
203     RELEASE_LOG_IF_ALLOWED(PerformanceLogging, "measurePostLoadMemoryUsage: Process was using %llu bytes of memory after the page load.", memoryUsage.value());
204     m_page.diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::postPageLoadMemoryUsageKey(), DiagnosticLoggingKeys::memoryUsageToDiagnosticLoggingKey(memoryUsage.value()), ShouldSample::No);
205
206     // On iOS, we report actual Jetsams instead.
207 #if !PLATFORM(IOS_FAMILY)
208     if (memoryUsage.value() > postPageLoadMemoryUsageDomainReportingThreshold)
209         reportPageOverPostLoadResourceThreshold(m_page, ReportingReason::HighMemoryUsage);
210 #endif
211 }
212
213 void PerformanceMonitor::measurePostBackgroundingMemoryUsage()
214 {
215     if (!m_page.isOnlyNonUtilityPage())
216         return;
217
218     Optional<uint64_t> memoryUsage = PerformanceLogging::physicalFootprint();
219     if (!memoryUsage)
220         return;
221
222     RELEASE_LOG_IF_ALLOWED(PerformanceLogging, "measurePostBackgroundingMemoryUsage: Process was using %llu bytes of memory after becoming non visible.", memoryUsage.value());
223     m_page.diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::postPageBackgroundingMemoryUsageKey(), DiagnosticLoggingKeys::memoryUsageToDiagnosticLoggingKey(memoryUsage.value()), ShouldSample::No);
224 }
225
226 void PerformanceMonitor::measurePostBackgroundingCPUUsage()
227 {
228     if (!m_page.isOnlyNonUtilityPage()) {
229         m_postBackgroundingCPUTime = WTF::nullopt;
230         return;
231     }
232
233     if (!m_postBackgroundingCPUTime) {
234         m_postBackgroundingCPUTime = CPUTime::get();
235         if (m_postBackgroundingCPUTime)
236             m_postBackgroundingCPUUsageTimer.startOneShot(backgroundCPUUsageMeasurementDuration);
237         return;
238     }
239     Optional<CPUTime> cpuTime = CPUTime::get();
240     if (!cpuTime)
241         return;
242
243     double cpuUsage = cpuTime.value().percentageCPUUsageSince(*m_postBackgroundingCPUTime);
244     RELEASE_LOG_IF_ALLOWED(PerformanceLogging, "measurePostBackgroundingCPUUsage: Process was using %.1f%% CPU after becoming non visible.", cpuUsage);
245     m_page.diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::postPageBackgroundingCPUUsageKey(), DiagnosticLoggingKeys::backgroundCPUUsageToDiagnosticLoggingKey(cpuUsage), ShouldSample::No);
246 }
247
248 void PerformanceMonitor::measurePerActivityStateCPUUsage()
249 {
250     measureCPUUsageInActivityState(activityStateForCPUSampling(m_page.activityState()));
251 }
252
253 #if !RELEASE_LOG_DISABLED
254
255 static inline const char* stringForCPUSamplingActivityState(ActivityStateForCPUSampling activityState)
256 {
257     switch (activityState) {
258     case ActivityStateForCPUSampling::NonVisible:
259         return "NonVisible";
260     case ActivityStateForCPUSampling::VisibleNonActive:
261         return "VisibleNonActive";
262     case ActivityStateForCPUSampling::VisibleAndActive:
263         return "VisibleAndActive";
264     }
265 }
266
267 #endif
268
269 void PerformanceMonitor::measureCPUUsageInActivityState(ActivityStateForCPUSampling activityState)
270 {
271     if (!m_page.isOnlyNonUtilityPage()) {
272         m_perActivityStateCPUTime = WTF::nullopt;
273         return;
274     }
275
276     if (!m_perActivityStateCPUTime) {
277         m_perActivityStateCPUTime = CPUTime::get();
278         return;
279     }
280
281     Optional<CPUTime> cpuTime = CPUTime::get();
282     if (!cpuTime) {
283         m_perActivityStateCPUTime = WTF::nullopt;
284         return;
285     }
286
287 #if !RELEASE_LOG_DISABLED
288     double cpuUsage = cpuTime.value().percentageCPUUsageSince(*m_perActivityStateCPUTime);
289     RELEASE_LOG_IF_ALLOWED(PerformanceLogging, "measureCPUUsageInActivityState: Process is using %.1f%% CPU in state: %s", cpuUsage, stringForCPUSamplingActivityState(activityState));
290 #endif
291     m_page.chrome().client().reportProcessCPUTime((cpuTime.value().systemTime + cpuTime.value().userTime) - (m_perActivityStateCPUTime.value().systemTime + m_perActivityStateCPUTime.value().userTime), activityState);
292
293     m_perActivityStateCPUTime = WTFMove(cpuTime);
294 }
295
296 void PerformanceMonitor::processMayBecomeInactiveTimerFired()
297 {
298     m_processMayBecomeInactive = true;
299     updateProcessStateForMemoryPressure();
300 }
301
302 void PerformanceMonitor::updateProcessStateForMemoryPressure()
303 {
304     bool hasAudiblePages = false;
305     bool hasCapturingPages = false;
306     bool mayBecomeInactive = true;
307
308     Page::forEachPage([&] (Page& page) {
309         if (!page.performanceMonitor())
310             return;
311         if (!page.performanceMonitor()->m_processMayBecomeInactive)
312             mayBecomeInactive = false;
313         if (page.activityState() & ActivityState::IsAudible)
314             hasAudiblePages = true;
315         if (page.activityState() & ActivityState::IsCapturingMedia)
316             hasCapturingPages = true;
317     });
318
319     bool isActiveProcess = !mayBecomeInactive || hasAudiblePages || hasCapturingPages;
320     MemoryPressureHandler::singleton().setProcessState(isActiveProcess ? WebsamProcessState::Active : WebsamProcessState::Inactive);
321 }
322
323 } // namespace WebCore