Web Inspector: "Reload Web Inspector" button no longer partially works
[WebKit-https.git] / Source / WebKit / UIProcess / WebResourceLoadStatisticsTelemetry.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 "WebResourceLoadStatisticsTelemetry.h"
28
29 #include "ResourceLoadStatisticsMemoryStore.h"
30 #include "WebProcessPool.h"
31 #include "WebProcessProxy.h"
32 #include <WebCore/DiagnosticLoggingKeys.h>
33 #include <WebCore/ResourceLoadStatistics.h>
34 #include <wtf/MainThread.h>
35 #include <wtf/NeverDestroyed.h>
36 #include <wtf/RunLoop.h>
37 #include <wtf/text/StringBuilder.h>
38
39 namespace WebKit {
40 using namespace WebCore;
41
42 const unsigned minimumPrevalentResourcesForTelemetry = 3;
43 const unsigned significantFiguresForLoggedValues = 3;
44 static bool notifyPagesWhenTelemetryWasCaptured = false;
45
46 struct PrevalentResourceTelemetry {
47     unsigned numberOfTimesDataRecordsRemoved;
48     bool hasHadUserInteraction;
49     unsigned daysSinceUserInteraction;
50     unsigned subframeUnderTopFrameOrigins;
51     unsigned subresourceUnderTopFrameOrigins;
52     unsigned subresourceUniqueRedirectsTo;
53     unsigned timesAccessedAsFirstPartyDueToUserInteraction;
54     unsigned timesAccessedAsFirstPartyDueToStorageAccessAPI;
55 };
56
57 static Vector<PrevalentResourceTelemetry> sortedPrevalentResourceTelemetry(const ResourceLoadStatisticsMemoryStore& store)
58 {
59     ASSERT(!RunLoop::isMain());
60     Vector<PrevalentResourceTelemetry> sorted;
61     store.processStatistics([&sorted] (auto& statistic) {
62         if (!statistic.isPrevalentResource)
63             return;
64
65         unsigned daysSinceUserInteraction = statistic.mostRecentUserInteractionTime <= WallTime() ? 0 : std::floor((WallTime::now() - statistic.mostRecentUserInteractionTime) / 24_h);
66         sorted.append(PrevalentResourceTelemetry {
67             statistic.dataRecordsRemoved,
68             statistic.hadUserInteraction,
69             daysSinceUserInteraction,
70             statistic.subframeUnderTopFrameOrigins.size(),
71             statistic.subresourceUnderTopFrameOrigins.size(),
72             statistic.subresourceUniqueRedirectsTo.size(),
73             statistic.timesAccessedAsFirstPartyDueToUserInteraction,
74             statistic.timesAccessedAsFirstPartyDueToStorageAccessAPI
75         });
76     });
77
78     if (sorted.size() < minimumPrevalentResourcesForTelemetry)
79         return { };
80
81     std::sort(sorted.begin(), sorted.end(), [](const PrevalentResourceTelemetry& a, const PrevalentResourceTelemetry& b) {
82         return a.subframeUnderTopFrameOrigins + a.subresourceUnderTopFrameOrigins + a.subresourceUniqueRedirectsTo >
83         b.subframeUnderTopFrameOrigins + b.subresourceUnderTopFrameOrigins + b.subresourceUniqueRedirectsTo;
84     });
85
86     return sorted;
87 }
88
89 static unsigned numberOfResourcesWithUserInteraction(const Vector<PrevalentResourceTelemetry>& resources, size_t begin, size_t end)
90 {
91     if (resources.isEmpty() || resources.size() < begin + 1 || resources.size() < end + 1)
92         return 0;
93     
94     unsigned result = 0;
95     for (size_t i = begin; i < end; ++i) {
96         if (resources[i].hasHadUserInteraction)
97             ++result;
98     }
99     
100     return result;
101 }
102     
103 static unsigned median(const Vector<unsigned>& v)
104 {
105     if (v.isEmpty())
106         return 0;
107     if (v.size() == 1)
108         return v[0];
109     
110     auto size = v.size();
111     auto middle = size / 2;
112     if (size % 2)
113         return v[middle];
114     return (v[middle - 1] + v[middle]) / 2;
115 }
116     
117 static unsigned median(const Vector<PrevalentResourceTelemetry>& v, unsigned begin, unsigned end, const WTF::Function<unsigned(const PrevalentResourceTelemetry& telemetry)>& statisticGetter)
118 {
119     if (v.isEmpty() || v.size() < begin + 1 || v.size() < end + 1)
120         return 0;
121     
122     Vector<unsigned> part;
123     part.reserveInitialCapacity(end - begin + 1);
124     for (unsigned i = begin; i <= end; ++i)
125         part.uncheckedAppend(statisticGetter(v[i]));
126     
127     return median(part);
128 }
129     
130 static WebPageProxy* nonEphemeralWebPageProxy()
131 {
132     auto processPools = WebProcessPool::allProcessPools();
133     if (processPools.isEmpty())
134         return nullptr;
135     
136     auto processPool = processPools[0];
137     if (!processPool)
138         return nullptr;
139     
140     for (auto& webProcess : processPool->processes()) {
141         for (auto& page : webProcess->pages()) {
142             if (page->sessionID().isEphemeral())
143                 continue;
144             return page;
145         }
146     }
147     return nullptr;
148 }
149     
150 static void submitTopList(unsigned numberOfResourcesFromTheTop, const Vector<PrevalentResourceTelemetry>& sortedPrevalentResources, const Vector<PrevalentResourceTelemetry>& sortedPrevalentResourcesWithoutUserInteraction, WebPageProxy& webPageProxy)
151 {
152     WTF::Function<unsigned(const PrevalentResourceTelemetry& telemetry)> subframeUnderTopFrameOriginsGetter = [] (auto& t) {
153         return t.subframeUnderTopFrameOrigins;
154     };
155     WTF::Function<unsigned(const PrevalentResourceTelemetry& telemetry)> subresourceUnderTopFrameOriginsGetter = [] (auto& t) {
156         return t.subresourceUnderTopFrameOrigins;
157     };
158     WTF::Function<unsigned(const PrevalentResourceTelemetry& telemetry)> subresourceUniqueRedirectsToGetter = [] (auto& t) {
159         return t.subresourceUniqueRedirectsTo;
160     };
161     WTF::Function<unsigned(const PrevalentResourceTelemetry& telemetry)> numberOfTimesDataRecordsRemovedGetter = [] (auto& t) {
162         return t.numberOfTimesDataRecordsRemoved;
163     };
164     WTF::Function<unsigned(const PrevalentResourceTelemetry& telemetry)> numberOfTimesAccessedAsFirstPartyDueToUserInteractionGetter = [] (auto& t) {
165         return t.timesAccessedAsFirstPartyDueToUserInteraction;
166     };
167     WTF::Function<unsigned(const PrevalentResourceTelemetry& telemetry)> numberOfTimesAccessedAsFirstPartyDueToStorageAccessAPIGetter = [] (auto& t) {
168         return t.timesAccessedAsFirstPartyDueToStorageAccessAPI;
169     };
170
171     unsigned topPrevalentResourcesWithUserInteraction = numberOfResourcesWithUserInteraction(sortedPrevalentResources, 0, numberOfResourcesFromTheTop - 1);
172     unsigned topSubframeUnderTopFrameOrigins = median(sortedPrevalentResourcesWithoutUserInteraction, 0, numberOfResourcesFromTheTop - 1, subframeUnderTopFrameOriginsGetter);
173     unsigned topSubresourceUnderTopFrameOrigins = median(sortedPrevalentResourcesWithoutUserInteraction, 0, numberOfResourcesFromTheTop - 1, subresourceUnderTopFrameOriginsGetter);
174     unsigned topSubresourceUniqueRedirectsTo = median(sortedPrevalentResourcesWithoutUserInteraction, 0, numberOfResourcesFromTheTop - 1, subresourceUniqueRedirectsToGetter);
175     unsigned topNumberOfTimesDataRecordsRemoved = median(sortedPrevalentResourcesWithoutUserInteraction, 0, numberOfResourcesFromTheTop - 1, numberOfTimesDataRecordsRemovedGetter);
176     unsigned topNumberOfTimesAccessedAsFirstPartyDueToUserInteraction = median(sortedPrevalentResourcesWithoutUserInteraction, 0, numberOfResourcesFromTheTop - 1, numberOfTimesAccessedAsFirstPartyDueToUserInteractionGetter);
177     unsigned topNumberOfTimesAccessedAsFirstPartyDueToStorageAccessAPI = median(sortedPrevalentResourcesWithoutUserInteraction, 0, numberOfResourcesFromTheTop - 1, numberOfTimesAccessedAsFirstPartyDueToStorageAccessAPIGetter);
178
179     StringBuilder preambleBuilder;
180     preambleBuilder.appendLiteral("top");
181     preambleBuilder.appendNumber(numberOfResourcesFromTheTop);
182     String descriptionPreamble = preambleBuilder.toString();
183     
184     webPageProxy.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), descriptionPreamble + "PrevalentResourcesWithUserInteraction",
185         topPrevalentResourcesWithUserInteraction, significantFiguresForLoggedValues, ShouldSample::No);
186     webPageProxy.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), descriptionPreamble + "SubframeUnderTopFrameOrigins",
187         topSubframeUnderTopFrameOrigins, significantFiguresForLoggedValues, ShouldSample::No);
188     webPageProxy.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), descriptionPreamble + "SubresourceUnderTopFrameOrigins",
189         topSubresourceUnderTopFrameOrigins, significantFiguresForLoggedValues, ShouldSample::No);
190     webPageProxy.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), descriptionPreamble + "SubresourceUniqueRedirectsTo",
191         topSubresourceUniqueRedirectsTo, significantFiguresForLoggedValues, ShouldSample::No);
192     webPageProxy.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), descriptionPreamble + "NumberOfTimesDataRecordsRemoved",
193         topNumberOfTimesDataRecordsRemoved, significantFiguresForLoggedValues, ShouldSample::No);
194     webPageProxy.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), descriptionPreamble + "NumberOfTimesAccessedAsFirstPartyDueToUserInteraction",
195         topNumberOfTimesAccessedAsFirstPartyDueToUserInteraction, significantFiguresForLoggedValues, ShouldSample::No);
196     webPageProxy.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), descriptionPreamble + "NumberOfTimesAccessedAsFirstPartyDueToStorageAccessAPI",
197         topNumberOfTimesAccessedAsFirstPartyDueToStorageAccessAPI, significantFiguresForLoggedValues, ShouldSample::No);
198 }
199     
200 static void submitTopLists(const Vector<PrevalentResourceTelemetry>& sortedPrevalentResources, const Vector<PrevalentResourceTelemetry>& sortedPrevalentResourcesWithoutUserInteraction, WebPageProxy& webPageProxy)
201 {
202     submitTopList(1, sortedPrevalentResources, sortedPrevalentResourcesWithoutUserInteraction, webPageProxy);
203     
204     if (sortedPrevalentResourcesWithoutUserInteraction.size() < 3)
205         return;
206     submitTopList(3, sortedPrevalentResources, sortedPrevalentResourcesWithoutUserInteraction, webPageProxy);
207     
208     if (sortedPrevalentResourcesWithoutUserInteraction.size() < 10)
209         return;
210     submitTopList(10, sortedPrevalentResources, sortedPrevalentResourcesWithoutUserInteraction, webPageProxy);
211     
212     if (sortedPrevalentResourcesWithoutUserInteraction.size() < 50)
213         return;
214     submitTopList(50, sortedPrevalentResources, sortedPrevalentResourcesWithoutUserInteraction, webPageProxy);
215     
216     if (sortedPrevalentResourcesWithoutUserInteraction.size() < 100)
217         return;
218     submitTopList(100, sortedPrevalentResources, sortedPrevalentResourcesWithoutUserInteraction, webPageProxy);
219 }
220     
221 // This function is for testing purposes.
222 void static notifyPages(unsigned totalPrevalentResources, unsigned totalPrevalentResourcesWithUserInteraction, unsigned top3SubframeUnderTopFrameOrigins)
223 {
224     RunLoop::main().dispatch([totalPrevalentResources, totalPrevalentResourcesWithUserInteraction, top3SubframeUnderTopFrameOrigins] {
225         API::Dictionary::MapType messageBody;
226         messageBody.set("TotalPrevalentResources"_s, API::UInt64::create(totalPrevalentResources));
227         messageBody.set("TotalPrevalentResourcesWithUserInteraction"_s, API::UInt64::create(totalPrevalentResourcesWithUserInteraction));
228         messageBody.set("Top3SubframeUnderTopFrameOrigins"_s, API::UInt64::create(top3SubframeUnderTopFrameOrigins));
229         WebProcessProxy::notifyPageStatisticsTelemetryFinished(API::Dictionary::create(messageBody).ptr());
230     });
231 }
232     
233 // This function is for testing purposes.
234 void static notifyPages(const Vector<PrevalentResourceTelemetry>& sortedPrevalentResources, const Vector<PrevalentResourceTelemetry>& sortedPrevalentResourcesWithoutUserInteraction, unsigned totalNumberOfPrevalentResourcesWithUserInteraction)
235 {
236     WTF::Function<unsigned(const PrevalentResourceTelemetry& telemetry)> subframeUnderTopFrameOriginsGetter = [] (const PrevalentResourceTelemetry& t) {
237         return t.subframeUnderTopFrameOrigins;
238     };
239     
240     notifyPages(sortedPrevalentResources.size(), totalNumberOfPrevalentResourcesWithUserInteraction, median(sortedPrevalentResourcesWithoutUserInteraction, 0, 2, subframeUnderTopFrameOriginsGetter));
241 }
242     
243 void WebResourceLoadStatisticsTelemetry::calculateAndSubmit(const ResourceLoadStatisticsMemoryStore& resourceLoadStatisticsStore)
244 {
245     ASSERT(!RunLoop::isMain());
246     
247     auto sortedPrevalentResources = sortedPrevalentResourceTelemetry(resourceLoadStatisticsStore);
248     if (notifyPagesWhenTelemetryWasCaptured && sortedPrevalentResources.isEmpty()) {
249         notifyPages(0, 0, 0);
250         return;
251     }
252     
253     Vector<PrevalentResourceTelemetry> sortedPrevalentResourcesWithoutUserInteraction;
254     sortedPrevalentResourcesWithoutUserInteraction.reserveInitialCapacity(sortedPrevalentResources.size());
255     Vector<unsigned> prevalentResourcesDaysSinceUserInteraction;
256     
257     for (auto& prevalentResource : sortedPrevalentResources) {
258         if (prevalentResource.hasHadUserInteraction)
259             prevalentResourcesDaysSinceUserInteraction.append(prevalentResource.daysSinceUserInteraction);
260         else
261             sortedPrevalentResourcesWithoutUserInteraction.uncheckedAppend(prevalentResource);
262     }
263     
264     // Dispatch on the main thread to make sure the WebPageProxy we're using doesn't go away.
265     RunLoop::main().dispatch([sortedPrevalentResources = WTFMove(sortedPrevalentResources), sortedPrevalentResourcesWithoutUserInteraction = WTFMove(sortedPrevalentResourcesWithoutUserInteraction), prevalentResourcesDaysSinceUserInteraction = WTFMove(prevalentResourcesDaysSinceUserInteraction)] () {
266         auto webPageProxy = nonEphemeralWebPageProxy();
267         if (!webPageProxy) {
268             if (notifyPagesWhenTelemetryWasCaptured)
269                 notifyPages(0, 0, 0);
270             return;
271         }
272         
273         if (notifyPagesWhenTelemetryWasCaptured) {
274             notifyPages(sortedPrevalentResources, sortedPrevalentResourcesWithoutUserInteraction, prevalentResourcesDaysSinceUserInteraction.size());
275             // The notify pages function is for testing so we don't need to do an actual submission.
276             return;
277         }
278         
279         webPageProxy->logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), "totalNumberOfPrevalentResources"_s, sortedPrevalentResources.size(), significantFiguresForLoggedValues, ShouldSample::No);
280         webPageProxy->logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), "totalNumberOfPrevalentResourcesWithUserInteraction"_s, prevalentResourcesDaysSinceUserInteraction.size(), significantFiguresForLoggedValues, ShouldSample::No);
281         
282         if (prevalentResourcesDaysSinceUserInteraction.size() > 0)
283             webPageProxy->logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), "topPrevalentResourceWithUserInteractionDaysSinceUserInteraction"_s, prevalentResourcesDaysSinceUserInteraction[0], significantFiguresForLoggedValues, ShouldSample::No);
284         if (prevalentResourcesDaysSinceUserInteraction.size() > 1)
285             webPageProxy->logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceLoadStatisticsTelemetryKey(), "medianPrevalentResourcesWithUserInteractionDaysSinceUserInteraction"_s, median(prevalentResourcesDaysSinceUserInteraction), significantFiguresForLoggedValues, ShouldSample::No);
286         
287         submitTopLists(sortedPrevalentResources, sortedPrevalentResourcesWithoutUserInteraction, *webPageProxy);
288     });
289 }
290     
291 void WebResourceLoadStatisticsTelemetry::setNotifyPagesWhenTelemetryWasCaptured(bool always)
292 {
293     notifyPagesWhenTelemetryWasCaptured = always;
294 }
295     
296 }