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
43namespace WebCore {
44
45static 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
93static 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
153void ResourceUsageThread::platformSaveStateBeforeStarting()
154{
155}
156
157void 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
167void 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 currentGCOwnedExtra = 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