Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / WebCore / page / cocoa / ResourceUsageOverlayCocoa.mm
1 /*
2  * Copyright (C) 2015, 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 "ResourceUsageOverlay.h"
28
29 #if ENABLE(RESOURCE_USAGE)
30
31 #include <CoreText/CoreText.h>
32
33 #include "CommonVM.h"
34 #include "JSDOMWindow.h"
35 #include "PlatformCALayer.h"
36 #include "ResourceUsageThread.h"
37 #include <CoreGraphics/CGContext.h>
38 #include <QuartzCore/CALayer.h>
39 #include <QuartzCore/CATransaction.h>
40 #include <wtf/MainThread.h>
41 #include <wtf/MathExtras.h>
42 #include <wtf/NeverDestroyed.h>
43
44 using WebCore::ResourceUsageOverlay;
45 @interface WebOverlayLayer : CALayer {
46     ResourceUsageOverlay* m_overlay;
47 }
48 @end
49
50 @implementation WebOverlayLayer
51
52 - (WebOverlayLayer *)initWithResourceUsageOverlay:(ResourceUsageOverlay *)overlay
53 {
54     self = [super init];
55     if (!self)
56         return nil;
57     m_overlay = overlay;
58     return self;
59 }
60
61 - (void)drawInContext:(CGContextRef)context
62 {
63     m_overlay->platformDraw(context);
64 }
65
66 @end
67
68 namespace WebCore {
69
70 template<typename T, size_t size = 70>
71 class RingBuffer {
72 public:
73     RingBuffer()
74     {
75         m_data.fill(0);
76     }
77
78     void append(T v)
79     {
80         m_data[m_current] = WTFMove(v);
81         incrementIndex(m_current);
82     }
83
84     T& last()
85     {
86         unsigned index = m_current;
87         decrementIndex(index);
88         return m_data[index];
89     }
90
91     void forEach(const WTF::Function<void(T)>& apply) const
92     {
93         unsigned i = m_current;
94         for (unsigned visited = 0; visited < size; ++visited) {
95             apply(m_data[i]);
96             incrementIndex(i);
97         }
98     }
99
100 private:
101     static void incrementIndex(unsigned& index)
102     {
103         if (++index == size)
104             index = 0;
105     }
106
107     static void decrementIndex(unsigned& index)
108     {
109         if (index)
110             --index;
111         else
112             index = size - 1;
113     }
114
115     std::array<T, size> m_data;
116     unsigned m_current { 0 };
117 };
118
119 static CGColorRef createColor(float r, float g, float b, float a)
120 {
121     static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
122     CGFloat components[4] = { r, g, b, a };
123     return CGColorCreate(colorSpace, components);
124 }
125
126 struct HistoricMemoryCategoryInfo {
127     HistoricMemoryCategoryInfo() { } // Needed for std::array.
128
129     HistoricMemoryCategoryInfo(unsigned category, RGBA32 rgba, String name, bool subcategory = false)
130         : name(WTFMove(name))
131         , isSubcategory(subcategory)
132         , type(category)
133     {
134         float r, g, b, a;
135         Color(rgba).getRGBA(r, g, b, a);
136         color = adoptCF(createColor(r, g, b, a));
137     }
138
139     String name;
140     RetainPtr<CGColorRef> color;
141     RingBuffer<size_t> dirtySize;
142     RingBuffer<size_t> reclaimableSize;
143     RingBuffer<size_t> externalSize;
144     bool isSubcategory { false };
145     unsigned type { MemoryCategory::NumberOfCategories };
146 };
147
148 struct HistoricResourceUsageData {
149     HistoricResourceUsageData();
150
151     RingBuffer<float> cpu;
152     RingBuffer<size_t> totalDirtySize;
153     RingBuffer<size_t> totalExternalSize;
154     RingBuffer<size_t> gcHeapSize;
155     std::array<HistoricMemoryCategoryInfo, MemoryCategory::NumberOfCategories> categories;
156     MonotonicTime timeOfNextEdenCollection { MonotonicTime::nan() };
157     MonotonicTime timeOfNextFullCollection { MonotonicTime::nan() };
158 };
159
160 HistoricResourceUsageData::HistoricResourceUsageData()
161 {
162     // VM tag categories.
163     categories[MemoryCategory::JSJIT] = HistoricMemoryCategoryInfo(MemoryCategory::JSJIT, 0xFFFF60FF, "JS JIT");
164     categories[MemoryCategory::WebAssembly] = HistoricMemoryCategoryInfo(MemoryCategory::WebAssembly, 0xFF654FF0, "WebAssembly");
165     categories[MemoryCategory::Images] = HistoricMemoryCategoryInfo(MemoryCategory::Images, 0xFFFFFF00, "Images");
166     categories[MemoryCategory::Layers] = HistoricMemoryCategoryInfo(MemoryCategory::Layers, 0xFF00FFFF, "Layers");
167     categories[MemoryCategory::LibcMalloc] = HistoricMemoryCategoryInfo(MemoryCategory::LibcMalloc, 0xFF00FF00, "libc malloc");
168     categories[MemoryCategory::bmalloc] = HistoricMemoryCategoryInfo(MemoryCategory::bmalloc, 0xFFFF6060, "bmalloc");
169     categories[MemoryCategory::Other] = HistoricMemoryCategoryInfo(MemoryCategory::Other, 0xFFC0FF00, "Other");
170
171     // Sub categories (e.g breakdown of bmalloc tag.)
172     categories[MemoryCategory::GCHeap] = HistoricMemoryCategoryInfo(MemoryCategory::GCHeap, 0xFFA0A0FF, "GC heap", true);
173     categories[MemoryCategory::GCOwned] = HistoricMemoryCategoryInfo(MemoryCategory::GCOwned, 0xFFFFC060, "GC owned", true);
174
175 #ifndef NDEBUG
176     // Ensure this aligns with ResourceUsageData's category order.
177     ResourceUsageData d;
178     ASSERT(categories.size() == d.categories.size());
179     for (size_t i = 0; i < categories.size(); ++i)
180         ASSERT(categories[i].type == d.categories[i].type);
181 #endif
182 }
183
184 static HistoricResourceUsageData& historicUsageData()
185 {
186     static NeverDestroyed<HistoricResourceUsageData> data;
187     return data;
188 }
189
190 static void appendDataToHistory(const ResourceUsageData& data)
191 {
192     ASSERT(isMainThread());
193
194     auto& historicData = historicUsageData();
195     historicData.cpu.append(data.cpu);
196     historicData.totalDirtySize.append(data.totalDirtySize);
197     historicData.totalExternalSize.append(data.totalExternalSize);
198     for (auto& category : historicData.categories) {
199         category.dirtySize.append(data.categories[category.type].dirtySize);
200         category.reclaimableSize.append(data.categories[category.type].reclaimableSize);
201         category.externalSize.append(data.categories[category.type].externalSize);
202     }
203     historicData.timeOfNextEdenCollection = data.timeOfNextEdenCollection;
204     historicData.timeOfNextFullCollection = data.timeOfNextFullCollection;
205
206     // FIXME: Find a way to add this to ResourceUsageData and calculate it on the resource usage sampler thread.
207     {
208         JSC::VM* vm = &commonVM();
209         JSC::JSLockHolder lock(vm);
210         historicData.gcHeapSize.append(vm->heap.size() - vm->heap.extraMemorySize());
211     }
212 }
213
214 void ResourceUsageOverlay::platformInitialize()
215 {
216     m_layer = adoptNS([[WebOverlayLayer alloc] initWithResourceUsageOverlay:this]);
217
218     m_containerLayer = adoptNS([[CALayer alloc] init]);
219     [m_containerLayer.get() addSublayer:m_layer.get()];
220
221     [m_containerLayer.get() setAnchorPoint:CGPointZero];
222     [m_containerLayer.get() setBounds:CGRectMake(0, 0, normalWidth, normalHeight)];
223
224     [m_layer.get() setAnchorPoint:CGPointZero];
225     [m_layer.get() setContentsScale:2.0];
226     [m_layer.get() setBackgroundColor:adoptCF(createColor(0, 0, 0, 0.8)).get()];
227     [m_layer.get() setBounds:CGRectMake(0, 0, normalWidth, normalHeight)];
228
229     overlay().layer().setContentsToPlatformLayer(m_layer.get(), GraphicsLayer::ContentsLayerPurpose::None);
230
231     ResourceUsageThread::addObserver(this, [this] (const ResourceUsageData& data) {
232         appendDataToHistory(data);
233
234         // FIXME: It shouldn't be necessary to update the bounds on every single thread loop iteration,
235         // but something is causing them to become 0x0.
236         [CATransaction begin];
237         CALayer *containerLayer = [m_layer superlayer];
238         CGRect rect = CGRectMake(0, 0, ResourceUsageOverlay::normalWidth, ResourceUsageOverlay::normalHeight);
239         [m_layer setBounds:rect];
240         [containerLayer setBounds:rect];
241         [m_layer setNeedsDisplay];
242         [CATransaction commit];
243     });
244 }
245
246 void ResourceUsageOverlay::platformDestroy()
247 {
248     ResourceUsageThread::removeObserver(this);
249 }
250
251 static void showText(CGContextRef context, float x, float y, CGColorRef color, const String& text)
252 {
253     CGContextSaveGState(context);
254
255     CGContextSetTextDrawingMode(context, kCGTextFill);
256     CGContextSetFillColorWithColor(context, color);
257
258     auto matrix = CGAffineTransformMakeScale(1, -1);
259 #if PLATFORM(IOS)
260     CFStringRef fontName = CFSTR("Courier");
261     CGFloat fontSize = 10;
262 #else
263     CFStringRef fontName = CFSTR("Menlo");
264     CGFloat fontSize = 11;
265 #endif
266     auto font = adoptCF(CTFontCreateWithName(fontName, fontSize, &matrix));
267     CFTypeRef keys[] = { kCTFontAttributeName, kCTForegroundColorFromContextAttributeName };
268     CFTypeRef values[] = { font.get(), kCFBooleanTrue };
269     auto attributes = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
270     CString cstr = text.ascii();
271     auto string = adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(cstr.data()), cstr.length(), kCFStringEncodingASCII, false, kCFAllocatorNull));
272     auto attributedString = adoptCF(CFAttributedStringCreate(kCFAllocatorDefault, string.get(), attributes.get()));
273     auto line = adoptCF(CTLineCreateWithAttributedString(attributedString.get()));
274     CGContextSetTextPosition(context, x, y);
275     CTLineDraw(line.get(), context);
276
277     CGContextRestoreGState(context);
278 }
279
280 static void drawGraphLabel(CGContextRef context, float x, float y, const String& text)
281 {
282     static CGColorRef black = createColor(0, 0, 0, 1);
283     showText(context, x + 5, y - 3, black, text);
284     static CGColorRef white = createColor(1, 1, 1, 1);
285     showText(context, x + 4, y - 4, white, text);
286 }
287
288 static void drawCpuHistory(CGContextRef context, float x1, float y1, float y2, RingBuffer<float>& history)
289 {
290     static CGColorRef cpuColor = createColor(0, 1, 0, 1);
291
292     CGContextSetStrokeColorWithColor(context, cpuColor);
293     CGContextSetLineWidth(context, 1);
294
295     int i = 0;
296
297     history.forEach([&](float c) {
298         float cpu = c / 100;
299         float yScale = y2 - y1;
300
301         CGContextBeginPath(context);
302         CGContextMoveToPoint(context, x1 + i, y2);
303         CGContextAddLineToPoint(context, x1 + i, y2 - (yScale * cpu));
304         CGContextStrokePath(context);
305         i++;
306     });
307
308     drawGraphLabel(context, x1, y2, "CPU");
309 }
310
311 static void drawGCHistory(CGContextRef context, float x1, float y1, float y2, RingBuffer<size_t>& sizeHistory, RingBuffer<size_t>& capacityHistory)
312 {
313     float yScale = y2 - y1;
314
315     size_t peak = 0;
316     capacityHistory.forEach([&](size_t m) {
317         if (m > peak)
318             peak = m;
319     });
320
321     CGContextSetLineWidth(context, 1);
322
323     static CGColorRef capacityColor = createColor(1, 0, 0.3, 1);
324     CGContextSetStrokeColorWithColor(context, capacityColor);
325
326     size_t i = 0;
327
328     capacityHistory.forEach([&](size_t m) {
329         float mem = (float)m / (float)peak;
330         CGContextBeginPath(context);
331         CGContextMoveToPoint(context, x1 + i, y2);
332         CGContextAddLineToPoint(context, x1 + i, y2 - (yScale * mem));
333         CGContextStrokePath(context);
334         i++;
335     });
336
337     static CGColorRef sizeColor = createColor(0.6, 0.5, 0.9, 1);
338     CGContextSetStrokeColorWithColor(context, sizeColor);
339
340     i = 0;
341
342     sizeHistory.forEach([&](size_t m) {
343         float mem = (float)m / (float)peak;
344         CGContextBeginPath(context);
345         CGContextMoveToPoint(context, x1 + i, y2);
346         CGContextAddLineToPoint(context, x1 + i, y2 - (yScale * mem));
347         CGContextStrokePath(context);
348         i++;
349     });
350
351     drawGraphLabel(context, x1, y2, "GC");
352 }
353
354 static void drawMemHistory(CGContextRef context, float x1, float y1, float y2, HistoricResourceUsageData& data)
355 {
356     float yScale = y2 - y1;
357
358     size_t peak = 0;
359     data.totalDirtySize.forEach([&](size_t m) {
360         if (m > peak)
361             peak = m;
362     });
363
364     CGContextSetLineWidth(context, 1);
365
366     struct ColorAndSize {
367         CGColorRef color;
368         size_t size;
369     };
370
371     std::array<std::array<ColorAndSize, MemoryCategory::NumberOfCategories>, 70> columns;
372
373     for (auto& category : data.categories) {
374         unsigned x = 0;
375         category.dirtySize.forEach([&](size_t m) {
376             columns[x][category.type] = { category.color.get(), m };
377             ++x;
378         });
379     }
380
381     unsigned i = 0;
382     for (auto& column : columns) {
383         float currentY2 = y2;
384         for (auto& colorAndSize : column) {
385             float chunk = (float)colorAndSize.size / (float)peak;
386             float nextY2 = currentY2 - (yScale * chunk);
387             CGContextBeginPath(context);
388             CGContextMoveToPoint(context, x1 + i, currentY2);
389             CGContextAddLineToPoint(context, x1 + i, nextY2);
390             CGContextSetStrokeColorWithColor(context, colorAndSize.color);
391             CGContextStrokePath(context);
392             currentY2 = nextY2;
393         }
394         ++i;
395     }
396
397     drawGraphLabel(context, x1, y2, "Mem");
398 }
399
400 static const float fullCircleInRadians = piFloat * 2;
401
402 static void drawSlice(CGContextRef context, FloatPoint center, float& angle, float radius, size_t sliceSize, size_t totalSize, CGColorRef color)
403 {
404     float part = (float)sliceSize / (float)totalSize;
405
406     CGContextBeginPath(context);
407     CGContextMoveToPoint(context, center.x(), center.y());
408     CGContextAddArc(context, center.x(), center.y(), radius, angle, angle + part * fullCircleInRadians, false);
409     CGContextSetFillColorWithColor(context, color);
410     CGContextFillPath(context);
411     angle += part * fullCircleInRadians;
412 }
413
414 static void drawMemoryPie(CGContextRef context, FloatRect& rect, HistoricResourceUsageData& data)
415 {
416     float radius = rect.width() / 2;
417     FloatPoint center = rect.center();
418     size_t totalDirty = data.totalDirtySize.last();
419
420     float angle = 0;
421     for (auto& category : data.categories)
422         drawSlice(context, center, angle, radius, category.dirtySize.last(), totalDirty, category.color.get());
423 }
424
425 static String formatByteNumber(size_t number)
426 {
427     if (number >= 1024 * 1048576)
428         return String::format("%.3f GB", static_cast<double>(number) / (1024 * 1048576));
429     if (number >= 1048576)
430         return String::format("%.2f MB", static_cast<double>(number) / 1048576);
431     if (number >= 1024)
432         return String::format("%.1f kB", static_cast<double>(number) / 1024);
433     return String::format("%lu", number);
434 }
435
436 static String gcTimerString(MonotonicTime timerFireDate, MonotonicTime now)
437 {
438     if (std::isnan(timerFireDate))
439         return "[not scheduled]"_s;
440     return String::format("%g", (timerFireDate - now).seconds());
441 }
442
443 void ResourceUsageOverlay::platformDraw(CGContextRef context)
444 {
445     auto& data = historicUsageData();
446
447     if (![m_layer.get() contentsAreFlipped]) {
448         CGContextScaleCTM(context, 1, -1);
449         CGContextTranslateCTM(context, 0, -normalHeight);
450     }
451
452     CGContextSetShouldAntialias(context, false);
453     CGContextSetShouldSmoothFonts(context, false);
454
455     CGRect viewBounds = m_overlay->bounds();
456     CGContextClearRect(context, viewBounds);
457
458     static CGColorRef colorForLabels = createColor(0.9, 0.9, 0.9, 1);
459     showText(context, 10, 20, colorForLabels, String::format("        CPU: %g", data.cpu.last()));
460     showText(context, 10, 30, colorForLabels, "  Footprint: " + formatByteNumber(data.totalDirtySize.last()));
461     showText(context, 10, 40, colorForLabels, "   External: " + formatByteNumber(data.totalExternalSize.last()));
462
463     float y = 55;
464     for (auto& category : data.categories) {
465         size_t dirty = category.dirtySize.last();
466         size_t reclaimable = category.reclaimableSize.last();
467         size_t external = category.externalSize.last();
468         
469         String label = String::format("% 11s: %s", category.name.ascii().data(), formatByteNumber(dirty).ascii().data());
470         if (external)
471             label = label + String::format(" + %s", formatByteNumber(external).ascii().data());
472         if (reclaimable)
473             label = label + String::format(" [%s]", formatByteNumber(reclaimable).ascii().data());
474
475         // FIXME: Show size/capacity of GC heap.
476         showText(context, 10, y, category.color.get(), label);
477         y += 10;
478     }
479     y -= 5;
480
481     MonotonicTime now = MonotonicTime::now();
482     showText(context, 10, y + 10, colorForLabels, String::format("    Eden GC: %s", gcTimerString(data.timeOfNextEdenCollection, now).ascii().data()));
483     showText(context, 10, y + 20, colorForLabels, String::format("    Full GC: %s", gcTimerString(data.timeOfNextFullCollection, now).ascii().data()));
484
485     drawCpuHistory(context, viewBounds.size.width - 70, 0, viewBounds.size.height, data.cpu);
486     drawGCHistory(context, viewBounds.size.width - 140, 0, viewBounds.size.height, data.gcHeapSize, data.categories[MemoryCategory::GCHeap].dirtySize);
487     drawMemHistory(context, viewBounds.size.width - 210, 0, viewBounds.size.height, data);
488
489     FloatRect pieRect(viewBounds.size.width - 330, 0, 100, viewBounds.size.height);
490     drawMemoryPie(context, pieRect, data);
491 }
492
493 }
494
495 #endif // ENABLE(RESOURCE_USAGE)