summaryrefslogtreecommitdiffstats
path: root/js/perf
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 15:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 15:07:22 +0000
commitf9d480cfe50ca1d7a0f0b5a2b8bb9932962bfbe7 (patch)
treece9e8db2d4e8799780fa72ae8f1953039373e2ee /js/perf
parentInitial commit. (diff)
downloadgnome-shell-upstream.tar.xz
gnome-shell-upstream.zip
Adding upstream version 3.38.6.upstream/3.38.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--js/perf/basic.js146
-rw-r--r--js/perf/core.js242
-rw-r--r--js/perf/hwtest.js309
3 files changed, 697 insertions, 0 deletions
diff --git a/js/perf/basic.js b/js/perf/basic.js
new file mode 100644
index 0000000..718532f
--- /dev/null
+++ b/js/perf/basic.js
@@ -0,0 +1,146 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported run, finish, script_topBarNavDone, script_notificationShowDone,
+ script_notificationCloseDone, script_overviewShowDone,
+ script_applicationsShowStart, script_applicationsShowDone, METRICS,
+*/
+/* eslint camelcase: ["error", { properties: "never", allow: ["^script_"] }] */
+
+const { St } = imports.gi;
+
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+const Scripting = imports.ui.scripting;
+
+// This script tests the most important (basic) functionality of the shell.
+
+var METRICS = {};
+
+async function run() {
+ /* eslint-disable no-await-in-loop */
+ Scripting.defineScriptEvent('topBarNavStart', 'Starting to navigate the top bar');
+ Scripting.defineScriptEvent('topBarNavDone', 'Done navigating the top bar');
+ Scripting.defineScriptEvent('notificationShowStart', 'Showing a notification');
+ Scripting.defineScriptEvent('notificationShowDone', 'Done showing a notification');
+ Scripting.defineScriptEvent('notificationCloseStart', 'Closing a notification');
+ Scripting.defineScriptEvent('notificationCloseDone', 'Done closing a notification');
+ Scripting.defineScriptEvent('overviewShowStart', 'Starting to show the overview');
+ Scripting.defineScriptEvent('overviewShowDone', 'Overview finished showing');
+ Scripting.defineScriptEvent('applicationsShowStart', 'Starting to switch to applications view');
+ Scripting.defineScriptEvent('applicationsShowDone', 'Done switching to applications view');
+
+ Main.overview.connect('shown',
+ () => Scripting.scriptEvent('overviewShowDone'));
+
+ await Scripting.sleep(1000);
+
+ // navigate through top bar
+ Scripting.scriptEvent('topBarNavStart');
+ Main.panel.statusArea.aggregateMenu.menu.open();
+ await Scripting.sleep(400);
+
+ const { menuManager } = Main.panel;
+ while (menuManager.activeMenu &&
+ Main.panel.navigate_focus(menuManager.activeMenu.sourceActor,
+ St.DirectionType.TAB_BACKWARD, false))
+ await Scripting.sleep(400);
+ Scripting.scriptEvent('topBarNavDone');
+
+ await Scripting.sleep(1000);
+
+ // notification
+ const source = new MessageTray.SystemNotificationSource();
+ Main.messageTray.add(source);
+
+ Scripting.scriptEvent('notificationShowStart');
+ source.connect('notification-show',
+ () => Scripting.scriptEvent('notificationShowDone'));
+
+ const notification = new MessageTray.Notification(source,
+ 'A test notification');
+ source.showNotification(notification);
+ await Scripting.sleep(400);
+
+ Main.panel.statusArea.dateMenu.menu.open();
+ await Scripting.sleep(400);
+
+ Scripting.scriptEvent('notificationCloseStart');
+ notification.connect('destroy',
+ () => Scripting.scriptEvent('notificationCloseDone'));
+
+ notification.destroy();
+ await Scripting.sleep(400);
+
+ Main.panel.statusArea.dateMenu.menu.close();
+ await Scripting.waitLeisure();
+
+ await Scripting.sleep(1000);
+
+ // overview (window picker)
+ Scripting.scriptEvent('overviewShowStart');
+ Main.overview.show();
+ await Scripting.waitLeisure();
+ Main.overview.hide();
+ await Scripting.waitLeisure();
+
+ await Scripting.sleep(1000);
+
+ // overview (app picker)
+ Main.overview.show();
+ await Scripting.waitLeisure();
+
+ 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();
+
+ Main.overview.hide();
+ await Scripting.waitLeisure();
+ /* eslint-enable no-await-in-loop */
+}
+
+let topBarNav = false;
+let notificationShown = false;
+let notificationClosed = false;
+let windowPickerShown = false;
+let appPickerShown = false;
+
+function script_topBarNavDone() {
+ topBarNav = true;
+}
+
+function script_notificationShowDone() {
+ notificationShown = true;
+}
+
+function script_notificationCloseDone() {
+ notificationClosed = true;
+}
+
+function script_overviewShowDone() {
+ windowPickerShown = true;
+}
+
+function script_applicationsShowDone() {
+ appPickerShown = true;
+}
+
+function finish() {
+ if (!topBarNav)
+ throw new Error('Failed to navigate top bar');
+
+ if (!notificationShown)
+ throw new Error('Failed to show notification');
+
+ if (!notificationClosed)
+ throw new Error('Failed to close notification');
+
+ if (!windowPickerShown)
+ throw new Error('Failed to show window picker');
+
+ if (!appPickerShown)
+ throw new Error('Failed to show app picker');
+}
diff --git a/js/perf/core.js b/js/perf/core.js
new file mode 100644
index 0000000..f3f496b
--- /dev/null
+++ b/js/perf/core.js
@@ -0,0 +1,242 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported run, script_overviewShowStart, script_overviewShowDone,
+ script_applicationsShowStart, script_applicationsShowDone,
+ script_afterShowHide, malloc_usedSize, glx_swapComplete,
+ clutter_stagePaintDone */
+/* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^malloc", "^glx", "^clutter"] }] */
+
+const System = imports.system;
+
+const Main = imports.ui.main;
+const Scripting = imports.ui.scripting;
+
+// 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.
+
+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" },
+};
+
+let 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' },
+];
+
+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;
+
+function script_overviewShowStart(time) {
+ showingOverview = true;
+ finishedShowingOverview = false;
+ overviewShowStart = time;
+ overviewFrames = 0;
+}
+
+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;
+}
+
+function script_applicationsShowStart(time) {
+ applicationsShowStart = time;
+}
+
+function script_applicationsShowDone(time) {
+ applicationsShowCount++;
+ if (applicationsShowCount == 1)
+ METRICS.applicationsShowTimeFirst.value = time - applicationsShowStart;
+ else
+ METRICS.applicationsShowTimeSubsequent.value = time - applicationsShowStart;
+}
+
+function script_afterShowHide(_time) {
+ if (overviewShowCount == 1)
+ METRICS.usedAfterOverview.value = mallocUsedSize;
+ else
+ METRICS.leakedAfterOverview.value = mallocUsedSize - METRICS.usedAfterOverview.value;
+}
+
+function malloc_usedSize(time, bytes) {
+ mallocUsedSize = bytes;
+}
+
+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;
+ }
+ }
+}
+
+function glx_swapComplete(time, swapTime) {
+ haveSwapComplete = true;
+
+ _frameDone(swapTime);
+}
+
+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);
+}
diff --git a/js/perf/hwtest.js b/js/perf/hwtest.js
new file mode 100644
index 0000000..0f396ac
--- /dev/null
+++ b/js/perf/hwtest.js
@@ -0,0 +1,309 @@
+/* exported run, script_desktopShown, script_overviewShowStart,
+ script_overviewShowDone, script_applicationsShowStart,
+ script_applicationsShowDone, script_mainViewDrawStart,
+ script_mainViewDrawDone, script_overviewDrawStart,
+ script_overviewDrawDone, script_redrawTestStart,
+ script_redrawTestDone, script_collectTimings,
+ script_geditLaunch, script_geditFirstFrame,
+ clutter_stagePaintStart, clutter_paintCompletedTimestamp */
+/* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^clutter"] }] */
+const { Clutter, Gio, Shell } = imports.gi;
+const Main = imports.ui.main;
+const Scripting = imports.ui.scripting;
+
+var METRICS = {
+ timeToDesktop:
+ { description: "Time from starting graphical.target to desktop showing",
+ units: "us" },
+
+ overviewShowTime:
+ { description: "Time to switch to overview view, first time",
+ units: "us" },
+
+ applicationsShowTime:
+ { description: "Time to switch to applications view, first time",
+ units: "us" },
+
+ mainViewRedrawTime:
+ { description: "Time to redraw the main view, full screen",
+ units: "us" },
+
+ overviewRedrawTime:
+ { description: "Time to redraw the overview, full screen, 5 windows",
+ units: "us" },
+
+ applicationRedrawTime:
+ { description: "Time to redraw frame with a maximized application update",
+ units: "us" },
+
+ geditStartTime:
+ { description: "Time from gedit launch to window drawn",
+ units: "us" },
+};
+
+function waitAndDraw(milliseconds) {
+ let cb;
+
+ let timeline = new Clutter.Timeline({ duration: milliseconds });
+ timeline.start();
+
+ timeline.connect('new-frame', (_timeline, _frame) => {
+ global.stage.queue_redraw();
+ });
+
+ timeline.connect('completed', () => {
+ timeline.stop();
+ if (cb)
+ cb();
+ });
+
+ return callback => (cb = callback);
+}
+
+function waitSignal(object, signal) {
+ let cb;
+
+ let id = object.connect(signal, () => {
+ object.disconnect(id);
+ if (cb)
+ cb();
+ });
+
+ return callback => (cb = callback);
+}
+
+function extractBootTimestamp() {
+ let sp = Gio.Subprocess.new(['journalctl', '-b',
+ 'MESSAGE_ID=7d4958e842da4a758f6c1cdc7b36dcc5',
+ 'UNIT=graphical.target',
+ '-o',
+ 'json'],
+ Gio.SubprocessFlags.STDOUT_PIPE);
+ let result = null;
+
+ let datastream = Gio.DataInputStream.new(sp.get_stdout_pipe());
+ while (true) { // eslint-disable-line no-constant-condition
+ let [line, length_] = datastream.read_line_utf8(null);
+ if (line === null)
+ break;
+
+ let fields = JSON.parse(line);
+ result = Number(fields['__MONOTONIC_TIMESTAMP']);
+ }
+ datastream.close(null);
+ return result;
+}
+
+async function run() {
+ /* eslint-disable no-await-in-loop */
+ Scripting.defineScriptEvent("desktopShown", "Finished initial animation");
+ Scripting.defineScriptEvent("overviewShowStart", "Starting to show the overview");
+ Scripting.defineScriptEvent("overviewShowDone", "Overview finished showing");
+ Scripting.defineScriptEvent("applicationsShowStart", "Starting to switch to applications view");
+ Scripting.defineScriptEvent("applicationsShowDone", "Done switching to applications view");
+ Scripting.defineScriptEvent("mainViewDrawStart", "Drawing main view");
+ Scripting.defineScriptEvent("mainViewDrawDone", "Ending timing main view drawing");
+ Scripting.defineScriptEvent("overviewDrawStart", "Drawing overview");
+ Scripting.defineScriptEvent("overviewDrawDone", "Ending timing overview drawing");
+ Scripting.defineScriptEvent("redrawTestStart", "Drawing application window");
+ Scripting.defineScriptEvent("redrawTestDone", "Ending timing application window drawing");
+ Scripting.defineScriptEvent("collectTimings", "Accumulate frame timings from redraw tests");
+ Scripting.defineScriptEvent("geditLaunch", "gedit application launch");
+ Scripting.defineScriptEvent("geditFirstFrame", "first frame of gedit window drawn");
+
+ await Scripting.waitLeisure();
+ Scripting.scriptEvent('desktopShown');
+
+ let interfaceSettings = new Gio.Settings({
+ schema_id: 'org.gnome.desktop.interface',
+ });
+ interfaceSettings.set_boolean('enable-animations', false);
+
+ Scripting.scriptEvent('overviewShowStart');
+ Main.overview.show();
+ await Scripting.waitLeisure();
+ Scripting.scriptEvent('overviewShowDone');
+
+ await Scripting.sleep(1000);
+
+ Scripting.scriptEvent('applicationsShowStart');
+ // eslint-disable-next-line require-atomic-updates
+ Main.overview.dash.showAppsButton.checked = true;
+
+ await Scripting.waitLeisure();
+ Scripting.scriptEvent('applicationsShowDone');
+
+ await Scripting.sleep(1000);
+
+ Main.overview.hide();
+ await Scripting.waitLeisure();
+
+ // --------------------- //
+ // Tests of redraw speed //
+ // --------------------- //
+
+ global.frame_timestamps = true;
+ global.frame_finish_timestamp = true;
+
+ for (let k = 0; k < 5; k++)
+ await Scripting.createTestWindow({ maximized: true });
+ await Scripting.waitTestWindows();
+
+ await Scripting.sleep(1000);
+
+ Scripting.scriptEvent('mainViewDrawStart');
+ await waitAndDraw(1000);
+ Scripting.scriptEvent('mainViewDrawDone');
+
+ Main.overview.show();
+ Scripting.waitLeisure();
+
+ await Scripting.sleep(1500);
+
+ Scripting.scriptEvent('overviewDrawStart');
+ await waitAndDraw(1000);
+ Scripting.scriptEvent('overviewDrawDone');
+
+ await Scripting.destroyTestWindows();
+ Main.overview.hide();
+
+ await Scripting.createTestWindow({ maximized: true,
+ redraws: true });
+ await Scripting.waitTestWindows();
+
+ await Scripting.sleep(1000);
+
+ Scripting.scriptEvent('redrawTestStart');
+ await Scripting.sleep(1000);
+ Scripting.scriptEvent('redrawTestDone');
+
+ await Scripting.sleep(1000);
+ Scripting.scriptEvent('collectTimings');
+
+ await Scripting.destroyTestWindows();
+
+ global.frame_timestamps = false;
+ global.frame_finish_timestamp = false;
+
+ await Scripting.sleep(1000);
+
+ let appSys = Shell.AppSystem.get_default();
+ let app = appSys.lookup_app('org.gnome.gedit.desktop');
+
+ Scripting.scriptEvent('geditLaunch');
+ app.activate();
+
+ let windows = app.get_windows();
+ if (windows.length > 0)
+ throw new Error('gedit was already running');
+
+ while (windows.length == 0) {
+ await waitSignal(global.display, 'window-created');
+ windows = app.get_windows();
+ }
+
+ let actor = windows[0].get_compositor_private();
+ await waitSignal(actor, 'first-frame');
+ Scripting.scriptEvent('geditFirstFrame');
+
+ await Scripting.sleep(1000);
+
+ windows[0].delete(global.get_current_time());
+
+ await Scripting.sleep(1000);
+
+ interfaceSettings.set_boolean('enable-animations', true);
+ /* eslint-enable no-await-in-loop */
+}
+
+let overviewShowStart;
+let applicationsShowStart;
+let stagePaintStart;
+let redrawTiming;
+let redrawTimes = {};
+let geditLaunchTime;
+
+function script_desktopShown(time) {
+ let bootTimestamp = extractBootTimestamp();
+ METRICS.timeToDesktop.value = time - bootTimestamp;
+}
+
+function script_overviewShowStart(time) {
+ overviewShowStart = time;
+}
+
+function script_overviewShowDone(time) {
+ METRICS.overviewShowTime.value = time - overviewShowStart;
+}
+
+function script_applicationsShowStart(time) {
+ applicationsShowStart = time;
+}
+
+function script_applicationsShowDone(time) {
+ METRICS.applicationsShowTime.value = time - applicationsShowStart;
+}
+
+function script_mainViewDrawStart(_time) {
+ redrawTiming = 'mainView';
+}
+
+function script_mainViewDrawDone(_time) {
+ redrawTiming = null;
+}
+
+function script_overviewDrawStart(_time) {
+ redrawTiming = 'overview';
+}
+
+function script_overviewDrawDone(_time) {
+ redrawTiming = null;
+}
+
+function script_redrawTestStart(_time) {
+ redrawTiming = 'application';
+}
+
+function script_redrawTestDone(_time) {
+ redrawTiming = null;
+}
+
+function script_collectTimings(_time) {
+ for (let timing in redrawTimes) {
+ let times = redrawTimes[timing];
+ times.sort((a, b) => a - b);
+
+ let len = times.length;
+ let median;
+
+ if (len == 0)
+ median = -1;
+ else if (len % 2 == 1)
+ median = times[(len - 1) / 2];
+ else
+ median = Math.round((times[len / 2 - 1] + times[len / 2]) / 2);
+
+ METRICS[`${timing}RedrawTime`].value = median;
+ }
+}
+
+function script_geditLaunch(time) {
+ geditLaunchTime = time;
+}
+
+function script_geditFirstFrame(time) {
+ METRICS.geditStartTime.value = time - geditLaunchTime;
+}
+
+function clutter_stagePaintStart(time) {
+ stagePaintStart = time;
+}
+
+function clutter_paintCompletedTimestamp(time) {
+ if (redrawTiming != null && stagePaintStart != null) {
+ if (!(redrawTiming in redrawTimes))
+ redrawTimes[redrawTiming] = [];
+ redrawTimes[redrawTiming].push(time - stagePaintStart);
+ }
+ stagePaintStart = null;
+}