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