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