☠☠ backed out by ea03df7a1554 ☠ ☠ | |
author | David Teller <dteller@mozilla.com> |
Mon, 03 Aug 2020 14:54:25 +0000 | |
changeset 543133 | 1b1fa5dbd9bdf7d3579af6bcdb3a727e6ec9a777 |
parent 543132 | 0f097362abf86343710620aa61113434c294dc0e |
child 543134 | 01b6951d0aaee040d5c51ef737046ea88cd9f7bb |
push id | 123248 |
push user | dteller@mozilla.com |
push date | Mon, 03 Aug 2020 15:05:58 +0000 |
treeherder | autoland@01b6951d0aae [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | florian |
bugs | 1647695 |
milestone | 81.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/toolkit/components/aboutprocesses/content/aboutProcesses.css +++ b/toolkit/components/aboutprocesses/content/aboutProcesses.css @@ -37,29 +37,42 @@ body { min-width: 40em; background-color: var(--in-content-box-background); } tr { display: table; table-layout: fixed; width: 100%; } + +/* column-pid */ td:nth-child(1) { width: 16%; } /* At least one column needs to have a flexible width, - so no width specified for td:nth-child(2) */ + so no width specified for td:nth-child(2) aka column-name*/ + + +/* column-memory-resident */ td:nth-child(3) { width: 10%; } + +/* column-cpu-user */ td:nth-child(4) { - width: 10%; + width: 10%; } + +/* column-cpu-kernel */ td:nth-child(5) { - width: 10%; + width: 10%; +} +/* column-threads */ +td:nth-child(6) { + width: 2%; } #process-thead > tr { height: inherit; } #process-thead > tr > td { border: none; @@ -163,16 +176,23 @@ td { #process-tbody > tr.process { font-weight: bold; } #process-tbody > tr.thread { font-size-adjust: 0.5; } +/* column-name */ + +/* When the process is reported as frozen, we display an hourglass before its name. */ +.process.hung > :nth-child(2)::before { + content: "⌛️"; +} + /* Show a the separation between process groups. */ #process-tbody > tr.separate-from-next-process-group { border-bottom: dotted 1px var(--in-content-box-border-color); margin-bottom: -1px; }
--- a/toolkit/components/aboutprocesses/content/aboutProcesses.js +++ b/toolkit/components/aboutprocesses/content/aboutProcesses.js @@ -21,16 +21,18 @@ const UPDATE_INTERVAL_MS = 2000; const MS_PER_NS = 1000000; const NS_PER_S = 1000000000; const ONE_GIGA = 1024 * 1024 * 1024; const ONE_MEGA = 1024 * 1024; const ONE_KILO = 1024; +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + /** * Returns a Promise that's resolved after the next turn of the event loop. * * Just returning a resolved Promise would mean that any `then` callbacks * would be called right after the end of the current turn, so `setTimeout` * is used to delay Promise resolution until the next turn. * * In mochi tests, it's possible for this to be called after the @@ -143,16 +145,17 @@ var State = { * Compute the delta between two process snapshots. * * @param {ProcessSnapshot} cur * @param {ProcessSnapshot?} prev */ _getProcessDelta(cur, prev) { let result = { pid: cur.pid, + childID: cur.childID, filename: cur.filename, totalVirtualMemorySize: cur.virtualMemorySize, deltaVirtualMemorySize: null, totalResidentSize: cur.residentSetSize, deltaResidentSize: null, totalCpuUser: cur.cpuUser, slopeCpuUser: null, totalCpuKernel: cur.cpuKernel, @@ -244,41 +247,44 @@ var View = { row.parentNode.insertBefore(this._fragment, row.nextSibling); this._fragment = document.createDocumentFragment(); }, /** * Append a row showing a single process (without its threads). * * @param {ProcessDelta} data The data to display. - * @param {bool} isOpen `true` if we're also displaying the threads of this process, `false` otherwise. * @return {DOMElement} The row displaying the process. */ - appendProcessRow(data, isOpen) { + appendProcessRow(data) { let row = document.createElement("tr"); row.classList.add("process"); + if (data.isHung) { + row.classList.add("hung"); + } + // Column: pid / twisty image { let elt = this._addCell(row, { content: data.pid, classes: ["pid", "root"], }); if (data.threads.length) { let img = document.createElement("span"); img.classList.add("twisty", "process"); - if (isOpen) { + if (data.isOpen) { img.classList.add("open"); } elt.insertBefore(img, elt.firstChild); } } - // Column: type + // Column: name/type { let content = data.origin ? `${data.origin} (${data.type})` : data.type; this._addCell(row, { content, classes: ["type"], }); } @@ -522,24 +528,30 @@ var View = { _setTextAndTooltip(elt, text, tooltip = text) { elt.textContent = text; elt.setAttribute("title", tooltip); }, }; var Control = { _openItems: new Set(), + // The set of all processes reported as "hung" by the process hang monitor. + // + // type: Set<ChildID> + _hungItems: new Set(), _sortColumn: null, _sortAscendent: true, _removeSubtree(row) { while (row.nextSibling && row.nextSibling.classList.contains("thread")) { row.nextSibling.remove(); } }, init() { + this._initHangReports(); + let tbody = document.getElementById("process-tbody"); tbody.addEventListener("click", event => { this._updateLastMouseEvent(); // Handle showing or hiding subitems of a row. let target = event.target; if (target.classList.contains("twisty")) { let row = target.parentNode.parentNode; @@ -610,16 +622,39 @@ var Control = { await this._updateDisplay(true); }); }, _lastMouseEvent: 0, _updateLastMouseEvent() { this._lastMouseEvent = Date.now(); }, + _initHangReports() { + const PROCESS_HANG_REPORT_NOTIFICATION = "process-hang-report"; + + // Receiving report of a hung child. + // Let's store if for our next update. + let hangReporter = report => { + report.QueryInterface(Ci.nsIHangReport); + this._hungItems.add(report.childID); + }; + Services.obs.addObserver(hangReporter, PROCESS_HANG_REPORT_NOTIFICATION); + + // Don't forget to unregister the reporter. + window.addEventListener( + "unload", + () => { + Services.obs.removeObserver( + hangReporter, + PROCESS_HANG_REPORT_NOTIFICATION + ); + }, + { once: true } + ); + }, async update() { await State.update(); if (document.hidden) { return; } await wait(0); @@ -639,23 +674,36 @@ var Control = { let counters = State.getCounters(); // Reset the selectedRow field and the _openItems set each time we redraw // to avoid keeping forever references to dead processes. let openItems = this._openItems; this._openItems = new Set(); + // Similarly, we reset `_hungItems`, based on the assumption that the process hang + // monitor will inform us again before the next update. Since the process hang monitor + // pings its clients about once per second and we update about once per 2 seconds + // (or more if the mouse moves), we should be ok. + let hungItems = this._hungItems; + this._hungItems = new Set(); + counters = this._sortProcesses(counters); let previousRow = null; let previousProcess = null; for (let process of counters) { let isOpen = openItems.has(process.pid); + process.isOpen = isOpen; + + let isHung = process.childID && hungItems.has(process.childID); + process.isHung = isHung; + let processRow = View.appendProcessRow(process, isOpen); processRow.process = process; + let latestRow = processRow; if (isOpen) { this._openItems.add(process.pid); latestRow = this._showChildren(processRow); } if ( this._sortColumn == null && previousProcess &&
--- a/toolkit/components/aboutprocesses/tests/browser/browser_aboutprocesses.js +++ b/toolkit/components/aboutprocesses/tests/browser/browser_aboutprocesses.js @@ -1,14 +1,14 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -let { AppConstants } = ChromeUtils.import( +const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); // A bunch of assumptions we make about the behavior of the parent process, // and which we use as sanity checks. If Firefox evolves, we will need to // update these values. const HARDCODED_ASSUMPTIONS_PROCESS = { minimalNumberOfThreads: 10, @@ -228,26 +228,58 @@ function testMemory(string, total, delta Assert.ok( isCloseEnough(Math.abs(computedDelta), Math.abs(delta)), `The displayed approximation of the delta amount of memory is reasonable: ${computedDelta} vs ${delta}` ); } add_task(async function testAboutProcesses() { info("Setting up about:processes"); + + // The tab we're testing. let tabAboutProcesses = (gBrowser.selectedTab = BrowserTestUtils.addTab( gBrowser, "about:processes" )); + // Another tab that we'll pretend is hung. + let tabHung = BrowserTestUtils.addTab(gBrowser, "https://example.org"); + await BrowserTestUtils.browserLoaded(tabAboutProcesses.linkedBrowser); + await BrowserTestUtils.browserLoaded(tabHung.linkedBrowser); + + let hungChildID = tabHung.linkedBrowser.frameLoader.childID; let doc = tabAboutProcesses.linkedBrowser.contentDocument; let tbody = doc.getElementById("process-tbody"); + // Keep informing about:processes that `tabHung` is hung. + // Note: this is a background task, do not `await` it. + let isProcessHangDetected = false; + let fakeProcessHangMonitor = async function() { + for (let i = 0; i < 100; ++i) { + if (isProcessHangDetected || !tabHung.linkedBrowser) { + // Let's stop spamming as soon as we can. + return; + } + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 300)); + Services.obs.notifyObservers( + { + childID: hungChildID, + hangType: Ci.nsIHangReport.PLUGIN_HANG, + pluginName: "Fake plug-in", + QueryInterface: ChromeUtils.generateQI(["nsIHangReport"]), + }, + "process-hang-report" + ); + } + }; + fakeProcessHangMonitor(); + // Wait until the table has first been populated. await TestUtils.waitForCondition(() => tbody.childElementCount); // And wait for another update using a mutation observer, to give our newly created test tab some time // to burn some CPU. await new Promise(resolve => { let observer = new doc.ownerGlobal.MutationObserver(() => { observer.disconnect(); @@ -344,13 +376,34 @@ add_task(async function testAboutProcess info("Sanity checks: CPU (kernel)"); testCpu( cpuKernelContent, threadRow.thread.totalCpuKernel, threadRow.thread.slopeCpuKernel, HARDCODED_ASSUMPTIONS_THREAD ); } - Assert.equal(numberOfThreads, numberOfThreadsFound); + info("Ensuring that the hung process is marked as hung"); + let isOneNonHungProcessDetected = false; + for (let row of tbody.getElementsByClassName("process")) { + if (row.classList.contains("hung")) { + if (row.process.childID == hungChildID) { + isProcessHangDetected = true; + } + } else { + isOneNonHungProcessDetected = true; + } + if (isProcessHangDetected && isOneNonHungProcessDetected) { + break; + } + } + + Assert.ok(isProcessHangDetected, "We have found our hung process"); + Assert.ok( + isOneNonHungProcessDetected, + "We have found at least one non-hung process" + ); + BrowserTestUtils.removeTab(tabAboutProcesses); + BrowserTestUtils.removeTab(tabHung); });