master ohmu/fastfetch-openbsd / src / detection / top / top_linux.c
ohmu  ·  2026-10-15
  1#include "top.h"
  2
  3#include "common/debug.h"
  4#include "common/io.h"
  5#include "common/memrchr.h"
  6#include "common/strutil.h"
  7
  8#include <inttypes.h>
  9
 10#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
 11
 12static bool parseStat(const char* buffer, size_t length, FFTopProcessSnapshot* result) {
 13    const char* open = strchr(buffer, '(');
 14    const char* close = (const char*) memrchr(buffer, ')', length);
 15    if (!open || !close || close <= open || close + 2 >= buffer + length) {
 16        FF_DEBUG("parseStat: malformed stat content");
 17        return false;
 18    }
 19
 20    ffStrbufSetNS(&result->name, (uint32_t) (close - open - 1), open + 1);
 21
 22    const char* cursor = close + 2; // skip ") "
 23    if (__builtin_expect(cursor >= buffer + length, false)) {
 24        return false;
 25    }
 26
 27    char* p;
 28    for (uint32_t field = 3; field <= 22 /*start time*/; ++field) {
 29        switch (field) {
 30            case 9: // flags
 31                if ((strtoul(cursor, &p, 10) & PF_KTHREAD)) {
 32                    FF_DEBUG("parseStat: skipping kernel task %s", result->name.chars);
 33                    return false;
 34                }
 35                cursor = p;
 36                break;
 37
 38            case 14: // user
 39            case 15: // system
 40                result->cpuTime += (uint64_t) strtoull(cursor, &p, 10);
 41                cursor = p;
 42                break;
 43
 44            case 20: // number of threads
 45                result->threads = (uint32_t) strtoul(cursor, &p, 10);
 46                cursor = p;
 47                break;
 48
 49            case 22: // start time
 50                result->startTime = (uint64_t) strtoull(cursor, &p, 10);
 51                cursor = p;
 52                break;
 53
 54            default:
 55                while (*cursor != ' ' && __builtin_expect(*cursor != '\0', true)) {
 56                    ++cursor;
 57                }
 58                break;
 59        }
 60
 61        ++cursor;
 62        if (__builtin_expect(cursor >= buffer + length, false)) {
 63            return false;
 64        }
 65    }
 66
 67    return true;
 68}
 69
 70const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
 71    const long ticks = sysconf(_SC_CLK_TCK); // sysconf returns -1 ONLY if `_SC_CLK_TCK` is an invalid name
 72    const uint32_t pageSizeShift = instance.state.platform.sysinfo.pageSizeShift;
 73    FF_DEBUG("Scanning /proc: clk_tck=%ld, pageSizeShift=%u", ticks, (unsigned) pageSizeShift);
 74
 75    FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
 76    if (!dir) {
 77        FF_DEBUG("opendir(\"/proc\") failed: %s", strerror(errno));
 78        return "opendir(\"/proc\") failed";
 79    }
 80
 81    int procfd = dirfd(dir);
 82
 83    struct dirent* entry;
 84    char statBuffer[1024];
 85    char statmBuffer[256];
 86    while ((entry = readdir(dir))) {
 87        if (!entry->d_name[0] || !ffCharIsDigit(entry->d_name[0])) {
 88            continue;
 89        }
 90
 91        unsigned long pid = strtoul(entry->d_name, nullptr, 10);
 92        if (pid == 0 || pid > UINT32_MAX) {
 93            FF_DEBUG("Skip invalid pid directory: %s", entry->d_name);
 94            continue;
 95        }
 96
 97        FF_AUTO_CLOSE_FD int subfd = openat(procfd, entry->d_name, O_RDONLY | O_DIRECTORY);
 98        if (subfd < 0) {
 99            FF_DEBUG("openat(/proc/%s) failed: %s", entry->d_name, strerror(errno));
100            continue;
101        }
102
103        ssize_t statLength;
104        if ((statLength = ffReadFileDataRelative(subfd, "stat", sizeof(statBuffer) - 1, statBuffer)) < 0) {
105            FF_DEBUG("Failed to read /proc/%s/stat: %s", entry->d_name, strerror(errno));
106            continue;
107        }
108        statBuffer[statLength] = '\0';
109
110        ssize_t statmLength = 0;
111        if (showTypes & FF_TOP_TYPE_MEMORY) {
112            if ((statmLength = ffReadFileDataRelative(subfd, "statm", sizeof(statmBuffer) - 1, statmBuffer)) < 0) {
113                FF_DEBUG("Failed to read /proc/%s/statm: %s", entry->d_name, strerror(errno));
114                statmLength = 0;
115            }
116            statmBuffer[statmLength] = '\0';
117        }
118
119        char ioBuffer[512];
120        ssize_t ioLength = 0;
121        if (showTypes & FF_TOP_TYPE_DISK) {
122            if ((ioLength = ffReadFileDataRelative(subfd, "io", sizeof(ioBuffer) - 1, ioBuffer)) < 0) {
123                FF_DEBUG("Failed to read /proc/%s/io: %s", entry->d_name, strerror(errno));
124                ioLength = 0;
125            }
126            ioBuffer[ioLength] = '\0';
127        }
128
129        auto snapshot = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
130        ffStrbufInit(&snapshot->name);
131        snapshot->threads = 0;
132        snapshot->cpuTime = 0;
133        snapshot->startTime = 0;
134
135        if (!parseStat(statBuffer, (size_t) statLength, snapshot)) {
136            ffStrbufDestroy(&snapshot->name);
137            --snapshots->length;
138            continue;
139        }
140
141        uint64_t rssPages = 0;
142        if (__builtin_expect((showTypes & FF_TOP_TYPE_MEMORY) && sscanf(statmBuffer, "%*u %" SCNu64, &rssPages) != 1, false)) {
143            FF_DEBUG("Failed to parse statm of /proc/%lu", pid);
144            ffStrbufDestroy(&snapshot->name);
145            --snapshots->length;
146            continue;
147        }
148
149        snapshot->pid = (uint32_t) pid;
150        snapshot->memBytes = (uint64_t) rssPages << pageSizeShift;
151        snapshot->bytesRead = 0;
152        snapshot->bytesWritten = 0;
153        if (ioLength > 0) {
154            sscanf(ioBuffer, "rchar: %*u\nwchar: %*u\nsyscr: %*u\nsyscw: %*u\nread_bytes: %" SCNu64 "\nwrite_bytes: %" SCNu64, &snapshot->bytesRead, &snapshot->bytesWritten);
155        }
156        snapshot->cpuTime = snapshot->cpuTime * 1000u / (uint64_t) ticks;
157        FF_DEBUG(
158            "Captured process %u (%s): cpuTime=%" PRIu64 "ms, mem=%" PRIu64 "B, diskRead=%" PRIu64 "B, diskWrite=%" PRIu64 "B",
159            snapshot->pid, snapshot->name.chars, snapshot->cpuTime, snapshot->memBytes, snapshot->bytesRead, snapshot->bytesWritten);
160    }
161    FF_DEBUG("Captured %u process snapshots in total", snapshots->length);
162    return nullptr;
163}