1 | /* |
2 | * Copyright (C) 2017 Igalia S.L. |
3 | * Copyright (C) 2018 Apple Inc. All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
16 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
18 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
24 | * THE POSSIBILITY OF SUCH DAMAGE. |
25 | */ |
26 | |
27 | #include "config.h" |
28 | #include "ResourceUsageThread.h" |
29 | |
30 | #if ENABLE(RESOURCE_USAGE) && OS(LINUX) |
31 | |
32 | #include <JavaScriptCore/GCActivityCallback.h> |
33 | #include <JavaScriptCore/VM.h> |
34 | #include <errno.h> |
35 | #include <fcntl.h> |
36 | #include <stdio.h> |
37 | #include <stdlib.h> |
38 | #include <sys/stat.h> |
39 | #include <sys/types.h> |
40 | #include <unistd.h> |
41 | #include <wtf/linux/CurrentProcessMemoryStatus.h> |
42 | |
43 | namespace WebCore { |
44 | |
45 | static float cpuPeriod() |
46 | { |
47 | FILE* file = fopen("/proc/stat" , "r" ); |
48 | if (!file) |
49 | return 0; |
50 | |
51 | static const unsigned statMaxLineLength = 512; |
52 | char buffer[statMaxLineLength + 1]; |
53 | char* line = fgets(buffer, statMaxLineLength, file); |
54 | if (!line) { |
55 | fclose(file); |
56 | return 0; |
57 | } |
58 | |
59 | unsigned long long userTime, niceTime, systemTime, idleTime; |
60 | unsigned long long ioWait, irq, softIrq, steal, guest, guestnice; |
61 | ioWait = irq = softIrq = steal = guest = guestnice = 0; |
62 | int retVal = sscanf(buffer, "cpu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu" , |
63 | &userTime, &niceTime, &systemTime, &idleTime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice); |
64 | // We expect 10 values to be matched by sscanf |
65 | if (retVal != 10) { |
66 | fclose(file); |
67 | return 0; |
68 | } |
69 | |
70 | |
71 | // Keep parsing if we still don't know cpuCount. |
72 | static unsigned cpuCount = 0; |
73 | if (!cpuCount) { |
74 | while ((line = fgets(buffer, statMaxLineLength, file))) { |
75 | if (strlen(line) > 4 && line[0] == 'c' && line[1] == 'p' && line[2] == 'u') |
76 | cpuCount++; |
77 | else |
78 | break; |
79 | } |
80 | } |
81 | fclose(file); |
82 | |
83 | if (!cpuCount) |
84 | return 0; |
85 | |
86 | static unsigned long long previousTotalTime = 0; |
87 | unsigned long long totalTime = userTime + niceTime + systemTime + irq + softIrq + idleTime + ioWait + steal; |
88 | unsigned long long period = totalTime > previousTotalTime ? totalTime - previousTotalTime : 0; |
89 | previousTotalTime = totalTime; |
90 | return static_cast<float>(period) / cpuCount; |
91 | } |
92 | |
93 | static float cpuUsage() |
94 | { |
95 | float period = cpuPeriod(); |
96 | if (!period) |
97 | return -1; |
98 | |
99 | int fd = open("/proc/self/stat" , O_RDONLY); |
100 | if (fd < 0) |
101 | return -1; |
102 | |
103 | static const ssize_t maxBufferLength = BUFSIZ - 1; |
104 | char buffer[BUFSIZ]; |
105 | buffer[0] = '\0'; |
106 | |
107 | ssize_t totalBytesRead = 0; |
108 | while (totalBytesRead < maxBufferLength) { |
109 | ssize_t bytesRead = read(fd, buffer + totalBytesRead, maxBufferLength - totalBytesRead); |
110 | if (bytesRead < 0) { |
111 | if (errno != EINTR) { |
112 | close(fd); |
113 | return -1; |
114 | } |
115 | continue; |
116 | } |
117 | |
118 | if (!bytesRead) |
119 | break; |
120 | |
121 | totalBytesRead += bytesRead; |
122 | } |
123 | close(fd); |
124 | buffer[totalBytesRead] = '\0'; |
125 | |
126 | // Skip pid and process name. |
127 | char* position = strrchr(buffer, ')'); |
128 | if (!position) |
129 | return -1; |
130 | |
131 | // Move after state. |
132 | position += 4; |
133 | |
134 | // Skip ppid, pgrp, sid, tty_nr, tty_pgrp, flags, min_flt, cmin_flt, maj_flt, cmaj_flt. |
135 | unsigned tokensToSkip = 10; |
136 | while (tokensToSkip--) { |
137 | while (!isASCIISpace(position[0])) |
138 | position++; |
139 | position++; |
140 | } |
141 | |
142 | static unsigned long long previousUtime = 0; |
143 | static unsigned long long previousStime = 0; |
144 | unsigned long long utime = strtoull(position, &position, 10); |
145 | unsigned long long stime = strtoull(position, &position, 10); |
146 | float usage = (utime + stime - (previousUtime + previousStime)) / period * 100.0; |
147 | previousUtime = utime; |
148 | previousStime = stime; |
149 | |
150 | return clampTo<float>(usage, 0, 100); |
151 | } |
152 | |
153 | void ResourceUsageThread::platformSaveStateBeforeStarting() |
154 | { |
155 | } |
156 | |
157 | void ResourceUsageThread::platformCollectCPUData(JSC::VM*, ResourceUsageData& data) |
158 | { |
159 | data.cpu = cpuUsage(); |
160 | |
161 | // FIXME: Exclude the ResourceUsage thread. |
162 | // FIXME: Exclude the SamplingProfiler thread. |
163 | // FIXME: Classify usage per thread. |
164 | data.cpuExcludingDebuggerThreads = data.cpu; |
165 | } |
166 | |
167 | void ResourceUsageThread::platformCollectMemoryData(JSC::VM* vm, ResourceUsageData& data) |
168 | { |
169 | ProcessMemoryStatus memoryStatus; |
170 | currentProcessMemoryStatus(memoryStatus); |
171 | data.totalDirtySize = memoryStatus.resident - memoryStatus.shared; |
172 | |
173 | size_t currentGCHeapCapacity = vm->heap.blockBytesAllocated(); |
174 | size_t = vm->heap.extraMemorySize(); |
175 | size_t currentGCOwnedExternal = vm->heap.externalMemorySize(); |
176 | RELEASE_ASSERT(currentGCOwnedExternal <= currentGCOwnedExtra); |
177 | |
178 | data.categories[MemoryCategory::GCHeap].dirtySize = currentGCHeapCapacity; |
179 | data.categories[MemoryCategory::GCOwned].dirtySize = currentGCOwnedExtra - currentGCOwnedExternal; |
180 | data.categories[MemoryCategory::GCOwned].externalSize = currentGCOwnedExternal; |
181 | |
182 | data.totalExternalSize = currentGCOwnedExternal; |
183 | |
184 | data.timeOfNextEdenCollection = data.timestamp + vm->heap.edenActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity())); |
185 | data.timeOfNextFullCollection = data.timestamp + vm->heap.fullActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity())); |
186 | } |
187 | |
188 | } // namespace WebCore |
189 | |
190 | #endif // ENABLE(RESOURCE_USAGE) && OS(LINUX) |
191 | |