DEV: Improve built-in browser performance marks/measurements (#26758)

- Rename `discourse-booted` to 'discourse-init' (because 'booted' makes it sound like boot was finished. When in fact, it was just starting)

- Introduce `discourse-paint`, which is fired after the Ember application has been painted to the screen by the browser. This happens slightly after DOMContentLoaded

- Add a `performance.measure` call to link those two marks, so they're easily visible in performance traces

Also removes an ember boot-order workaround which is no longer required.
This commit is contained in:
David Taylor 2024-05-02 23:07:36 +01:00 committed by GitHub
parent d937f5b098
commit 6bfc81978c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 45 additions and 12 deletions

View File

@ -1,6 +1,8 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { service } from "@ember/service";
import runAfterFramePaint from "discourse/lib/after-frame-paint";
import { isTesting } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import deprecated from "discourse-common/lib/deprecated";
import discourseComputed from "discourse-common/utils/decorators";
@ -123,4 +125,24 @@ export default Controller.extend({
}
}
},
@action
trackDiscoursePainted() {
if (isTesting()) {
return;
}
runAfterFramePaint(() => {
performance.mark("discourse-paint");
try {
performance.measure(
"discourse-init-to-paint",
"discourse-init",
"discourse-paint"
);
} catch (e) {
// eslint-disable-next-line no-console
console.warn("Failed to measure init-to-paint", e);
}
});
},
});

View File

@ -0,0 +1,18 @@
/**
* Runs `callback` shortly after the next browser Frame is produced.
* ref: https://webperf.tips/tip/measuring-paint-time
*/
export default function runAfterFramePaint(callback) {
// Queue a "before Render Steps" callback via requestAnimationFrame.
requestAnimationFrame(() => {
// MessageChannel is one of the highest priority task queues
// which will be executed after the frame has painted.
const messageChannel = new MessageChannel();
// Setup the callback to run in a Task
messageChannel.port1.onmessage = callback;
// Queue the Task on the Task Queue
messageChannel.port2.postMessage(undefined);
});
}

View File

@ -1,7 +1,7 @@
<DStyles />
<DVirtualHeight />
<DiscourseRoot>
<DiscourseRoot {{did-insert this.trackDiscoursePainted}}>
<a href="#main-container" id="skip-link">{{i18n "skip_to_main_content"}}</a>
<DDocument />
<PageLoadingSlider />

View File

@ -3,19 +3,12 @@
throw "Unsupported browser detected";
}
// In Ember 3.28, the `ember` package is responsible for configuring `Helper.helper`,
// so we need to require('ember') before setting up any helpers.
// https://github.com/emberjs/ember.js/blob/744e536d37/packages/ember/index.js#L493-L493
// In modern Ember, the Helper.helper definition has moved to the helper module itself
// https://github.com/emberjs/ember.js/blob/0c5518ea7b/packages/%40ember/-internals/glimmer/lib/helper.ts#L134-L138
require("ember");
let element = document.querySelector(
`meta[name="discourse/config/environment"]`
);
const config = JSON.parse(
decodeURIComponent(element.getAttribute("content"))
);
const event = new CustomEvent("discourse-booted", { detail: config });
const event = new CustomEvent("discourse-init", { detail: config });
document.dispatchEvent(event);
})();

View File

@ -1,5 +1,5 @@
document.addEventListener("discourse-booted", (e) => {
performance.mark("discourse-booted");
document.addEventListener("discourse-init", (e) => {
performance.mark("discourse-init");
const config = e.detail;
const app = require(`${config.modulePrefix}/app`)["default"].create(config);
app.start();

View File

@ -6,7 +6,7 @@ import { setup } from "qunit-dom";
import setupTests from "discourse/tests/setup-tests";
import config from "../config/environment";
document.addEventListener("discourse-booted", () => {
document.addEventListener("discourse-init", () => {
// eslint-disable-next-line no-undef
if (!EmberENV.TESTS_FILE_LOADED) {
throw new Error(