| /* |
| htop - LinuxMachine.c |
| (C) 2014 Hisham H. Muhammad |
| Released under the GNU GPLv2+, see the COPYING file |
| in the source distribution for its full text. |
| */ |
| |
| #include "config.h" // IWYU pragma: keep |
| |
| #include "linux/LinuxMachine.h" |
| |
| #include <assert.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <math.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <unistd.h> |
| #include <time.h> |
| |
| #include "Compat.h" |
| #include "CRT.h" |
| #include "Macros.h" |
| #include "ProcessTable.h" |
| #include "Row.h" |
| #include "Settings.h" |
| #include "UsersTable.h" |
| #include "XUtils.h" |
| |
| #include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep |
| |
| #ifdef HAVE_SENSORS_SENSORS_H |
| #include "LibSensors.h" |
| #endif |
| |
| #ifndef O_PATH |
| #define O_PATH 010000000 // declare for ancient glibc versions |
| #endif |
| |
| /* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF |
| * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD |
| */ |
| static void LinuxMachine_updateCPUcount(LinuxMachine* this) { |
| unsigned int existing = 0, active = 0; |
| Machine* super = &this->super; |
| |
| // Initialize the cpuData array before anything else. |
| if (!this->cpuData) { |
| this->cpuData = xCalloc(2, sizeof(CPUData)); |
| this->cpuData[0].online = true; /* average is always "online" */ |
| this->cpuData[1].online = true; |
| super->activeCPUs = 1; |
| super->existingCPUs = 1; |
| } |
| |
| DIR* dir = opendir("/sys/devices/system/cpu"); |
| if (!dir) |
| return; |
| |
| unsigned int currExisting = super->existingCPUs; |
| |
| const struct dirent* entry; |
| while ((entry = readdir(dir)) != NULL) { |
| if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) |
| continue; |
| |
| if (!String_startsWith(entry->d_name, "cpu")) |
| continue; |
| |
| char* endp; |
| unsigned long int id = strtoul(entry->d_name + 3, &endp, 10); |
| if (id == ULONG_MAX || endp == entry->d_name + 3 || *endp != '\0') |
| continue; |
| |
| #ifdef HAVE_OPENAT |
| int cpuDirFd = openat(xDirfd(dir), entry->d_name, O_DIRECTORY | O_PATH | O_NOFOLLOW); |
| if (cpuDirFd < 0) |
| continue; |
| #else |
| char cpuDirFd[4096]; |
| xSnprintf(cpuDirFd, sizeof(cpuDirFd), "/sys/devices/system/cpu/%s", entry->d_name); |
| #endif |
| |
| existing++; |
| |
| /* readdir() iterates with no specific order */ |
| unsigned int max = MAXIMUM(existing, id + 1); |
| if (max > currExisting) { |
| this->cpuData = xReallocArrayZero(this->cpuData, currExisting ? (currExisting + 1) : 0, max + /* aggregate */ 1, sizeof(CPUData)); |
| this->cpuData[0].online = true; /* average is always "online" */ |
| currExisting = max; |
| } |
| |
| char buffer[8]; |
| ssize_t res = xReadfileat(cpuDirFd, "online", buffer, sizeof(buffer)); |
| /* If the file "online" does not exist or on failure count as active */ |
| if (res < 1 || buffer[0] != '0') { |
| active++; |
| this->cpuData[id + 1].online = true; |
| } else { |
| this->cpuData[id + 1].online = false; |
| } |
| |
| Compat_openatArgClose(cpuDirFd); |
| } |
| |
| closedir(dir); |
| |
| // return if no CPU is found |
| if (existing < 1) |
| return; |
| |
| #ifdef HAVE_SENSORS_SENSORS_H |
| /* When started with offline CPUs, libsensors does not monitor those, |
| * even when they become online. */ |
| if (super->existingCPUs != 0 && (active > super->activeCPUs || currExisting > super->existingCPUs)) |
| LibSensors_reload(); |
| #endif |
| |
| super->activeCPUs = active; |
| assert(existing == currExisting); |
| super->existingCPUs = currExisting; |
| } |
| |
| static void LinuxMachine_scanMemoryInfo(LinuxMachine* this) { |
| Machine* host = &this->super; |
| memory_t availableMem = 0; |
| memory_t freeMem = 0; |
| memory_t totalMem = 0; |
| memory_t buffersMem = 0; |
| memory_t cachedMem = 0; |
| memory_t sharedMem = 0; |
| memory_t swapTotalMem = 0; |
| memory_t swapCacheMem = 0; |
| memory_t swapFreeMem = 0; |
| memory_t sreclaimableMem = 0; |
| memory_t zswapCompMem = 0; |
| memory_t zswapOrigMem = 0; |
| |
| FILE* file = fopen(PROCMEMINFOFILE, "r"); |
| if (!file) |
| CRT_fatalError("Cannot open " PROCMEMINFOFILE); |
| |
| char buffer[128]; |
| while (fgets(buffer, sizeof(buffer), file)) { |
| |
| #define tryRead(label, variable) \ |
| if (String_startsWith(buffer, label)) { \ |
| memory_t parsed_; \ |
| if (sscanf(buffer + strlen(label), "%llu kB", &parsed_) == 1) { \ |
| (variable) = parsed_; \ |
| } \ |
| break; \ |
| } else (void) 0 /* Require a ";" after the macro use. */ |
| |
| switch (buffer[0]) { |
| case 'M': |
| tryRead("MemAvailable:", availableMem); |
| tryRead("MemFree:", freeMem); |
| tryRead("MemTotal:", totalMem); |
| break; |
| case 'B': |
| tryRead("Buffers:", buffersMem); |
| break; |
| case 'C': |
| tryRead("Cached:", cachedMem); |
| break; |
| case 'S': |
| switch (buffer[1]) { |
| case 'h': |
| tryRead("Shmem:", sharedMem); |
| break; |
| case 'w': |
| tryRead("SwapTotal:", swapTotalMem); |
| tryRead("SwapCached:", swapCacheMem); |
| tryRead("SwapFree:", swapFreeMem); |
| break; |
| case 'R': |
| tryRead("SReclaimable:", sreclaimableMem); |
| break; |
| } |
| break; |
| case 'Z': |
| tryRead("Zswap:", zswapCompMem); |
| tryRead("Zswapped:", zswapOrigMem); |
| break; |
| } |
| |
| #undef tryRead |
| } |
| |
| fclose(file); |
| |
| /* |
| * Compute memory partition like procps(free) |
| * https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c |
| * |
| * Adjustments: |
| * - Shmem in part of Cached (see https://lore.kernel.org/patchwork/patch/648763/), |
| * do not show twice by subtracting from Cached and do not subtract twice from used. |
| */ |
| host->totalMem = totalMem; |
| host->cachedMem = cachedMem + sreclaimableMem - sharedMem; |
| host->sharedMem = sharedMem; |
| const memory_t usedDiff = freeMem + cachedMem + sreclaimableMem + buffersMem; |
| host->usedMem = (totalMem >= usedDiff) ? totalMem - usedDiff : totalMem - freeMem; |
| host->buffersMem = buffersMem; |
| host->availableMem = availableMem != 0 ? MINIMUM(availableMem, totalMem) : freeMem; |
| host->totalSwap = swapTotalMem; |
| host->usedSwap = swapTotalMem - swapFreeMem - swapCacheMem; |
| host->cachedSwap = swapCacheMem; |
| this->zswap.usedZswapComp = zswapCompMem; |
| this->zswap.usedZswapOrig = zswapOrigMem; |
| } |
| |
| static void LinuxMachine_scanHugePages(LinuxMachine* this) { |
| this->totalHugePageMem = 0; |
| for (unsigned i = 0; i < HTOP_HUGEPAGE_COUNT; i++) { |
| this->usedHugePageMem[i] = MEMORY_MAX; |
| } |
| |
| DIR* dir = opendir("/sys/kernel/mm/hugepages"); |
| if (!dir) |
| return; |
| |
| const struct dirent* entry; |
| while ((entry = readdir(dir)) != NULL) { |
| const char* name = entry->d_name; |
| |
| /* Ignore all non-directories */ |
| if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) |
| continue; |
| |
| if (!String_startsWith(name, "hugepages-")) |
| continue; |
| |
| char* endptr; |
| unsigned long int hugePageSize = strtoul(name + strlen("hugepages-"), &endptr, 10); |
| if (!endptr || *endptr != 'k') |
| continue; |
| |
| char content[64]; |
| char hugePagePath[128]; |
| ssize_t r; |
| |
| xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/nr_hugepages", name); |
| r = xReadfile(hugePagePath, content, sizeof(content)); |
| if (r <= 0) |
| continue; |
| |
| memory_t total = strtoull(content, NULL, 10); |
| if (total == 0) |
| continue; |
| |
| xSnprintf(hugePagePath, sizeof(hugePagePath), "/sys/kernel/mm/hugepages/%s/free_hugepages", name); |
| r = xReadfile(hugePagePath, content, sizeof(content)); |
| if (r <= 0) |
| continue; |
| |
| memory_t free = strtoull(content, NULL, 10); |
| |
| int shift = ffsl(hugePageSize) - 1 - (HTOP_HUGEPAGE_BASE_SHIFT - 10); |
| assert(shift >= 0 && shift < HTOP_HUGEPAGE_COUNT); |
| |
| this->totalHugePageMem += total * hugePageSize; |
| this->usedHugePageMem[shift] = (total - free) * hugePageSize; |
| } |
| |
| closedir(dir); |
| } |
| |
| static void LinuxMachine_scanZramInfo(LinuxMachine* this) { |
| memory_t totalZram = 0; |
| memory_t usedZramComp = 0; |
| memory_t usedZramOrig = 0; |
| |
| char mm_stat[34]; |
| char disksize[34]; |
| |
| unsigned int i = 0; |
| for (;;) { |
| xSnprintf(mm_stat, sizeof(mm_stat), "/sys/block/zram%u/mm_stat", i); |
| xSnprintf(disksize, sizeof(disksize), "/sys/block/zram%u/disksize", i); |
| i++; |
| FILE* disksize_file = fopen(disksize, "r"); |
| FILE* mm_stat_file = fopen(mm_stat, "r"); |
| if (disksize_file == NULL || mm_stat_file == NULL) { |
| if (disksize_file) { |
| fclose(disksize_file); |
| } |
| if (mm_stat_file) { |
| fclose(mm_stat_file); |
| } |
| break; |
| } |
| memory_t size = 0; |
| memory_t orig_data_size = 0; |
| memory_t compr_data_size = 0; |
| |
| if (1 != fscanf(disksize_file, "%llu\n", &size) || |
| 2 != fscanf(mm_stat_file, " %llu %llu", &orig_data_size, &compr_data_size)) { |
| fclose(disksize_file); |
| fclose(mm_stat_file); |
| break; |
| } |
| |
| totalZram += size; |
| usedZramComp += compr_data_size; |
| usedZramOrig += orig_data_size; |
| |
| fclose(disksize_file); |
| fclose(mm_stat_file); |
| } |
| |
| this->zram.totalZram = totalZram / 1024; |
| this->zram.usedZramComp = usedZramComp / 1024; |
| this->zram.usedZramOrig = usedZramOrig / 1024; |
| if (this->zram.usedZramComp > this->zram.usedZramOrig) { |
| this->zram.usedZramComp = this->zram.usedZramOrig; |
| } |
| } |
| |
| static void LinuxMachine_scanZfsArcstats(LinuxMachine* this) { |
| memory_t dbufSize = 0; |
| memory_t dnodeSize = 0; |
| memory_t bonusSize = 0; |
| |
| FILE* file = fopen(PROCARCSTATSFILE, "r"); |
| if (file == NULL) { |
| this->zfs.enabled = 0; |
| return; |
| } |
| char buffer[128]; |
| while (fgets(buffer, 128, file)) { |
| #define tryRead(label, variable) \ |
| if (String_startsWith(buffer, label)) { \ |
| sscanf(buffer + strlen(label), " %*2u %32llu", variable); \ |
| break; \ |
| } else (void) 0 /* Require a ";" after the macro use. */ |
| #define tryReadFlag(label, variable, flag) \ |
| if (String_startsWith(buffer, label)) { \ |
| (flag) = (1 == sscanf(buffer + strlen(label), " %*2u %32llu", variable)); \ |
| break; \ |
| } else (void) 0 /* Require a ";" after the macro use. */ |
| |
| switch (buffer[0]) { |
| case 'c': |
| tryRead("c_min", &this->zfs.min); |
| tryRead("c_max", &this->zfs.max); |
| tryReadFlag("compressed_size", &this->zfs.compressed, this->zfs.isCompressed); |
| break; |
| case 'u': |
| tryRead("uncompressed_size", &this->zfs.uncompressed); |
| break; |
| case 's': |
| tryRead("size", &this->zfs.size); |
| break; |
| case 'h': |
| tryRead("hdr_size", &this->zfs.header); |
| break; |
| case 'd': |
| tryRead("dbuf_size", &dbufSize); |
| tryRead("dnode_size", &dnodeSize); |
| break; |
| case 'b': |
| tryRead("bonus_size", &bonusSize); |
| break; |
| case 'a': |
| tryRead("anon_size", &this->zfs.anon); |
| break; |
| case 'm': |
| tryRead("mfu_size", &this->zfs.MFU); |
| tryRead("mru_size", &this->zfs.MRU); |
| break; |
| } |
| |
| #undef tryRead |
| #undef tryReadFlag |
| } |
| fclose(file); |
| |
| this->zfs.enabled = (this->zfs.size > 0 ? 1 : 0); |
| this->zfs.size /= 1024; |
| this->zfs.min /= 1024; |
| this->zfs.max /= 1024; |
| this->zfs.MFU /= 1024; |
| this->zfs.MRU /= 1024; |
| this->zfs.anon /= 1024; |
| this->zfs.header /= 1024; |
| this->zfs.other = (dbufSize + dnodeSize + bonusSize) / 1024; |
| if ( this->zfs.isCompressed ) { |
| this->zfs.compressed /= 1024; |
| this->zfs.uncompressed /= 1024; |
| } |
| } |
| |
| static void LinuxMachine_scanCPUTime(LinuxMachine* this) { |
| const Machine* super = &this->super; |
| |
| LinuxMachine_updateCPUcount(this); |
| |
| FILE* file = fopen(PROCSTATFILE, "r"); |
| if (!file) |
| CRT_fatalError("Cannot open " PROCSTATFILE); |
| |
| // Add an extra phantom thread for a later loop |
| bool adjCpuIdProcessed[super->existingCPUs+2]; |
| memset(adjCpuIdProcessed, 0, sizeof(adjCpuIdProcessed)); |
| |
| for (unsigned int i = 0; i <= super->existingCPUs; i++) { |
| char buffer[PROC_LINE_LENGTH + 1]; |
| unsigned long long int usertime, nicetime, systemtime, idletime; |
| unsigned long long int ioWait = 0, irq = 0, softIrq = 0, steal = 0, guest = 0, guestnice = 0; |
| |
| const char* ok = fgets(buffer, sizeof(buffer), file); |
| if (!ok) |
| break; |
| |
| // cpu fields are sorted first |
| if (!String_startsWith(buffer, "cpu")) |
| break; |
| |
| // Depending on your kernel version, |
| // 5, 7, 8 or 9 of these fields will be set. |
| // The rest will remain at zero. |
| unsigned int adjCpuId; |
| if (i == 0) { |
| (void) sscanf(buffer, "cpu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice); |
| adjCpuId = 0; |
| } else { |
| unsigned int cpuid; |
| (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); |
| adjCpuId = cpuid + 1; |
| } |
| |
| if (adjCpuId > super->existingCPUs) |
| break; |
| |
| // Guest time is already accounted in usertime |
| usertime -= guest; |
| nicetime -= guestnice; |
| // Fields existing on kernels >= 2.6 |
| // (and RHEL's patched kernel 2.4...) |
| unsigned long long int idlealltime = idletime + ioWait; |
| unsigned long long int systemalltime = systemtime + irq + softIrq; |
| unsigned long long int virtalltime = guest + guestnice; |
| unsigned long long int totaltime = usertime + nicetime + systemalltime + idlealltime + steal + virtalltime; |
| CPUData* cpuData = &(this->cpuData[adjCpuId]); |
| // Since we do a subtraction (usertime - guest) and cputime64_to_clock_t() |
| // used in /proc/stat rounds down numbers, it can lead to a case where the |
| // integer overflow. |
| cpuData->userPeriod = saturatingSub(usertime, cpuData->userTime); |
| cpuData->nicePeriod = saturatingSub(nicetime, cpuData->niceTime); |
| cpuData->systemPeriod = saturatingSub(systemtime, cpuData->systemTime); |
| cpuData->systemAllPeriod = saturatingSub(systemalltime, cpuData->systemAllTime); |
| cpuData->idleAllPeriod = saturatingSub(idlealltime, cpuData->idleAllTime); |
| cpuData->idlePeriod = saturatingSub(idletime, cpuData->idleTime); |
| cpuData->ioWaitPeriod = saturatingSub(ioWait, cpuData->ioWaitTime); |
| cpuData->irqPeriod = saturatingSub(irq, cpuData->irqTime); |
| cpuData->softIrqPeriod = saturatingSub(softIrq, cpuData->softIrqTime); |
| cpuData->stealPeriod = saturatingSub(steal, cpuData->stealTime); |
| cpuData->guestPeriod = saturatingSub(virtalltime, cpuData->guestTime); |
| cpuData->totalPeriod = saturatingSub(totaltime, cpuData->totalTime); |
| cpuData->userTime = usertime; |
| cpuData->niceTime = nicetime; |
| cpuData->systemTime = systemtime; |
| cpuData->systemAllTime = systemalltime; |
| cpuData->idleAllTime = idlealltime; |
| cpuData->idleTime = idletime; |
| cpuData->ioWaitTime = ioWait; |
| cpuData->irqTime = irq; |
| cpuData->softIrqTime = softIrq; |
| cpuData->stealTime = steal; |
| cpuData->guestTime = virtalltime; |
| cpuData->totalTime = totaltime; |
| |
| adjCpuIdProcessed[adjCpuId] = true; |
| } |
| |
| // Set the extra phantom thread as checked to make sure to mark trailing offline threads correctly in the loop |
| adjCpuIdProcessed[super->existingCPUs+1] = true; |
| unsigned int lastAdjCpuIdProcessed = 0; |
| for (unsigned int i = 0; i <= super->existingCPUs+1; i++) { |
| if (adjCpuIdProcessed[i]) { |
| for (unsigned int j = lastAdjCpuIdProcessed+1; j < i; j++) { |
| // Skipped an ID, but /proc/stat is ordered => threads in between are offline |
| memset(&(this->cpuData[j]), '\0', sizeof(CPUData)); |
| } |
| lastAdjCpuIdProcessed = i; |
| } |
| } |
| |
| this->period = (double)this->cpuData[0].totalPeriod / super->activeCPUs; |
| |
| if (!ferror(file) && !feof(file)) { |
| char buffer[PROC_LINE_LENGTH + 1]; |
| while (fgets(buffer, sizeof(buffer), file)) { |
| if (String_startsWith(buffer, "procs_running")) { |
| this->runningTasks = (unsigned int) strtoul(buffer + strlen("procs_running"), NULL, 10); |
| break; |
| } |
| } |
| } |
| |
| fclose(file); |
| } |
| |
| static int scanCPUFrequencyFromSysCPUFreq(LinuxMachine* this) { |
| const Machine* super = &this->super; |
| int numCPUsWithFrequency = 0; |
| unsigned long totalFrequency = 0; |
| |
| /* |
| * On some AMD and Intel CPUs read()ing scaling_cur_freq is quite slow (> 1ms). This delay |
| * accumulates for every core. For details see issue#471. |
| * If the read on CPU 0 takes longer than 500us bail out and fall back to reading the |
| * frequencies from /proc/cpuinfo. |
| * Once the condition has been met, bail out early for the next couple of scans. |
| */ |
| static int timeout = 0; |
| |
| if (timeout > 0) { |
| timeout--; |
| return -1; |
| } |
| |
| for (unsigned int i = 0; i < super->existingCPUs; ++i) { |
| if (!Machine_isCPUonline(super, i)) |
| continue; |
| |
| char pathBuffer[64]; |
| xSnprintf(pathBuffer, sizeof(pathBuffer), "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i); |
| |
| struct timespec start; |
| if (i == 0) |
| clock_gettime(CLOCK_MONOTONIC, &start); |
| |
| FILE* file = fopen(pathBuffer, "r"); |
| if (!file) |
| return -errno; |
| |
| unsigned long frequency; |
| if (fscanf(file, "%lu", &frequency) == 1) { |
| /* convert kHz to MHz */ |
| frequency = frequency / 1000; |
| this->cpuData[i + 1].frequency = frequency; |
| numCPUsWithFrequency++; |
| totalFrequency += frequency; |
| } |
| |
| fclose(file); |
| |
| if (i == 0) { |
| struct timespec end; |
| clock_gettime(CLOCK_MONOTONIC, &end); |
| const time_t timeTakenUs = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000; |
| if (timeTakenUs > 500) { |
| timeout = 30; |
| return -1; |
| } |
| } |
| } |
| |
| if (numCPUsWithFrequency > 0) |
| this->cpuData[0].frequency = (double)totalFrequency / numCPUsWithFrequency; |
| |
| return 0; |
| } |
| |
| static void scanCPUFrequencyFromCPUinfo(LinuxMachine* this) { |
| const Machine* super = &this->super; |
| |
| FILE* file = fopen(PROCCPUINFOFILE, "r"); |
| if (file == NULL) |
| return; |
| |
| int numCPUsWithFrequency = 0; |
| double totalFrequency = 0; |
| int cpuid = -1; |
| |
| while (!feof(file)) { |
| double frequency; |
| char buffer[PROC_LINE_LENGTH]; |
| |
| if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL) |
| break; |
| |
| if (sscanf(buffer, "processor : %d", &cpuid) == 1) { |
| continue; |
| } else if ( |
| (sscanf(buffer, "cpu MHz : %lf", &frequency) == 1) || |
| (sscanf(buffer, "clock : %lfMHz", &frequency) == 1) |
| ) { |
| if (cpuid < 0 || (unsigned int)cpuid > (super->existingCPUs - 1)) { |
| continue; |
| } |
| |
| CPUData* cpuData = &(this->cpuData[cpuid + 1]); |
| /* do not override sysfs data */ |
| if (!isNonnegative(cpuData->frequency)) { |
| cpuData->frequency = frequency; |
| } |
| numCPUsWithFrequency++; |
| totalFrequency += frequency; |
| } else if (buffer[0] == '\n') { |
| cpuid = -1; |
| } |
| } |
| fclose(file); |
| |
| if (numCPUsWithFrequency > 0) { |
| this->cpuData[0].frequency = totalFrequency / numCPUsWithFrequency; |
| } |
| } |
| |
| #ifdef HAVE_SENSORS_SENSORS_H |
| static void LinuxMachine_fetchCPUTopologyFromCPUinfo(LinuxMachine* this) { |
| const Machine* super = &this->super; |
| |
| FILE* file = fopen(PROCCPUINFOFILE, "r"); |
| if (file == NULL) |
| return; |
| |
| int cpuid = -1; |
| int coreid = -1; |
| int physicalid = -1; |
| |
| int max_physicalid = -1; |
| int max_coreid = -1; |
| |
| while (!feof(file)) { |
| char *buffer = String_readLine(file); |
| if (!buffer) |
| break; |
| |
| if (buffer[0] == '\0') { /* empty line after each cpu */ |
| if (cpuid >= 0 && (unsigned int)cpuid < super->existingCPUs) { |
| CPUData* cpuData = &(this->cpuData[cpuid + 1]); |
| cpuData->coreID = coreid; |
| cpuData->physicalID = physicalid; |
| |
| if (coreid > max_coreid) |
| max_coreid = coreid; |
| if (physicalid > max_physicalid) |
| max_physicalid = physicalid; |
| |
| cpuid = -1; |
| coreid = -1; |
| physicalid = -1; |
| } |
| } else if (String_startsWith(buffer, "processor")) { |
| sscanf(buffer, "processor : %d", &cpuid); |
| } else if (String_startsWith(buffer, "physical id")) { |
| sscanf(buffer, "physical id : %d", &physicalid); |
| } else if (String_startsWith(buffer, "core id")) { |
| sscanf(buffer, "core id : %d", &coreid); |
| } |
| |
| free(buffer); |
| } |
| |
| this->maxPhysicalID = max_physicalid; |
| this->maxCoreID = max_coreid; |
| |
| fclose(file); |
| } |
| |
| static void LinuxMachine_assignCCDs(LinuxMachine* this, int ccds) { |
| /* For AMD k10temp/zenpower, temperatures are provided for CCDs only, |
| which is an aggregate of multiple cores. |
| There's no obvious mapping between hwmon sensors and sockets and CCDs. |
| Assume both are iterated in order. |
| Hypothesis: Each CCD has same size N = #Cores/#CCD |
| and is assigned N coreID in sequence. |
| Also assume all CPUs have same number of CCDs. */ |
| |
| const Machine* super = &this->super; |
| CPUData *cpus = this->cpuData; |
| |
| if (ccds == 0) { |
| for (size_t i = 0; i < super->existingCPUs + 1; i++) { |
| cpus[i].ccdID = -1; |
| } |
| return; |
| } |
| |
| int coresPerCCD = super->existingCPUs / ccds; |
| |
| int ccd = 0; |
| int nc = coresPerCCD; |
| for (int p = 0; p <= (int)this->maxPhysicalID; p++) { |
| for (int c = 0; c <= (int)this->maxCoreID; c++) { |
| for (size_t i = 1; i <= super->existingCPUs; i++) { |
| if (cpus[i].physicalID != p || cpus[i].coreID != c) |
| continue; |
| |
| cpus[i].ccdID = ccd; |
| |
| if (--nc <= 0) { |
| nc = coresPerCCD; |
| ccd++; |
| } |
| } |
| } |
| } |
| } |
| |
| #endif |
| |
| static void LinuxMachine_scanCPUFrequency(LinuxMachine* this) { |
| const Machine* super = &this->super; |
| |
| for (unsigned int i = 0; i <= super->existingCPUs; i++) |
| this->cpuData[i].frequency = NAN; |
| |
| if (scanCPUFrequencyFromSysCPUFreq(this) == 0) |
| return; |
| |
| scanCPUFrequencyFromCPUinfo(this); |
| } |
| |
| void Machine_scan(Machine* super) { |
| LinuxMachine* this = (LinuxMachine*) super; |
| |
| LinuxMachine_scanMemoryInfo(this); |
| LinuxMachine_scanHugePages(this); |
| LinuxMachine_scanZfsArcstats(this); |
| LinuxMachine_scanZramInfo(this); |
| LinuxMachine_scanCPUTime(this); |
| |
| const Settings* settings = super->settings; |
| if (settings->showCPUFrequency |
| #ifdef HAVE_SENSORS_SENSORS_H |
| || settings->showCPUTemperature |
| #endif |
| ) |
| LinuxMachine_scanCPUFrequency(this); |
| |
| #ifdef HAVE_SENSORS_SENSORS_H |
| if (settings->showCPUTemperature) |
| LibSensors_getCPUTemperatures(this->cpuData, super->existingCPUs, super->activeCPUs); |
| #endif |
| } |
| |
| Machine* Machine_new(UsersTable* usersTable, uid_t userId) { |
| LinuxMachine* this = xCalloc(1, sizeof(LinuxMachine)); |
| Machine* super = &this->super; |
| |
| Machine_init(super, usersTable, userId); |
| |
| // Initialize page size |
| if ((this->pageSize = sysconf(_SC_PAGESIZE)) == -1) |
| CRT_fatalError("Cannot get pagesize by sysconf(_SC_PAGESIZE)"); |
| this->pageSizeKB = this->pageSize / ONE_K; |
| |
| // Initialize clock ticks |
| if ((this->jiffies = sysconf(_SC_CLK_TCK)) == -1) |
| CRT_fatalError("Cannot get clock ticks by sysconf(_SC_CLK_TCK)"); |
| |
| // Read btime (the kernel boot time, as number of seconds since the epoch) |
| FILE* statfile = fopen(PROCSTATFILE, "r"); |
| if (statfile == NULL) |
| CRT_fatalError("Cannot open " PROCSTATFILE); |
| |
| this->boottime = -1; |
| |
| while (true) { |
| char buffer[PROC_LINE_LENGTH + 1]; |
| if (fgets(buffer, sizeof(buffer), statfile) == NULL) |
| break; |
| if (String_startsWith(buffer, "btime ") == false) |
| continue; |
| if (sscanf(buffer, "btime %lld\n", &this->boottime) == 1) |
| break; |
| CRT_fatalError("Failed to parse btime from " PROCSTATFILE); |
| } |
| fclose(statfile); |
| |
| if (this->boottime == -1) |
| CRT_fatalError("No btime in " PROCSTATFILE); |
| |
| // Initialize CPU count |
| LinuxMachine_updateCPUcount(this); |
| |
| #ifdef HAVE_SENSORS_SENSORS_H |
| // Fetch CPU topology |
| LinuxMachine_fetchCPUTopologyFromCPUinfo(this); |
| int ccds = LibSensors_countCCDs(); |
| LinuxMachine_assignCCDs(this, ccds); |
| #endif |
| |
| return super; |
| } |
| |
| void Machine_delete(Machine* super) { |
| LinuxMachine* this = (LinuxMachine*) super; |
| GPUEngineData* gpuEngineData = this->gpuEngineData; |
| |
| Machine_done(super); |
| |
| while (gpuEngineData) { |
| GPUEngineData* next = gpuEngineData->next; |
| free(gpuEngineData->key); |
| free(gpuEngineData); |
| gpuEngineData = next; |
| } |
| |
| free(this->cpuData); |
| free(this); |
| } |
| |
| bool Machine_isCPUonline(const Machine* super, unsigned int id) { |
| const LinuxMachine* this = (const LinuxMachine*) super; |
| |
| assert(id < super->existingCPUs); |
| return this->cpuData[id + 1].online; |
| } |