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
1 changed files with 59 additions and 71 deletions

View File

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