blob: 4eb1268884fb8297392c2f5f8fec2500e59f6b99 [file] [log] [blame]
Nathan Scott72235d82023-05-02 16:56:18 +10001/*
2htop - LinuxMachine.c
3(C) 2014 Hisham H. Muhammad
4Released under the GNU GPLv2+, see the COPYING file
5in the source distribution for its full text.
6*/
7
8#include "config.h" // IWYU pragma: keep
9
10#include "linux/LinuxMachine.h"
11
12#include <assert.h>
Nathan Scott72235d82023-05-02 16:56:18 +100013#include <dirent.h>
14#include <errno.h>
15#include <fcntl.h>
Benny Baumanne56089e2023-11-28 15:15:03 +010016#include <limits.h>
Nathan Scott72235d82023-05-02 16:56:18 +100017#include <math.h>
18#include <stdbool.h>
Nathan Scott72235d82023-05-02 16:56:18 +100019#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <strings.h>
23#include <unistd.h>
Nathan Scott72235d82023-05-02 16:56:18 +100024#include <time.h>
25
26#include "Compat.h"
Benny Baumanne56089e2023-11-28 15:15:03 +010027#include "CRT.h"
Explorer09b4164332023-07-29 16:24:12 +080028#include "Macros.h"
Benny Baumanne56089e2023-11-28 15:15:03 +010029#include "ProcessTable.h"
30#include "Row.h"
31#include "Settings.h"
32#include "UsersTable.h"
Nathan Scott72235d82023-05-02 16:56:18 +100033#include "XUtils.h"
Benny Baumanne56089e2023-11-28 15:15:03 +010034
Nathan Scott72235d82023-05-02 16:56:18 +100035#include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep
36
37#ifdef HAVE_SENSORS_SENSORS_H
38#include "LibSensors.h"
39#endif
40
41#ifndef O_PATH
42#define O_PATH 010000000 // declare for ancient glibc versions
43#endif
44
45/* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF
46 * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD
47 */
48static void LinuxMachine_updateCPUcount(LinuxMachine* this) {
49 unsigned int existing = 0, active = 0;
50 Machine* super = &this->super;
51
52 // Initialize the cpuData array before anything else.
53 if (!this->cpuData) {
54 this->cpuData = xCalloc(2, sizeof(CPUData));
55 this->cpuData[0].online = true; /* average is always "online" */
56 this->cpuData[1].online = true;
57 super->activeCPUs = 1;
58 super->existingCPUs = 1;
59 }
60
61 DIR* dir = opendir("/sys/devices/system/cpu");
62 if (!dir)
63 return;
64
65 unsigned int currExisting = super->existingCPUs;
66
67 const struct dirent* entry;
68 while ((entry = readdir(dir)) != NULL) {
69 if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
70 continue;
71
72 if (!String_startsWith(entry->d_name, "cpu"))
73 continue;
74
75 char* endp;
76 unsigned long int id = strtoul(entry->d_name + 3, &endp, 10);
77 if (id == ULONG_MAX || endp == entry->d_name + 3 || *endp != '\0')
78 continue;
79
80#ifdef HAVE_OPENAT
Christian Göttsche49bb3c42024-08-09 17:57:10 +020081 int cpuDirFd = openat(xDirfd(dir), entry->d_name, O_DIRECTORY | O_PATH | O_NOFOLLOW);
Nathan Scott72235d82023-05-02 16:56:18 +100082 if (cpuDirFd < 0)
83 continue;
84#else
85 char cpuDirFd[4096];
86 xSnprintf(cpuDirFd, sizeof(cpuDirFd), "/sys/devices/system/cpu/%s", entry->d_name);
87#endif
88
89 existing++;
90
91 /* readdir() iterates with no specific order */
92 unsigned int max = MAXIMUM(existing, id + 1);
93 if (max > currExisting) {
94 this->cpuData = xReallocArrayZero(this->cpuData, currExisting ? (currExisting + 1) : 0, max + /* aggregate */ 1, sizeof(CPUData));
95 this->cpuData[0].online = true; /* average is always "online" */
96 currExisting = max;
97 }
98
99 char buffer[8];
100 ssize_t res = xReadfileat(cpuDirFd, "online", buffer, sizeof(buffer));
101 /* If the file "online" does not exist or on failure count as active */
102 if (res < 1 || buffer[0] != '0') {
103 active++;
104 this->cpuData[id + 1].online = true;
105 } else {
106 this->cpuData[id + 1].online = false;
107 }
108
109 Compat_openatArgClose(cpuDirFd);
110 }
111
112 closedir(dir);
113
114 // return if no CPU is found
115 if (existing < 1)
116 return;
117
118#ifdef HAVE_SENSORS_SENSORS_H
119 /* When started with offline CPUs, libsensors does not monitor those,
120 * even when they become online. */
121 if (super->existingCPUs != 0 && (active > super->activeCPUs || currExisting > super->existingCPUs))
122 LibSensors_reload();
123#endif
124
125 super->activeCPUs = active;
126 assert(existing == currExisting);
127 super->existingCPUs = currExisting;
128}
129
130static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) {
131 Machine* host = &this->super;
132 memory_t availableMem = 0;
133 memory_t freeMem = 0;
134 memory_t totalMem = 0;
135 memory_t buffersMem = 0;
136 memory_t cachedMem = 0;
137 memory_t sharedMem = 0;
138 memory_t swapTotalMem = 0;
139 memory_t swapCacheMem = 0;
140 memory_t swapFreeMem = 0;
141 memory_t sreclaimableMem = 0;
142 memory_t zswapCompMem = 0;
143 memory_t zswapOrigMem = 0;
144
145 FILE* file = fopen(PROCMEMINFOFILE, "r");
146 if (!file)
147 CRT_fatalError("Cannot open " PROCMEMINFOFILE);
148
149 char buffer[128];
150 while (fgets(buffer, sizeof(buffer), file)) {
151
152 #define tryRead(label, variable) \
153 if (String_startsWith(buffer, label)) { \
154 memory_t parsed_; \
155 if (sscanf(buffer + strlen(label), "%llu kB", &parsed_) == 1) { \
156 (variable) = parsed_; \
157 } \
158 break; \
159 } else (void) 0 /* Require a ";" after the macro use. */
160
161 switch (buffer[0]) {
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100162 case 'M':
163 tryRead("MemAvailable:", availableMem);
164 tryRead("MemFree:", freeMem);
165 tryRead("MemTotal:", totalMem);
Nathan Scott72235d82023-05-02 16:56:18 +1000166 break;
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100167 case 'B':
168 tryRead("Buffers:", buffersMem);
Nathan Scott72235d82023-05-02 16:56:18 +1000169 break;
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100170 case 'C':
171 tryRead("Cached:", cachedMem);
Nathan Scott72235d82023-05-02 16:56:18 +1000172 break;
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100173 case 'S':
174 switch (buffer[1]) {
175 case 'h':
176 tryRead("Shmem:", sharedMem);
177 break;
178 case 'w':
179 tryRead("SwapTotal:", swapTotalMem);
180 tryRead("SwapCached:", swapCacheMem);
181 tryRead("SwapFree:", swapFreeMem);
182 break;
183 case 'R':
184 tryRead("SReclaimable:", sreclaimableMem);
185 break;
186 }
187 break;
188 case 'Z':
189 tryRead("Zswap:", zswapCompMem);
190 tryRead("Zswapped:", zswapOrigMem);
191 break;
Nathan Scott72235d82023-05-02 16:56:18 +1000192 }
193
194 #undef tryRead
195 }
196
197 fclose(file);
198
199 /*
200 * Compute memory partition like procps(free)
201 * https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c
202 *
203 * Adjustments:
204 * - Shmem in part of Cached (see https://lore.kernel.org/patchwork/patch/648763/),
205 * do not show twice by subtracting from Cached and do not subtract twice from used.
206 */
207 host->totalMem = totalMem;
208 host->cachedMem = cachedMem + sreclaimableMem - sharedMem;
209 host->sharedMem = sharedMem;
210 const memory_t usedDiff = freeMem + cachedMem + sreclaimableMem + buffersMem;
211 host->usedMem = (totalMem >= usedDiff) ? totalMem - usedDiff : totalMem - freeMem;
212 host->buffersMem = buffersMem;
213 host->availableMem = availableMem != 0 ? MINIMUM(availableMem, totalMem) : freeMem;
214 host->totalSwap = swapTotalMem;
215 host->usedSwap = swapTotalMem - swapFreeMem - swapCacheMem;
216 host->cachedSwap = swapCacheMem;
217 this->zswap.usedZswapComp = zswapCompMem;
218 this->zswap.usedZswapOrig = zswapOrigMem;
219}
220
221static void LinuxMachine_scanHugePages(LinuxMachine* this) {
222 this->totalHugePageMem = 0;
223 for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {
224 this->usedHugePageMem[i] = MEMORY_MAX;
225 }
226
227 DIR* dir = opendir("/sys/kernel/mm/hugepages");
228 if (!dir)
229 return;
230
231 const struct dirent* entry;
232 while ((entry = readdir(dir)) != NULL) {
233 const char* name = entry->d_name;
234
235 /* Ignore all non-directories */
236 if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
237 continue;
238
239 if (!String_startsWith(name, "hugepages-"))
240 continue;
241
242 char* endptr;
243 unsigned long int hugePageSize = strtoul(name + strlen("hugepages-"), &endptr, 10);
244 if (!endptr || *endptr != 'k')
245 continue;
246
247 char content[64];
248 char hugePagePath[128];
249 ssize_t r;
250
251 xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/nr_hugepages", name);
252 r = xReadfile(hugePagePath, content, sizeof(content));
253 if (r <= 0)
254 continue;
255
256 memory_t total = strtoull(content, NULL, 10);
257 if (total == 0)
258 continue;
259
260 xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/free_hugepages", name);
261 r = xReadfile(hugePagePath, content, sizeof(content));
262 if (r <= 0)
263 continue;
264
265 memory_t free = strtoull(content, NULL, 10);
266
267 int shift = ffsl(hugePageSize) - 1 - (HTOP_HUGEPAGE_BASE_SHIFT - 10);
268 assert(shift >= 0 && shift < HTOP_HUGEPAGE_COUNT);
269
270 this->totalHugePageMem += total * hugePageSize;
271 this->usedHugePageMem[shift] = (total - free) * hugePageSize;
272 }
273
274 closedir(dir);
275}
276
Nathan Scott72235d82023-05-02 16:56:18 +1000277static void LinuxMachine_scanZramInfo(LinuxMachine* this) {
278 memory_t totalZram = 0;
279 memory_t usedZramComp = 0;
280 memory_t usedZramOrig = 0;
281
282 char mm_stat[34];
283 char disksize[34];
284
285 unsigned int i = 0;
286 for (;;) {
287 xSnprintf(mm_stat, sizeof(mm_stat), "/sys/block/zram%u/mm_stat", i);
288 xSnprintf(disksize, sizeof(disksize), "/sys/block/zram%u/disksize", i);
289 i++;
290 FILE* disksize_file = fopen(disksize, "r");
291 FILE* mm_stat_file = fopen(mm_stat, "r");
292 if (disksize_file == NULL || mm_stat_file == NULL) {
293 if (disksize_file) {
294 fclose(disksize_file);
295 }
296 if (mm_stat_file) {
297 fclose(mm_stat_file);
298 }
299 break;
300 }
301 memory_t size = 0;
302 memory_t orig_data_size = 0;
303 memory_t compr_data_size = 0;
304
Christian Göttsche94c78222024-01-20 11:51:20 +0100305 if (1 != fscanf(disksize_file, "%llu\n", &size) ||
306 2 != fscanf(mm_stat_file, " %llu %llu", &orig_data_size, &compr_data_size)) {
Nathan Scott72235d82023-05-02 16:56:18 +1000307 fclose(disksize_file);
308 fclose(mm_stat_file);
309 break;
310 }
311
312 totalZram += size;
313 usedZramComp += compr_data_size;
314 usedZramOrig += orig_data_size;
315
316 fclose(disksize_file);
317 fclose(mm_stat_file);
318 }
319
320 this->zram.totalZram = totalZram / 1024;
321 this->zram.usedZramComp = usedZramComp / 1024;
322 this->zram.usedZramOrig = usedZramOrig / 1024;
Explorer0925cb42f2023-08-26 16:56:06 +0800323 if (this->zram.usedZramComp > this->zram.usedZramOrig) {
324 this->zram.usedZramComp = this->zram.usedZramOrig;
325 }
Nathan Scott72235d82023-05-02 16:56:18 +1000326}
327
328static void LinuxMachine_scanZfsArcstats(LinuxMachine* this) {
329 memory_t dbufSize = 0;
330 memory_t dnodeSize = 0;
331 memory_t bonusSize = 0;
332
333 FILE* file = fopen(PROCARCSTATSFILE, "r");
334 if (file == NULL) {
335 this->zfs.enabled = 0;
336 return;
337 }
338 char buffer[128];
339 while (fgets(buffer, 128, file)) {
340 #define tryRead(label, variable) \
341 if (String_startsWith(buffer, label)) { \
342 sscanf(buffer + strlen(label), " %*2u %32llu", variable); \
343 break; \
344 } else (void) 0 /* Require a ";" after the macro use. */
Christian Göttsche94c78222024-01-20 11:51:20 +0100345 #define tryReadFlag(label, variable, flag) \
346 if (String_startsWith(buffer, label)) { \
347 (flag) = (1 == sscanf(buffer + strlen(label), " %*2u %32llu", variable)); \
348 break; \
Nathan Scott72235d82023-05-02 16:56:18 +1000349 } else (void) 0 /* Require a ";" after the macro use. */
350
351 switch (buffer[0]) {
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100352 case 'c':
353 tryRead("c_min", &this->zfs.min);
354 tryRead("c_max", &this->zfs.max);
355 tryReadFlag("compressed_size", &this->zfs.compressed, this->zfs.isCompressed);
356 break;
357 case 'u':
358 tryRead("uncompressed_size", &this->zfs.uncompressed);
359 break;
360 case 's':
361 tryRead("size", &this->zfs.size);
362 break;
363 case 'h':
364 tryRead("hdr_size", &this->zfs.header);
365 break;
366 case 'd':
367 tryRead("dbuf_size", &dbufSize);
368 tryRead("dnode_size", &dnodeSize);
369 break;
370 case 'b':
371 tryRead("bonus_size", &bonusSize);
372 break;
373 case 'a':
374 tryRead("anon_size", &this->zfs.anon);
375 break;
376 case 'm':
377 tryRead("mfu_size", &this->zfs.MFU);
378 tryRead("mru_size", &this->zfs.MRU);
379 break;
Nathan Scott72235d82023-05-02 16:56:18 +1000380 }
Benny Baumann6aa9ef22023-11-23 12:22:02 +0100381
Nathan Scott72235d82023-05-02 16:56:18 +1000382 #undef tryRead
383 #undef tryReadFlag
384 }
385 fclose(file);
386
387 this->zfs.enabled = (this->zfs.size > 0 ? 1 : 0);
388 this->zfs.size /= 1024;
389 this->zfs.min /= 1024;
390 this->zfs.max /= 1024;
391 this->zfs.MFU /= 1024;
392 this->zfs.MRU /= 1024;
393 this->zfs.anon /= 1024;
394 this->zfs.header /= 1024;
395 this->zfs.other = (dbufSize + dnodeSize + bonusSize) / 1024;
396 if ( this->zfs.isCompressed ) {
397 this->zfs.compressed /= 1024;
398 this->zfs.uncompressed /= 1024;
399 }
400}
401
402static void LinuxMachine_scanCPUTime(LinuxMachine* this) {
403 const Machine* super = &this->super;
404
405 LinuxMachine_updateCPUcount(this);
406
407 FILE* file = fopen(PROCSTATFILE, "r");
408 if (!file)
409 CRT_fatalError("Cannot open " PROCSTATFILE);
410
Martin Rys240dd4a2024-01-30 23:05:13 +0100411 // Add an extra phantom thread for a later loop
412 bool adjCpuIdProcessed[super->existingCPUs+2];
413 memset(adjCpuIdProcessed, 0, sizeof(adjCpuIdProcessed));
Nathan Scott72235d82023-05-02 16:56:18 +1000414
415 for (unsigned int i = 0; i <= super->existingCPUs; i++) {
416 char buffer[PROC_LINE_LENGTH + 1];
417 unsigned long long int usertime, nicetime, systemtime, idletime;
418 unsigned long long int ioWait = 0, irq = 0, softIrq = 0, steal = 0, guest = 0, guestnice = 0;
419
420 const char* ok = fgets(buffer, sizeof(buffer), file);
421 if (!ok)
422 break;
423
424 // cpu fields are sorted first
425 if (!String_startsWith(buffer, "cpu"))
426 break;
427
428 // Depending on your kernel version,
429 // 5, 7, 8 or 9 of these fields will be set.
430 // The rest will remain at zero.
431 unsigned int adjCpuId;
432 if (i == 0) {
Nathan Scott290ddba2023-05-08 10:08:20 +1000433 (void) sscanf(buffer, "cpu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
Nathan Scott72235d82023-05-02 16:56:18 +1000434 adjCpuId = 0;
435 } else {
436 unsigned int cpuid;
437 (void) sscanf(buffer, "cpu%4u %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);
438 adjCpuId = cpuid + 1;
439 }
440
441 if (adjCpuId > super->existingCPUs)
442 break;
443
Nathan Scott72235d82023-05-02 16:56:18 +1000444 // Guest time is already accounted in usertime
445 usertime -= guest;
446 nicetime -= guestnice;
447 // Fields existing on kernels >= 2.6
448 // (and RHEL's patched kernel 2.4...)
449 unsigned long long int idlealltime = idletime + ioWait;
450 unsigned long long int systemalltime = systemtime + irq + softIrq;
451 unsigned long long int virtalltime = guest + guestnice;
452 unsigned long long int totaltime = usertime + nicetime + systemalltime + idlealltime + steal + virtalltime;
453 CPUData* cpuData = &(this->cpuData[adjCpuId]);
454 // Since we do a subtraction (usertime - guest) and cputime64_to_clock_t()
455 // used in /proc/stat rounds down numbers, it can lead to a case where the
456 // integer overflow.
457 cpuData->userPeriod = saturatingSub(usertime, cpuData->userTime);
458 cpuData->nicePeriod = saturatingSub(nicetime, cpuData->niceTime);
459 cpuData->systemPeriod = saturatingSub(systemtime, cpuData->systemTime);
460 cpuData->systemAllPeriod = saturatingSub(systemalltime, cpuData->systemAllTime);
461 cpuData->idleAllPeriod = saturatingSub(idlealltime, cpuData->idleAllTime);
462 cpuData->idlePeriod = saturatingSub(idletime, cpuData->idleTime);
463 cpuData->ioWaitPeriod = saturatingSub(ioWait, cpuData->ioWaitTime);
464 cpuData->irqPeriod = saturatingSub(irq, cpuData->irqTime);
465 cpuData->softIrqPeriod = saturatingSub(softIrq, cpuData->softIrqTime);
466 cpuData->stealPeriod = saturatingSub(steal, cpuData->stealTime);
467 cpuData->guestPeriod = saturatingSub(virtalltime, cpuData->guestTime);
468 cpuData->totalPeriod = saturatingSub(totaltime, cpuData->totalTime);
469 cpuData->userTime = usertime;
470 cpuData->niceTime = nicetime;
471 cpuData->systemTime = systemtime;
472 cpuData->systemAllTime = systemalltime;
473 cpuData->idleAllTime = idlealltime;
474 cpuData->idleTime = idletime;
475 cpuData->ioWaitTime = ioWait;
476 cpuData->irqTime = irq;
477 cpuData->softIrqTime = softIrq;
478 cpuData->stealTime = steal;
479 cpuData->guestTime = virtalltime;
480 cpuData->totalTime = totaltime;
Martin Rys240dd4a2024-01-30 23:05:13 +0100481
482 adjCpuIdProcessed[adjCpuId] = true;
483 }
484
485 // Set the extra phantom thread as checked to make sure to mark trailing offline threads correctly in the loop
486 adjCpuIdProcessed[super->existingCPUs+1] = true;
487 unsigned int lastAdjCpuIdProcessed = 0;
488 for (unsigned int i = 0; i <= super->existingCPUs+1; i++) {
489 if (adjCpuIdProcessed[i]) {
490 for (unsigned int j = lastAdjCpuIdProcessed+1; j < i; j++) {
491 // Skipped an ID, but /proc/stat is ordered => threads in between are offline
492 memset(&(this->cpuData[j]), '\0', sizeof(CPUData));
493 }
494 lastAdjCpuIdProcessed = i;
495 }
Nathan Scott72235d82023-05-02 16:56:18 +1000496 }
497
498 this->period = (double)this->cpuData[0].totalPeriod / super->activeCPUs;
499
Christian Göttsche49bb3c42024-08-09 17:57:10 +0200500 if (!ferror(file) && !feof(file)) {
501 char buffer[PROC_LINE_LENGTH + 1];
502 while (fgets(buffer, sizeof(buffer), file)) {
503 if (String_startsWith(buffer, "procs_running")) {
504 this->runningTasks = (unsigned int) strtoul(buffer + strlen("procs_running"), NULL, 10);
505 break;
506 }
Nathan Scott72235d82023-05-02 16:56:18 +1000507 }
508 }
509
510 fclose(file);
511}
512
513static int scanCPUFrequencyFromSysCPUFreq(LinuxMachine* this) {
514 const Machine* super = &this->super;
515 int numCPUsWithFrequency = 0;
516 unsigned long totalFrequency = 0;
517
518 /*
519 * On some AMD and Intel CPUs read()ing scaling_cur_freq is quite slow (> 1ms). This delay
520 * accumulates for every core. For details see issue#471.
521 * If the read on CPU 0 takes longer than 500us bail out and fall back to reading the
522 * frequencies from /proc/cpuinfo.
523 * Once the condition has been met, bail out early for the next couple of scans.
524 */
525 static int timeout = 0;
526
527 if (timeout > 0) {
528 timeout--;
529 return -1;
530 }
531
532 for (unsigned int i = 0; i < super->existingCPUs; ++i) {
533 if (!Machine_isCPUonline(super, i))
534 continue;
535
536 char pathBuffer[64];
537 xSnprintf(pathBuffer, sizeof(pathBuffer), "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i);
538
539 struct timespec start;
540 if (i == 0)
541 clock_gettime(CLOCK_MONOTONIC, &start);
542
543 FILE* file = fopen(pathBuffer, "r");
544 if (!file)
545 return -errno;
546
547 unsigned long frequency;
548 if (fscanf(file, "%lu", &frequency) == 1) {
549 /* convert kHz to MHz */
550 frequency = frequency / 1000;
551 this->cpuData[i + 1].frequency = frequency;
552 numCPUsWithFrequency++;
553 totalFrequency += frequency;
554 }
555
556 fclose(file);
557
558 if (i == 0) {
559 struct timespec end;
560 clock_gettime(CLOCK_MONOTONIC, &end);
561 const time_t timeTakenUs = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;
562 if (timeTakenUs > 500) {
563 timeout = 30;
564 return -1;
565 }
566 }
567 }
568
569 if (numCPUsWithFrequency > 0)
570 this->cpuData[0].frequency = (double)totalFrequency / numCPUsWithFrequency;
571
572 return 0;
573}
574
575static void scanCPUFrequencyFromCPUinfo(LinuxMachine* this) {
576 const Machine* super = &this->super;
577
578 FILE* file = fopen(PROCCPUINFOFILE, "r");
579 if (file == NULL)
580 return;
581
582 int numCPUsWithFrequency = 0;
583 double totalFrequency = 0;
584 int cpuid = -1;
585
586 while (!feof(file)) {
587 double frequency;
588 char buffer[PROC_LINE_LENGTH];
589
590 if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)
591 break;
592
593 if (sscanf(buffer, "processor : %d", &cpuid) == 1) {
594 continue;
595 } else if (
596 (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) ||
597 (sscanf(buffer, "clock : %lfMHz", &frequency) == 1)
598 ) {
599 if (cpuid < 0 || (unsigned int)cpuid > (super->existingCPUs - 1)) {
600 continue;
601 }
602
603 CPUData* cpuData = &(this->cpuData[cpuid + 1]);
604 /* do not override sysfs data */
Explorer09b4164332023-07-29 16:24:12 +0800605 if (!isNonnegative(cpuData->frequency)) {
Nathan Scott72235d82023-05-02 16:56:18 +1000606 cpuData->frequency = frequency;
607 }
608 numCPUsWithFrequency++;
609 totalFrequency += frequency;
610 } else if (buffer[0] == '\n') {
611 cpuid = -1;
612 }
613 }
614 fclose(file);
615
616 if (numCPUsWithFrequency > 0) {
617 this->cpuData[0].frequency = totalFrequency / numCPUsWithFrequency;
618 }
619}
620
Leah Neukirchen82667592023-12-25 14:52:19 +0100621#ifdef HAVE_SENSORS_SENSORS_H
622static void LinuxMachine_fetchCPUTopologyFromCPUinfo(LinuxMachine* this) {
623 const Machine* super = &this->super;
624
625 FILE* file = fopen(PROCCPUINFOFILE, "r");
626 if (file == NULL)
627 return;
628
629 int cpuid = -1;
630 int coreid = -1;
631 int physicalid = -1;
632
633 int max_physicalid = -1;
634 int max_coreid = -1;
635
636 while (!feof(file)) {
637 char *buffer = String_readLine(file);
638 if (!buffer)
639 break;
640
641 if (buffer[0] == '\0') { /* empty line after each cpu */
642 if (cpuid >= 0 && (unsigned int)cpuid < super->existingCPUs) {
643 CPUData* cpuData = &(this->cpuData[cpuid + 1]);
644 cpuData->coreID = coreid;
645 cpuData->physicalID = physicalid;
646
647 if (coreid > max_coreid)
648 max_coreid = coreid;
649 if (physicalid > max_physicalid)
650 max_physicalid = physicalid;
651
652 cpuid = -1;
653 coreid = -1;
654 physicalid = -1;
655 }
656 } else if (String_startsWith(buffer, "processor")) {
657 sscanf(buffer, "processor : %d", &cpuid);
658 } else if (String_startsWith(buffer, "physical id")) {
659 sscanf(buffer, "physical id : %d", &physicalid);
660 } else if (String_startsWith(buffer, "core id")) {
661 sscanf(buffer, "core id : %d", &coreid);
662 }
663
664 free(buffer);
665 }
666
667 this->maxPhysicalID = max_physicalid;
668 this->maxCoreID = max_coreid;
669
670 fclose(file);
671}
Leah Neukirchenc079bcc2023-12-25 14:52:56 +0100672
673static void LinuxMachine_assignCCDs(LinuxMachine* this, int ccds) {
674 /* For AMD k10temp/zenpower, temperatures are provided for CCDs only,
675 which is an aggregate of multiple cores.
676 There's no obvious mapping between hwmon sensors and sockets and CCDs.
677 Assume both are iterated in order.
678 Hypothesis: Each CCD has same size N = #Cores/#CCD
679 and is assigned N coreID in sequence.
680 Also assume all CPUs have same number of CCDs. */
681
682 const Machine* super = &this->super;
683 CPUData *cpus = this->cpuData;
684
685 if (ccds == 0) {
686 for (size_t i = 0; i < super->existingCPUs + 1; i++) {
687 cpus[i].ccdID = -1;
688 }
689 return;
690 }
691
692 int coresPerCCD = super->existingCPUs / ccds;
693
694 int ccd = 0;
695 int nc = coresPerCCD;
696 for (int p = 0; p <= (int)this->maxPhysicalID; p++) {
697 for (int c = 0; c <= (int)this->maxCoreID; c++) {
698 for (size_t i = 1; i <= super->existingCPUs; i++) {
699 if (cpus[i].physicalID != p || cpus[i].coreID != c)
700 continue;
701
702 cpus[i].ccdID = ccd;
703
704 if (--nc <= 0) {
705 nc = coresPerCCD;
706 ccd++;
707 }
708 }
709 }
710 }
711}
712
Leah Neukirchen82667592023-12-25 14:52:19 +0100713#endif
714
Nathan Scott72235d82023-05-02 16:56:18 +1000715static void LinuxMachine_scanCPUFrequency(LinuxMachine* this) {
716 const Machine* super = &this->super;
717
718 for (unsigned int i = 0; i <= super->existingCPUs; i++)
719 this->cpuData[i].frequency = NAN;
720
721 if (scanCPUFrequencyFromSysCPUFreq(this) == 0)
722 return;
723
724 scanCPUFrequencyFromCPUinfo(this);
725}
726
727void Machine_scan(Machine* super) {
728 LinuxMachine* this = (LinuxMachine*) super;
729
730 LinuxMachine_scanMemoryInfo(this);
731 LinuxMachine_scanHugePages(this);
732 LinuxMachine_scanZfsArcstats(this);
733 LinuxMachine_scanZramInfo(this);
Nathan Scott72235d82023-05-02 16:56:18 +1000734 LinuxMachine_scanCPUTime(this);
735
736 const Settings* settings = super->settings;
Leah Neukirchen82667592023-12-25 14:52:19 +0100737 if (settings->showCPUFrequency
738#ifdef HAVE_SENSORS_SENSORS_H
739 || settings->showCPUTemperature
740#endif
741 )
Nathan Scott72235d82023-05-02 16:56:18 +1000742 LinuxMachine_scanCPUFrequency(this);
743
744 #ifdef HAVE_SENSORS_SENSORS_H
745 if (settings->showCPUTemperature)
746 LibSensors_getCPUTemperatures(this->cpuData, super->existingCPUs, super->activeCPUs);
747 #endif
748}
749
750Machine* Machine_new(UsersTable* usersTable, uid_t userId) {
751 LinuxMachine* this = xCalloc(1, sizeof(LinuxMachine));
752 Machine* super = &this->super;
753
754 Machine_init(super, usersTable, userId);
755
756 // Initialize page size
757 if ((this->pageSize = sysconf(_SC_PAGESIZE)) == -1)
758 CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)");
759 this->pageSizeKB = this->pageSize / ONE_K;
760
761 // Initialize clock ticks
762 if ((this->jiffies = sysconf(_SC_CLK_TCK)) == -1)
763 CRT_fatalError("Cannot get clock ticks by sysconf(_SC_CLK_TCK)");
764
765 // Read btime (the kernel boot time, as number of seconds since the epoch)
766 FILE* statfile = fopen(PROCSTATFILE, "r");
767 if (statfile == NULL)
768 CRT_fatalError("Cannot open " PROCSTATFILE);
769
770 this->boottime = -1;
771
772 while (true) {
773 char buffer[PROC_LINE_LENGTH + 1];
774 if (fgets(buffer, sizeof(buffer), statfile) == NULL)
775 break;
776 if (String_startsWith(buffer, "btime ") == false)
777 continue;
778 if (sscanf(buffer, "btime %lld\n", &this->boottime) == 1)
779 break;
780 CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
781 }
782 fclose(statfile);
783
784 if (this->boottime == -1)
785 CRT_fatalError("No btime in " PROCSTATFILE);
786
787 // Initialize CPU count
788 LinuxMachine_updateCPUcount(this);
789
Leah Neukirchen82667592023-12-25 14:52:19 +0100790 #ifdef HAVE_SENSORS_SENSORS_H
791 // Fetch CPU topology
792 LinuxMachine_fetchCPUTopologyFromCPUinfo(this);
Leah Neukirchenc079bcc2023-12-25 14:52:56 +0100793 int ccds = LibSensors_countCCDs();
794 LinuxMachine_assignCCDs(this, ccds);
Leah Neukirchen82667592023-12-25 14:52:19 +0100795 #endif
796
Nathan Scott72235d82023-05-02 16:56:18 +1000797 return super;
798}
799
800void Machine_delete(Machine* super) {
801 LinuxMachine* this = (LinuxMachine*) super;
Christian Göttschef8c5bdd2023-08-29 13:03:31 +0200802 GPUEngineData* gpuEngineData = this->gpuEngineData;
803
Nathan Scott72235d82023-05-02 16:56:18 +1000804 Machine_done(super);
Christian Göttschef8c5bdd2023-08-29 13:03:31 +0200805
806 while (gpuEngineData) {
807 GPUEngineData* next = gpuEngineData->next;
808 free(gpuEngineData->key);
809 free(gpuEngineData);
810 gpuEngineData = next;
811 }
812
Nathan Scott72235d82023-05-02 16:56:18 +1000813 free(this->cpuData);
814 free(this);
815}
816
817bool Machine_isCPUonline(const Machine* super, unsigned int id) {
818 const LinuxMachine* this = (const LinuxMachine*) super;
819
820 assert(id < super->existingCPUs);
821 return this->cpuData[id + 1].online;
822}