ep_performance_test_hooks 0.0.57 → 0.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -16,20 +16,20 @@ jobs:
|
|
|
16
16
|
steps:
|
|
17
17
|
-
|
|
18
18
|
name: Install libreoffice
|
|
19
|
-
uses: awalsh128/cache-apt-pkgs-action@v1.6.
|
|
19
|
+
uses: awalsh128/cache-apt-pkgs-action@v1.6.1
|
|
20
20
|
with:
|
|
21
21
|
packages: libreoffice libreoffice-pdfimport
|
|
22
22
|
version: 1.0
|
|
23
23
|
-
|
|
24
24
|
name: Install etherpad core
|
|
25
|
-
uses: actions/checkout@
|
|
25
|
+
uses: actions/checkout@v7
|
|
26
26
|
with:
|
|
27
27
|
repository: ether/etherpad-lite
|
|
28
28
|
path: etherpad-lite
|
|
29
29
|
- uses: actions/setup-node@v6
|
|
30
30
|
name: Install Node.js
|
|
31
31
|
with:
|
|
32
|
-
node-version:
|
|
32
|
+
node-version: 25
|
|
33
33
|
- uses: pnpm/action-setup@v6
|
|
34
34
|
name: Install pnpm
|
|
35
35
|
with:
|
|
@@ -48,7 +48,7 @@ jobs:
|
|
|
48
48
|
${{ runner.os }}-pnpm-store-
|
|
49
49
|
-
|
|
50
50
|
name: Checkout plugin repository
|
|
51
|
-
uses: actions/checkout@
|
|
51
|
+
uses: actions/checkout@v7
|
|
52
52
|
with:
|
|
53
53
|
path: plugin
|
|
54
54
|
- name: Remove tests
|
|
@@ -12,14 +12,14 @@ jobs:
|
|
|
12
12
|
steps:
|
|
13
13
|
-
|
|
14
14
|
name: Check out Etherpad core
|
|
15
|
-
uses: actions/checkout@
|
|
15
|
+
uses: actions/checkout@v7
|
|
16
16
|
with:
|
|
17
17
|
repository: ether/etherpad-lite
|
|
18
18
|
path: etherpad-lite
|
|
19
19
|
- uses: actions/setup-node@v6
|
|
20
20
|
name: Install Node.js
|
|
21
21
|
with:
|
|
22
|
-
node-version:
|
|
22
|
+
node-version: 25
|
|
23
23
|
- uses: pnpm/action-setup@v6
|
|
24
24
|
name: Install pnpm
|
|
25
25
|
with:
|
|
@@ -38,7 +38,7 @@ jobs:
|
|
|
38
38
|
${{ runner.os }}-pnpm-store-
|
|
39
39
|
-
|
|
40
40
|
name: Checkout plugin repository
|
|
41
|
-
uses: actions/checkout@
|
|
41
|
+
uses: actions/checkout@v7
|
|
42
42
|
with:
|
|
43
43
|
path: plugin
|
|
44
44
|
-
|
|
@@ -23,12 +23,12 @@ jobs:
|
|
|
23
23
|
# OIDC trusted publishing needs npm >= 11.5.1, which requires
|
|
24
24
|
# Node >= 20.17.0. setup-node's `20` resolves to the latest
|
|
25
25
|
# 20.x, which satisfies that.
|
|
26
|
-
node-version:
|
|
26
|
+
node-version: 25
|
|
27
27
|
registry-url: https://registry.npmjs.org/
|
|
28
28
|
- name: Upgrade npm to >=11.5.1 (required for trusted publishing)
|
|
29
29
|
run: npm install -g npm@latest
|
|
30
30
|
- name: Check out Etherpad core
|
|
31
|
-
uses: actions/checkout@
|
|
31
|
+
uses: actions/checkout@v7
|
|
32
32
|
with:
|
|
33
33
|
repository: ether/etherpad-lite
|
|
34
34
|
- uses: pnpm/action-setup@v6
|
|
@@ -47,7 +47,7 @@ jobs:
|
|
|
47
47
|
restore-keys: |
|
|
48
48
|
${{ runner.os }}-pnpm-store-
|
|
49
49
|
-
|
|
50
|
-
uses: actions/checkout@
|
|
50
|
+
uses: actions/checkout@v7
|
|
51
51
|
with:
|
|
52
52
|
fetch-depth: 0
|
|
53
53
|
-
|
package/README.md
CHANGED
|
@@ -2,11 +2,90 @@
|
|
|
2
2
|
|
|
3
3
|
# Performance Test Hooks for Etherpad
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**See exactly how long your Etherpad takes to become editable — and which assets weigh it down — straight from the browser, with no external APM.**
|
|
6
|
+
|
|
7
|
+
This plugin instruments the client lifecycle: it times every Etherpad boot hook (`documentReady` → `postAceInit`) and records the load time and byte size of every resource the pad, outer frame, and inner editor request. The data is exposed as JSON at `/stats`, ready to graph, alert on, or diff between releases.
|
|
8
|
+
|
|
9
|
+
## Why you'd want it
|
|
10
|
+
|
|
11
|
+
- **Find the slow phase of pad load.** The boot waterfall (`etherpadHooksDuration`) shows the cost of each lifecycle stage, so you know whether time goes to the editor, the toolbar, or plugin init — not just a single opaque "load time".
|
|
12
|
+
- **Catch asset bloat before users do.** Per-resource decoded/encoded/transfer sizes make it obvious when a font, locale bundle, or plugin asset balloons.
|
|
13
|
+
- **Spot regressions across releases.** `/stats` is plain JSON, so you can snapshot it in CI and fail the build when boot time or payload size creeps up.
|
|
14
|
+
- **No third-party APM.** Everything is collected with the browser's own `PerformanceResourceTiming` API and stays on your server.
|
|
15
|
+
|
|
16
|
+
## The data
|
|
17
|
+
|
|
18
|
+
Open any pad (this is what populates the metrics), then `GET /stats`. The plugin's output lives under the `ep_performance_test_hooks` key. Below is a **real response** from an Etherpad running this plugin — the values are exactly as captured; only explanatory `//` comments have been added and the `loadTimes`/`loadSizes` maps truncated (the full response carries every resource across all three frames — 49 + 49 + 46 in this capture):
|
|
19
|
+
|
|
20
|
+
```jsonc
|
|
21
|
+
{
|
|
22
|
+
"ep_performance_test_hooks": {
|
|
23
|
+
// Wall-clock timestamp each lifecycle hook fired
|
|
24
|
+
"etherpadHooks": {
|
|
25
|
+
"documentReady": 1781864859040,
|
|
26
|
+
"aceInitInnerdocbodyHead": 1781864859200,
|
|
27
|
+
"aceInitialized": 1781864859220,
|
|
28
|
+
"postToolbarInit": 1781864859250,
|
|
29
|
+
"postAceInit": 1781864859323
|
|
30
|
+
},
|
|
31
|
+
// The boot waterfall: ms from documentReady to each hook
|
|
32
|
+
"etherpadHooksDuration": {
|
|
33
|
+
"documentReady": 0,
|
|
34
|
+
"aceInitInnerdocbodyHead": 160,
|
|
35
|
+
"aceInitialized": 180,
|
|
36
|
+
"postToolbarInit": 210,
|
|
37
|
+
"postAceInit": 283 // editor interactive 283 ms after documentReady
|
|
38
|
+
},
|
|
39
|
+
// Per-resource timing for the main / outer / inner frames (2 of 49 shown)
|
|
40
|
+
"loadTimes": {
|
|
41
|
+
"main": {
|
|
42
|
+
"/static/css/pad.css": {
|
|
43
|
+
"redirectTime": 0, "domainLookupTime": 0, "tcpTime": 0,
|
|
44
|
+
"secureConnectionTime": "0", "responseTime": 0,
|
|
45
|
+
"fetchUntilResponseEndTime": 0,
|
|
46
|
+
"requestStartUntilResponseEndTime": 0,
|
|
47
|
+
"startUntilResponseEndTime": 0
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
// Per-resource byte sizes (2 of 49 shown)
|
|
52
|
+
"loadSizes": {
|
|
53
|
+
"main": {
|
|
54
|
+
"/static/css/pad.css": { "decodedBodySize": 9279, "encodedBodySize": 9279, "transferSize": 0 },
|
|
55
|
+
"/static/skins/colibris/pad.css": { "decodedBodySize": 2145, "encodedBodySize": 2145, "transferSize": 0 }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## What's measured
|
|
63
|
+
|
|
64
|
+
| Field | What it tells you |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `etherpadHooks` | Wall-clock timestamp each lifecycle hook fired |
|
|
67
|
+
| `etherpadHooksDuration` | Milliseconds from `documentReady` to each hook — the **boot waterfall** |
|
|
68
|
+
| `loadTimes.{main,outer,inner}` | Redirect / DNS / TCP / TLS / response timings per resource, for each of the three pad frames |
|
|
69
|
+
| `loadSizes.{main,outer,inner}` | `decodedBodySize` / `encodedBodySize` / `transferSize` per resource |
|
|
70
|
+
| `performance` | The page's `navigation` PerformanceEntry |
|
|
6
71
|
|
|
7
72
|
## Usage
|
|
8
73
|
|
|
9
|
-
|
|
74
|
+
The plugin only emits data — render or alert on it however you like. For example, fail CI if the editor takes too long to become interactive:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
curl -s http://localhost:9001/stats \
|
|
78
|
+
| jq '.ep_performance_test_hooks.etherpadHooksDuration.postAceInit' # -> 283
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
…or find your heaviest assets:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
curl -s http://localhost:9001/stats \
|
|
85
|
+
| jq -r '.ep_performance_test_hooks.loadSizes.main
|
|
86
|
+
| to_entries | sort_by(-.value.decodedBodySize)[:10][]
|
|
87
|
+
| "\(.value.decodedBodySize)\t\(.key)"'
|
|
88
|
+
```
|
|
10
89
|
|
|
11
90
|
## Installation
|
|
12
91
|
|
package/package.json
CHANGED
package/static/js/performance.js
CHANGED
|
@@ -5,22 +5,29 @@
|
|
|
5
5
|
// was false, it was only when minify was set to true that I needed to do this hack.
|
|
6
6
|
// CRITICAL TODO: This causes a global leak which causes the tests to fail.
|
|
7
7
|
|
|
8
|
+
// Lazily get (and create) the shared namespace. `documentReady` only fires on the
|
|
9
|
+
// pad page (pad.html), never in the timeslider bootstrap, so hooks that also fire in
|
|
10
|
+
// the timeslider (postToolbarInit, postTimesliderInit) used to throw
|
|
11
|
+
// `TypeError: Cannot set properties of undefined` there. Those uncaught errors
|
|
12
|
+
// surfaced as error toasts and broke core's timeslider specs. Ensuring the namespace
|
|
13
|
+
// exists makes every hook safe regardless of which page fired it.
|
|
14
|
+
const hooks = () => (window.top.etherpadHooks = window.top.etherpadHooks || {});
|
|
15
|
+
|
|
8
16
|
exports.documentReady = () => {
|
|
9
|
-
|
|
10
|
-
window.top.etherpadHooks.documentReady = Date.now();
|
|
17
|
+
hooks().documentReady = Date.now();
|
|
11
18
|
};
|
|
12
|
-
exports.aceAttribClasses = () =>
|
|
13
|
-
exports.aceEditorCSS = () =>
|
|
19
|
+
exports.aceAttribClasses = () => hooks().aceAttribClasses = Date.now();
|
|
20
|
+
exports.aceEditorCSS = () => hooks().aceEditorCSS = Date.now();
|
|
14
21
|
exports.aceInitInnerdocbodyHead =
|
|
15
|
-
() =>
|
|
16
|
-
exports.aceInitialized = () =>
|
|
17
|
-
exports.postToolbarInit = () =>
|
|
18
|
-
exports.postTimesliderInit = () =>
|
|
22
|
+
() => hooks().aceInitInnerdocbodyHead = Date.now();
|
|
23
|
+
exports.aceInitialized = () => hooks().aceInitialized = Date.now();
|
|
24
|
+
exports.postToolbarInit = () => hooks().postToolbarInit = Date.now();
|
|
25
|
+
exports.postTimesliderInit = () => hooks().postTimesliderInit = Date.now();
|
|
19
26
|
|
|
20
27
|
exports.postAceInit = () => {
|
|
21
|
-
|
|
28
|
+
hooks().postAceInit = Date.now();
|
|
22
29
|
const perf = {};
|
|
23
|
-
perf.etherpadHooks =
|
|
30
|
+
perf.etherpadHooks = hooks();
|
|
24
31
|
perf.etherpadHooksDuration = {};
|
|
25
32
|
|
|
26
33
|
// Takes previous hook times and stores a duration
|