Bug 1734597 - Make about:processes fallback to cycle count to decide if a thread or process is active to workaround low CPU timing precision on Windows, r=dthayer,fluent-reviewers,flod.

Differential Revision: https://phabricator.services.mozilla.com/D127813
This commit is contained in:
Florian Queze 2021-10-11 12:46:28 +00:00
Родитель 745866d09d
Коммит 19119e94a7
6 изменённых файлов: 90 добавлений и 83 удалений

Просмотреть файл

@ -631,6 +631,7 @@ enum WebIDLProcType {
dictionary ThreadInfoDictionary {
long long tid = 0;
DOMString name = "";
unsigned long long cpuCycleCount = 0;
unsigned long long cpuUser = 0;
unsigned long long cpuKernel = 0;
};
@ -684,6 +685,11 @@ dictionary ChildProcInfoDictionary {
// Time spent by the process in kernel mode, in ns.
unsigned long long cpuKernel = 0;
// Total CPU cycles used by this process.
// On Windows where the resolution of CPU timings is 16ms, this can
// be used to determine if a process is idle or slightly active.
unsigned long long cpuCycleCount = 0;
// Thread information for this process.
sequence<ThreadInfoDictionary> threads = [];
@ -727,6 +733,11 @@ dictionary ParentProcInfoDictionary {
// Time spent by the process in kernel mode, in ns.
unsigned long long cpuKernel = 0;
// Total CPU cycles used by this process.
// On Windows where the resolution of CPU timings is 16ms, this can
// be used to determine if a process is idle or slightly active.
unsigned long long cpuCycleCount = 0;
// Thread information for this process.
sequence<ThreadInfoDictionary> threads = [];

Просмотреть файл

@ -34,6 +34,9 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
ContextualIdentityService:
@ -219,15 +222,10 @@ var State = {
let result = {
tid: cur.tid,
name: cur.name || `(${cur.tid})`,
// Total amount of CPU used, in ns (user).
totalCpuUser: cur.cpuUser,
slopeCpuUser: null,
// Total amount of CPU used, in ns (kernel).
totalCpuKernel: cur.cpuKernel,
slopeCpuKernel: null,
// Total amount of CPU used, in ns (user + kernel).
totalCpu: cur.cpuUser + cur.cpuKernel,
slopeCpu: null,
active: null,
};
if (!prev) {
return result;
@ -235,9 +233,9 @@ var State = {
if (prev.tid != cur.tid) {
throw new Error("Assertion failed: A thread cannot change tid.");
}
result.slopeCpuUser = (cur.cpuUser - prev.cpuUser) / deltaT;
result.slopeCpuKernel = (cur.cpuKernel - prev.cpuKernel) / deltaT;
result.slopeCpu = result.slopeCpuKernel + result.slopeCpuUser;
result.slopeCpu =
(cur.cpuUser + cur.cpuKernel - prev.cpuUser - prev.cpuKernel) / deltaT;
result.active = !!result.slopeCpu || cur.cpuCycleCount > prev.cpuCycleCount;
return result;
},
@ -314,12 +312,9 @@ var State = {
filename: cur.filename,
totalRamSize: cur.memory,
deltaRamSize: null,
totalCpuUser: cur.cpuUser,
slopeCpuUser: null,
totalCpuKernel: cur.cpuKernel,
slopeCpuKernel: null,
totalCpu: cur.cpuUser + cur.cpuKernel,
slopeCpu: null,
active: null,
type: cur.type,
origin: cur.origin || "",
threads: null,
@ -366,9 +361,9 @@ var State = {
});
}
result.deltaRamSize = cur.memory - prev.memory;
result.slopeCpuUser = (cur.cpuUser - prev.cpuUser) / deltaT;
result.slopeCpuKernel = (cur.cpuKernel - prev.cpuKernel) / deltaT;
result.slopeCpu = result.slopeCpuUser + result.slopeCpuKernel;
result.slopeCpu =
(cur.cpuUser + cur.cpuKernel - prev.cpuUser - prev.cpuKernel) / deltaT;
result.active = !!result.slopeCpu || cur.cpuCycleCount > prev.cpuCycleCount;
result.threads = threads;
return result;
},
@ -471,6 +466,46 @@ var View = {
return row;
},
displayCpu(data, cpuCell) {
if (data.slopeCpu == null) {
this._fillCell(cpuCell, {
fluentName: "about-processes-cpu-user-and-kernel-not-ready",
classes: ["cpu"],
});
} else {
let { duration, unit } = this._getDuration(data.totalCpu);
if (data.totalCpu == 0 && AppConstants.platform == "win") {
// The minimum non zero CPU time we can get on Windows is 16ms
// so avoid displaying '0ns'.
unit = "ms";
}
let localizedUnit = gLocalizedUnits.duration[unit];
if (data.slopeCpu == 0) {
let fluentName = data.active
? "about-processes-cpu-almost-idle"
: "about-processes-cpu-fully-idle";
this._fillCell(cpuCell, {
fluentName,
fluentArgs: {
total: duration,
unit: localizedUnit,
},
classes: ["cpu"],
});
} else {
this._fillCell(cpuCell, {
fluentName: "about-processes-cpu",
fluentArgs: {
percent: data.slopeCpu,
total: duration,
unit: localizedUnit,
},
classes: ["cpu"],
});
}
}
},
/**
* Display a row showing a single process (without its threads).
*
@ -682,37 +717,9 @@ var View = {
}
}
// Column: CPU: User and Kernel
// Column: CPU
let cpuCell = memoryCell.nextSibling;
if (data.slopeCpu == null) {
this._fillCell(cpuCell, {
fluentName: "about-processes-cpu-user-and-kernel-not-ready",
classes: ["cpu"],
});
} else {
let { duration, unit } = this._getDuration(data.totalCpu);
let localizedUnit = gLocalizedUnits.duration[unit];
if (data.slopeCpu == 0) {
this._fillCell(cpuCell, {
fluentName: "about-processes-cpu-idle",
fluentArgs: {
total: duration,
unit: localizedUnit,
},
classes: ["cpu"],
});
} else {
this._fillCell(cpuCell, {
fluentName: "about-processes-cpu",
fluentArgs: {
percent: data.slopeCpu,
total: duration,
unit: localizedUnit,
},
classes: ["cpu"],
});
}
}
this.displayCpu(data, cpuCell);
// Column: Kill button – but not for all processes.
let killButton = cpuCell.nextSibling;
@ -764,7 +771,7 @@ var View = {
let activeThreads = new Map();
let activeThreadCount = 0;
for (let t of data.threads) {
if (!t.slopeCpu) {
if (!t.active) {
continue;
}
++activeThreadCount;
@ -928,37 +935,8 @@ var View = {
classes: ["name", "double_indent"],
});
// Column: CPU: User and Kernel
let cpuCell = nameCell.nextSibling;
if (data.slopeCpu == null) {
this._fillCell(cpuCell, {
fluentName: "about-processes-cpu-user-and-kernel-not-ready",
classes: ["cpu"],
});
} else {
let { duration, unit } = this._getDuration(data.totalCpu);
let localizedUnit = gLocalizedUnits.duration[unit];
if (data.slopeCpu == 0) {
this._fillCell(cpuCell, {
fluentName: "about-processes-cpu-idle",
fluentArgs: {
total: duration,
unit: localizedUnit,
},
classes: ["cpu"],
});
} else {
this._fillCell(cpuCell, {
fluentName: "about-processes-cpu",
fluentArgs: {
percent: data.slopeCpu,
total: duration,
unit: localizedUnit,
},
classes: ["cpu"],
});
}
}
// Column: CPU
this.displayCpu(data, nameCell.nextSibling);
// Third column (Buttons) is empty, nothing to do.
},
@ -1338,6 +1316,11 @@ var Control = {
// Used by tests to differentiate full updates from l10n updates.
document.dispatchEvent(new CustomEvent("AboutProcessesUpdated"));
},
_compareCpu(a, b) {
return (
b.slopeCpu - a.slopeCpu || b.active - a.active || b.totalCpu - a.totalCpu
);
},
_showThreads(row) {
let process = row.process;
this._sortThreads(process.threads);
@ -1353,7 +1336,7 @@ var Control = {
order = a.name.localeCompare(b.name) || a.tid - b.tid;
break;
case "column-cpu-total":
order = b.slopeCpu - a.slopeCpu;
order = this._compareCpu(a, b);
break;
case "column-memory-resident":
case null:
@ -1379,7 +1362,7 @@ var Control = {
a.pid - b.pid;
break;
case "column-cpu-total":
order = b.slopeCpu - a.slopeCpu;
order = this._compareCpu(a, b);
break;
case "column-memory-resident":
order = b.totalRamSize - a.totalRamSize;

Просмотреть файл

@ -634,7 +634,7 @@ async function testAboutProcessesWithConfig({ showAllFrames, showThreads }) {
number,
"The number of active threads should not exceed the total number of threads"
);
let activeThreads = row.process.threads.filter(t => t.slopeCpu);
let activeThreads = row.process.threads.filter(t => t.active);
Assert.equal(
active,
activeThreads.length,

Просмотреть файл

@ -60,6 +60,7 @@ struct ThreadInfo {
uint64_t cpuUser = 0;
// System time in ns.
uint64_t cpuKernel = 0;
uint64_t cpuCycleCount = 0;
};
// Info on a DOM window.
@ -111,6 +112,7 @@ struct ProcInfo {
uint64_t cpuUser = 0;
// System time in ns.
uint64_t cpuKernel = 0;
uint64_t cpuCycleCount = 0;
// Threads owned by this process.
CopyableTArray<ThreadInfo> threads;
// DOM windows represented by this process.
@ -213,6 +215,7 @@ nsresult CopySysProcInfoToDOM(const ProcInfo& source, T* dest) {
dest->mMemory = source.memory;
dest->mCpuUser = source.cpuUser;
dest->mCpuKernel = source.cpuKernel;
dest->mCpuCycleCount = source.cpuCycleCount;
// Copy thread info.
mozilla::dom::Sequence<mozilla::dom::ThreadInfoDictionary> threads;
@ -224,6 +227,7 @@ nsresult CopySysProcInfoToDOM(const ProcInfo& source, T* dest) {
}
thread->mCpuUser = entry.cpuUser;
thread->mCpuKernel = entry.cpuKernel;
thread->mCpuCycleCount = entry.cpuCycleCount;
thread->mTid = entry.tid;
thread->mName.Assign(entry.name);
}

Просмотреть файл

@ -91,6 +91,8 @@ RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
info.filename.Assign(filename);
info.cpuKernel = ToNanoSeconds(kernelTime);
info.cpuUser = ToNanoSeconds(userTime);
QueryProcessCycleTime(handle.get(), &info.cpuCycleCount);
info.memory = memoryCounters.PrivateUsage;
if (!gathered.put(request.pid, std::move(info))) {
@ -157,6 +159,8 @@ RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
threadInfo->cpuUser = ToNanoSeconds(userTime);
}
QueryThreadCycleTime(hThread.get(), &threadInfo->cpuCycleCount);
// Attempt to get thread name.
// If we fail, continue without this piece of information.
if (getThreadDescription) {

Просмотреть файл

@ -138,9 +138,14 @@ about-processes-cpu = { NUMBER($percent, maximumSignificantDigits: 2, style: "pe
# Special case: data is not available yet.
about-processes-cpu-user-and-kernel-not-ready = (measuring)
# Special case: process or thread is almost idle (using less than 0.1% of a CPU core).
# This case only occurs on Windows where the precision of the CPU times is low.
about-processes-cpu-almost-idle = < 0.1%
.title = Total CPU time: { NUMBER($total, maximumFractionDigits: 0) }{ $unit }
# Special case: process or thread is currently idle.
about-processes-cpu-idle = idle
.title = Total CPU time: { NUMBER($total, maximumFractionDigits: 2) }{ $unit }
about-processes-cpu-fully-idle = idle
.title = Total CPU time: { NUMBER($total, maximumFractionDigits: 0) }{ $unit }
## Displaying Memory (total and delta)
## Variables: