LinuxProcessList: refactor /proc/stat parsing

Combine reading CPU count and CPU usage, only open the file once.
Do not separately initialize totalPeriod and totalTime, cause the value
0 is handled in Platform_setCPUValues().

Take the number of currently running process from the entry
procs_running in /proc/stat instead of counting all scanned process
with state 'R', to include hidden tasks, e.g. threads.
This commit is contained in:
Christian Göttsche 2021-02-17 16:26:10 +01:00
parent 521f1343e3
commit 0cfc9b0980

View File

@ -163,43 +163,29 @@ static void LinuxProcessList_initNetlinkSocket(LinuxProcessList* this) {
#endif
static int LinuxProcessList_computeCPUcount(void) {
FILE* file = fopen(PROCSTATFILE, "r");
if (file == NULL) {
CRT_fatalError("Cannot open " PROCSTATFILE);
}
static void LinuxProcessList_updateCPUcount(ProcessList* super, FILE* stream) {
LinuxProcessList* this = (LinuxProcessList*) super;
int cpus = 0;
char buffer[PROC_LINE_LENGTH + 1];
while (fgets(buffer, sizeof(buffer), file)) {
while (fgets(buffer, sizeof(buffer), stream)) {
if (String_startsWith(buffer, "cpu")) {
cpus++;
}
}
fclose(file);
if (cpus == 0)
CRT_fatalError("No cpu entry in " PROCSTATFILE);
if (cpus == 1)
CRT_fatalError("No cpu aggregate or cpuN entry in " PROCSTATFILE);
/* subtract raw cpu entry */
if (cpus > 0) {
cpus--;
}
/* Subtract aggregate cpu entry */
cpus--;
return cpus;
}
static void LinuxProcessList_updateCPUcount(LinuxProcessList* this) {
ProcessList* pl = &(this->super);
int cpus = LinuxProcessList_computeCPUcount();
if (cpus == 0 || cpus == pl->cpuCount)
return;
pl->cpuCount = cpus;
free(this->cpus);
this->cpus = xCalloc(cpus + 1, sizeof(CPUData));
for (int i = 0; i <= cpus; i++) {
this->cpus[i].totalTime = 1;
this->cpus[i].totalPeriod = 1;
if (cpus != super->cpuCount || !this->cpus) {
super->cpuCount = MAXIMUM(cpus, 1);
free(this->cpus);
this->cpus = xCalloc(cpus + 1, sizeof(CPUData));
}
}
@ -225,37 +211,29 @@ ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidMatchList, ui
this->haveSmapsRollup = (access(PROCDIR "/self/smaps_rollup", R_OK) == 0);
// 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);
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", &btime) == 1)
break;
CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
}
fclose(statfile);
if (btime == -1)
CRT_fatalError("No btime in " PROCSTATFILE);
FILE* statfile = fopen(PROCSTATFILE, "r");
if (statfile == NULL)
CRT_fatalError("Cannot open " PROCSTATFILE);
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", &btime) == 1)
break;
CRT_fatalError("Failed to parse btime from " PROCSTATFILE);
}
if (btime == -1)
CRT_fatalError("No btime in " PROCSTATFILE);
rewind(statfile);
// Initialize CPU count
{
int cpus = LinuxProcessList_computeCPUcount();
pl->cpuCount = MAXIMUM(cpus, 1);
this->cpus = xCalloc(cpus + 1, sizeof(CPUData));
LinuxProcessList_updateCPUcount(pl, statfile);
for (int i = 0; i <= cpus; i++) {
this->cpus[i].totalTime = 1;
this->cpus[i].totalPeriod = 1;
}
}
fclose(statfile);
return pl;
}
@ -1498,8 +1476,7 @@ static bool LinuxProcessList_recurseProcTree(LinuxProcessList* this, openat_arg_
proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
pl->totalTasks++;
if (proc->state == 'R')
pl->runningTasks++;
/* runningTasks is set in LinuxProcessList_scanCPUTime() from /proc/stat */
proc->updated = true;
Compat_openatArgClose(procFd);
continue;
@ -1772,26 +1749,28 @@ static inline void LinuxProcessList_scanZfsArcstats(LinuxProcessList* lpl) {
}
}
static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) {
static inline double LinuxProcessList_scanCPUTime(ProcessList* super) {
LinuxProcessList* this = (LinuxProcessList*) super;
FILE* file = fopen(PROCSTATFILE, "r");
if (file == NULL) {
if (!file)
CRT_fatalError("Cannot open " PROCSTATFILE);
}
int cpus = this->super.cpuCount;
assert(cpus > 0);
LinuxProcessList_updateCPUcount(super, file);
rewind(file);
int cpus = super->cpuCount;
for (int i = 0; i <= cpus; i++) {
char buffer[PROC_LINE_LENGTH + 1];
unsigned long long int usertime, nicetime, systemtime, idletime;
unsigned long long int ioWait, irq, softIrq, steal, guest, guestnice;
ioWait = irq = softIrq = steal = guest = guestnice = 0;
unsigned long long int ioWait = 0, irq = 0, softIrq = 0, steal = 0, guest = 0, guestnice = 0;
// Depending on your kernel version,
// 5, 7, 8 or 9 of these fields will be set.
// The rest will remain at zero.
const char* ok = fgets(buffer, PROC_LINE_LENGTH, file);
if (!ok) {
buffer[0] = '\0';
}
const char* ok = fgets(buffer, sizeof(buffer), file);
if (!ok)
break;
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);
@ -1801,8 +1780,8 @@ static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) {
assert(cpuid == i - 1);
}
// Guest time is already accounted in usertime
usertime = usertime - guest;
nicetime = nicetime - guestnice;
usertime -= guest;
nicetime -= guestnice;
// Fields existing on kernels >= 2.6
// (and RHEL's patched kernel 2.4...)
unsigned long long int idlealltime = idletime + ioWait;
@ -1842,7 +1821,17 @@ static inline double LinuxProcessList_scanCPUTime(LinuxProcessList* this) {
}
double period = (double)this->cpus[0].totalPeriod / cpus;
char buffer[PROC_LINE_LENGTH + 1];
while (fgets(buffer, sizeof(buffer), file)) {
if (String_startsWith(buffer, "procs_running")) {
super->runningTasks = strtoul(buffer + strlen("procs_running"), NULL, 10);
break;
}
}
fclose(file);
return period;
}
@ -1978,10 +1967,9 @@ void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
LinuxProcessList_scanMemoryInfo(super);
LinuxProcessList_scanHugePages(this);
LinuxProcessList_scanZfsArcstats(this);
LinuxProcessList_updateCPUcount(this);
LinuxProcessList_scanZramInfo(this);
double period = LinuxProcessList_scanCPUTime(this);
double period = LinuxProcessList_scanCPUTime(super);
if (settings->showCPUFrequency) {
LinuxProcessList_scanCPUFrequency(this);