Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / WebCore / page / cocoa / ResourceUsageThreadCocoa.mm
1 /*
2  * Copyright (C) 2015-2018 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 "ResourceUsageThread.h"
28
29 #if ENABLE(RESOURCE_USAGE)
30
31 #include <JavaScriptCore/GCActivityCallback.h>
32 #include <JavaScriptCore/Heap.h>
33 #include <JavaScriptCore/VM.h>
34 #include <mach/mach.h>
35 #include <mach/vm_statistics.h>
36 #include <pal/spi/cocoa/MachVMSPI.h>
37 #include <wtf/MachSendRight.h>
38
39 namespace WebCore {
40
41 size_t vmPageSize()
42 {
43 #if PLATFORM(IOS)
44     return vm_kernel_page_size;
45 #else
46     static size_t cached = sysconf(_SC_PAGESIZE);
47     return cached;
48 #endif
49 }
50
51 void logFootprintComparison(const std::array<TagInfo, 256>& before, const std::array<TagInfo, 256>& after)
52 {
53     const size_t pageSize = vmPageSize();
54
55     WTFLogAlways("Per-tag breakdown of memory reclaimed by pressure handler:");
56     WTFLogAlways("  ## %16s %10s %10s %10s", "VM Tag", "Before", "After", "Diff");
57     for (unsigned i = 0; i < 256; ++i) {
58         ssize_t dirtyBefore = before[i].dirty * pageSize;
59         ssize_t dirtyAfter = after[i].dirty * pageSize;
60         ssize_t dirtyDiff = dirtyAfter - dirtyBefore;
61         if (!dirtyBefore && !dirtyAfter)
62             continue;
63         String tagName = displayNameForVMTag(i);
64         if (!tagName)
65             tagName = String::format("Tag %u", i);
66         WTFLogAlways("  %02X %16s %10ld %10ld %10ld",
67             i,
68             tagName.ascii().data(),
69             dirtyBefore,
70             dirtyAfter,
71             dirtyDiff
72         );
73     }
74 }
75
76 const char* displayNameForVMTag(unsigned tag)
77 {
78     switch (tag) {
79     case VM_MEMORY_IOKIT: return "IOKit";
80     case VM_MEMORY_LAYERKIT: return "CoreAnimation";
81     case VM_MEMORY_IMAGEIO: return "ImageIO";
82     case VM_MEMORY_CGIMAGE: return "CG image";
83     case VM_MEMORY_JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR: return "JSC JIT";
84     case VM_MEMORY_JAVASCRIPT_CORE: return "WebAssembly";
85     case VM_MEMORY_MALLOC: return "malloc";
86     case VM_MEMORY_MALLOC_HUGE: return "malloc (huge)";
87     case VM_MEMORY_MALLOC_LARGE: return "malloc (large)";
88     case VM_MEMORY_MALLOC_SMALL: return "malloc (small)";
89     case VM_MEMORY_MALLOC_TINY: return "malloc (tiny)";
90     case VM_MEMORY_MALLOC_NANO: return "malloc (nano)";
91     case VM_MEMORY_TCMALLOC: return "bmalloc";
92     case VM_MEMORY_FOUNDATION: return "Foundation";
93     case VM_MEMORY_STACK: return "Stack";
94     case VM_MEMORY_SQLITE: return "SQLite";
95     case VM_MEMORY_UNSHARED_PMAP: return "pmap (unshared)";
96     case VM_MEMORY_DYLIB: return "dylib";
97     case VM_MEMORY_CORESERVICES: return "CoreServices";
98     case VM_MEMORY_OS_ALLOC_ONCE: return "os_alloc_once";
99     case VM_MEMORY_LIBDISPATCH: return "libdispatch";
100     default: return nullptr;
101     }
102 }
103
104 std::array<TagInfo, 256> pagesPerVMTag()
105 {
106     std::array<TagInfo, 256> tags;
107     task_t task = mach_task_self();
108     mach_vm_size_t size;
109     uint32_t depth = 0;
110     struct vm_region_submap_info_64 info = { };
111     mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
112     for (mach_vm_address_t addr = 0; ; addr += size) {
113         int purgeableState;
114         if (mach_vm_purgable_control(task, addr, VM_PURGABLE_GET_STATE, &purgeableState) != KERN_SUCCESS)
115             purgeableState = VM_PURGABLE_DENY;
116
117         kern_return_t kr = mach_vm_region_recurse(task, &addr, &size, &depth, (vm_region_info_t)&info, &count);
118         if (kr != KERN_SUCCESS)
119             break;
120
121         if (purgeableState == VM_PURGABLE_VOLATILE) {
122             tags[info.user_tag].reclaimable += info.pages_resident;
123             continue;
124         }
125
126         if (purgeableState == VM_PURGABLE_EMPTY) {
127             tags[info.user_tag].reclaimable += size / vmPageSize();
128             continue;
129         }
130
131         bool anonymous = !info.external_pager;
132         if (anonymous) {
133             tags[info.user_tag].dirty += info.pages_resident - info.pages_reusable;
134             tags[info.user_tag].reclaimable += info.pages_reusable;
135         } else
136             tags[info.user_tag].dirty += info.pages_dirtied;
137     }
138
139     return tags;
140 }
141
142 static Vector<MachSendRight> threadSendRights()
143 {
144     thread_array_t threadList = nullptr;
145     mach_msg_type_number_t threadCount = 0;
146     kern_return_t kr = task_threads(mach_task_self(), &threadList, &threadCount);
147     if (kr != KERN_SUCCESS)
148         return { };
149
150     Vector<MachSendRight> machThreads;
151     for (mach_msg_type_number_t i = 0; i < threadCount; ++i)
152         machThreads.append(MachSendRight::adopt(threadList[i]));
153
154     kr = vm_deallocate(mach_task_self(), (vm_offset_t)threadList, threadCount * sizeof(thread_t));
155     ASSERT(kr == KERN_SUCCESS);
156
157     return machThreads;
158 }
159
160 static float cpuUsage()
161 {
162     auto machThreads = threadSendRights();
163
164     float usage = 0;
165
166     for (auto& machThread : machThreads) {
167         thread_info_data_t threadInfo;
168         mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;
169         auto kr = thread_info(machThread.sendRight(), THREAD_BASIC_INFO, static_cast<thread_info_t>(threadInfo), &threadInfoCount);
170         if (kr != KERN_SUCCESS)
171             return -1;
172
173         auto threadBasicInfo = reinterpret_cast<thread_basic_info_t>(threadInfo);
174         if (!(threadBasicInfo->flags & TH_FLAGS_IDLE))
175             usage += threadBasicInfo->cpu_usage / static_cast<float>(TH_USAGE_SCALE) * 100.0;
176     }
177
178     return usage;
179 }
180
181 static unsigned categoryForVMTag(unsigned tag)
182 {
183     switch (tag) {
184     case VM_MEMORY_IOKIT:
185     case VM_MEMORY_LAYERKIT:
186         return MemoryCategory::Layers;
187     case VM_MEMORY_IMAGEIO:
188     case VM_MEMORY_CGIMAGE:
189         return MemoryCategory::Images;
190     case VM_MEMORY_JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR:
191         return MemoryCategory::JSJIT;
192     case VM_MEMORY_JAVASCRIPT_CORE:
193         return MemoryCategory::WebAssembly;
194     case VM_MEMORY_MALLOC:
195     case VM_MEMORY_MALLOC_HUGE:
196     case VM_MEMORY_MALLOC_LARGE:
197     case VM_MEMORY_MALLOC_SMALL:
198     case VM_MEMORY_MALLOC_TINY:
199     case VM_MEMORY_MALLOC_NANO:
200         return MemoryCategory::LibcMalloc;
201     case VM_MEMORY_TCMALLOC:
202         return MemoryCategory::bmalloc;
203     default:
204         return MemoryCategory::Other;
205     }
206 }
207
208 void ResourceUsageThread::platformThreadBody(JSC::VM* vm, ResourceUsageData& data)
209 {
210     data.cpu = cpuUsage();
211
212     auto tags = pagesPerVMTag();
213     std::array<TagInfo, MemoryCategory::NumberOfCategories> pagesPerCategory;
214     size_t totalDirtyPages = 0;
215     for (unsigned i = 0; i < 256; ++i) {
216         pagesPerCategory[categoryForVMTag(i)].dirty += tags[i].dirty;
217         pagesPerCategory[categoryForVMTag(i)].reclaimable += tags[i].reclaimable;
218         totalDirtyPages += tags[i].dirty;
219     }
220
221     for (auto& category : data.categories) {
222         if (category.isSubcategory) // Only do automatic tallying for top-level categories.
223             continue;
224         category.dirtySize = pagesPerCategory[category.type].dirty * vmPageSize();
225         category.reclaimableSize = pagesPerCategory[category.type].reclaimable * vmPageSize();
226     }
227     data.totalDirtySize = totalDirtyPages * vmPageSize();
228
229     size_t currentGCHeapCapacity = vm->heap.blockBytesAllocated();
230     size_t currentGCOwnedExtra = vm->heap.extraMemorySize();
231     size_t currentGCOwnedExternal = vm->heap.externalMemorySize();
232     ASSERT(currentGCOwnedExternal <= currentGCOwnedExtra);
233
234     data.categories[MemoryCategory::GCHeap].dirtySize = currentGCHeapCapacity;
235     data.categories[MemoryCategory::GCOwned].dirtySize = currentGCOwnedExtra - currentGCOwnedExternal;
236     data.categories[MemoryCategory::GCOwned].externalSize = currentGCOwnedExternal;
237
238     auto& mallocBucket = isFastMallocEnabled() ? data.categories[MemoryCategory::bmalloc] : data.categories[MemoryCategory::LibcMalloc];
239
240     // First subtract memory allocated by the GC heap, since we track that separately.
241     mallocBucket.dirtySize -= currentGCHeapCapacity;
242
243     // It would be nice to assert that the "GC owned" amount is smaller than the total dirty malloc size,
244     // but since the "GC owned" accounting is inexact, it's not currently feasible.
245     size_t currentGCOwnedGenerallyInMalloc = currentGCOwnedExtra - currentGCOwnedExternal;
246     if (currentGCOwnedGenerallyInMalloc < mallocBucket.dirtySize)
247         mallocBucket.dirtySize -= currentGCOwnedGenerallyInMalloc;
248
249     data.totalExternalSize = currentGCOwnedExternal;
250
251     data.timeOfNextEdenCollection = vm->heap.edenActivityCallback()->nextFireTime();
252     data.timeOfNextFullCollection = vm->heap.fullActivityCallback()->nextFireTime();
253 }
254
255 }
256
257 #endif