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 |
324 | |