// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^malloc", "^glx", "^clutter"] }] */ import * as System from 'system'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Scripting from 'resource:///org/gnome/shell/ui/scripting.js'; // This performance script measure the most important (core) performance // metrics for the shell. By looking at the output metrics of this script // someone should be able to get an idea of how well the shell is performing // on a particular system. export var METRICS = { overviewLatencyFirst: { description: 'Time to first frame after triggering overview, first time', units: 'us', }, overviewFpsFirst: { description: 'Frame rate when going to the overview, first time', units: 'frames / s', }, overviewLatencySubsequent: { description: 'Time to first frame after triggering overview, second time', units: 'us', }, overviewFpsSubsequent: { description: 'Frames rate when going to the overview, second time', units: 'frames / s', }, overviewFps5Windows: { description: 'Frames rate when going to the overview, 5 windows open', units: 'frames / s', }, overviewFps10Windows: { description: 'Frames rate when going to the overview, 10 windows open', units: 'frames / s', }, overviewFps5Maximized: { description: 'Frames rate when going to the overview, 5 maximized windows open', units: 'frames / s', }, overviewFps10Maximized: { description: 'Frames rate when going to the overview, 10 maximized windows open', units: 'frames / s', }, overviewFps5Alpha: { description: 'Frames rate when going to the overview, 5 alpha-transparent windows open', units: 'frames / s', }, overviewFps10Alpha: { description: 'Frames rate when going to the overview, 10 alpha-transparent windows open', units: 'frames / s', }, usedAfterOverview: { description: "Malloc'ed bytes after the overview is shown once", units: 'B', }, leakedAfterOverview: { description: "Additional malloc'ed bytes the second time the overview is shown", units: 'B', }, applicationsShowTimeFirst: { description: 'Time to switch to applications view, first time', units: 'us', }, applicationsShowTimeSubsequent: { description: 'Time to switch to applications view, second time', units: 'us', }, }; const WINDOW_CONFIGS = [{ width: 640, height: 480, alpha: false, maximized: false, count: 1, metric: 'overviewFpsSubsequent', }, { width: 640, height: 480, alpha: false, maximized: false, count: 5, metric: 'overviewFps5Windows', }, { width: 640, height: 480, alpha: false, maximized: false, count: 10, metric: 'overviewFps10Windows', }, { width: 640, height: 480, alpha: false, maximized: true, count: 5, metric: 'overviewFps5Maximized', }, { width: 640, height: 480, alpha: false, maximized: true, count: 10, metric: 'overviewFps10Maximized', }, { width: 640, height: 480, alpha: true, maximized: false, count: 5, metric: 'overviewFps5Alpha', }, { width: 640, height: 480, alpha: true, maximized: false, count: 10, metric: 'overviewFps10Alpha', }]; /** @returns {void} */ export async function run() { /* eslint-disable no-await-in-loop */ Scripting.defineScriptEvent('overviewShowStart', 'Starting to show the overview'); Scripting.defineScriptEvent('overviewShowDone', 'Overview finished showing'); Scripting.defineScriptEvent('afterShowHide', 'After a show/hide cycle for the overview'); Scripting.defineScriptEvent('applicationsShowStart', 'Starting to switch to applications view'); Scripting.defineScriptEvent('applicationsShowDone', 'Done switching to applications view'); // Enable recording of timestamps for different points in the frame cycle global.frame_timestamps = true; Main.overview.connect('shown', () => { Scripting.scriptEvent('overviewShowDone'); }); await Scripting.sleep(1000); for (let i = 0; i < 2 * WINDOW_CONFIGS.length; i++) { // We go to the overview twice for each configuration; the first time // to calculate the mipmaps for the windows, the second time to get // a clean set of numbers. if ((i % 2) === 0) { let config = WINDOW_CONFIGS[i / 2]; await Scripting.destroyTestWindows(); for (let k = 0; k < config.count; k++) { await Scripting.createTestWindow({ width: config.width, height: config.height, alpha: config.alpha, maximized: config.maximized, }); } await Scripting.waitTestWindows(); await Scripting.sleep(1000); await Scripting.waitLeisure(); } Scripting.scriptEvent('overviewShowStart'); Main.overview.show(); await Scripting.waitLeisure(); Main.overview.hide(); await Scripting.waitLeisure(); System.gc(); await Scripting.sleep(1000); Scripting.collectStatistics(); Scripting.scriptEvent('afterShowHide'); } await Scripting.destroyTestWindows(); await Scripting.sleep(1000); Main.overview.show(); await Scripting.waitLeisure(); for (let i = 0; i < 2; i++) { Scripting.scriptEvent('applicationsShowStart'); // eslint-disable-next-line require-atomic-updates Main.overview.dash.showAppsButton.checked = true; await Scripting.waitLeisure(); Scripting.scriptEvent('applicationsShowDone'); // eslint-disable-next-line require-atomic-updates Main.overview.dash.showAppsButton.checked = false; await Scripting.waitLeisure(); } /* eslint-enable no-await-in-loop */ } let showingOverview = false; let finishedShowingOverview = false; let overviewShowStart; let overviewFrames; let overviewLatency; let mallocUsedSize = 0; let overviewShowCount = 0; let haveSwapComplete = false; let applicationsShowStart; let applicationsShowCount = 0; /** * @param {number} time - event timestamp * @returns {void} */ export function script_overviewShowStart(time) { showingOverview = true; finishedShowingOverview = false; overviewShowStart = time; overviewFrames = 0; } /** * @param {number} _time - event timestamp * @returns {void} */ export function script_overviewShowDone(_time) { // We've set up the state at the end of the zoom out, but we // need to wait for one more frame to paint before we count // ourselves as done. finishedShowingOverview = true; } /** * @param {number} time - event timestamp * @returns {void} */ export function script_applicationsShowStart(time) { applicationsShowStart = time; } /** @returns {void} */ /** * @param {number} time - event timestamp * @returns {void} */ export function script_applicationsShowDone(time) { applicationsShowCount++; if (applicationsShowCount === 1) METRICS.applicationsShowTimeFirst.value = time - applicationsShowStart; else METRICS.applicationsShowTimeSubsequent.value = time - applicationsShowStart; } /** @returns {void} */ /** * @param {number} _time - event timestamp * @returns {void} */ export function script_afterShowHide(_time) { if (overviewShowCount === 1) METRICS.usedAfterOverview.value = mallocUsedSize; else METRICS.leakedAfterOverview.value = mallocUsedSize - METRICS.usedAfterOverview.value; } /** * @param {number} time - event timestamp * @param {number} bytes - event data * @returns {void} */ export function malloc_usedSize(time, bytes) { mallocUsedSize = bytes; } /** * @param {number} time - event timestamp * @returns {void} */ function _frameDone(time) { if (showingOverview) { if (overviewFrames === 0) overviewLatency = time - overviewShowStart; overviewFrames++; } if (finishedShowingOverview) { showingOverview = false; finishedShowingOverview = false; overviewShowCount++; let dt = (time - (overviewShowStart + overviewLatency)) / 1000000; // If we see a start frame and an end frame, that would // be 1 frame for a FPS computation, hence the '- 1' let fps = (overviewFrames - 1) / dt; if (overviewShowCount === 1) { METRICS.overviewLatencyFirst.value = overviewLatency; METRICS.overviewFpsFirst.value = fps; } else if (overviewShowCount === 2) { METRICS.overviewLatencySubsequent.value = overviewLatency; } // Other than overviewFpsFirst, we collect FPS metrics the second // we show each window configuration. overviewShowCount is 1,2,3... if (overviewShowCount % 2 === 0) { let config = WINDOW_CONFIGS[(overviewShowCount / 2) - 1]; METRICS[config.metric].value = fps; } } } /** * @param {number} time - event timestamp * @param {number} swapTime - event data * @returns {void} */ export function glx_swapComplete(time, swapTime) { haveSwapComplete = true; _frameDone(swapTime); } /** * @param {number} time - event timestamp * @returns {void} */ export function clutter_stagePaintDone(time) { // If we aren't receiving GLXBufferSwapComplete events, then we approximate // the time the user sees a frame with the time we finished doing drawing // commands for the frame. This doesn't take into account the time for // the GPU to finish painting, and the time for waiting for the buffer // swap, but if this are uniform - every frame takes the same time to draw - // then it won't upset our FPS calculation, though the latency value // will be slightly too low. if (!haveSwapComplete) _frameDone(time); }