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.
@@ -24,7 +24,7 @@ jobs:
24
24
  runs-on: ubuntu-latest
25
25
  steps:
26
26
  - name: Checkout
27
- uses: actions/checkout@v6
27
+ uses: actions/checkout@v7
28
28
 
29
29
  - name: Automerge
30
30
  id: automerge
@@ -16,20 +16,20 @@ jobs:
16
16
  steps:
17
17
  -
18
18
  name: Install libreoffice
19
- uses: awalsh128/cache-apt-pkgs-action@v1.6.0
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@v6
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: 22
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@v6
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@v6
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
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@v6
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: 20
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@v6
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@v6
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
- A plugin to get test data to help improve the performance of Etherpad.
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
- See the collected stats at ``/stats``
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
@@ -39,7 +39,7 @@
39
39
  "type": "git",
40
40
  "url": "git+https://github.com/ether/ep_performance_test_hooks.git"
41
41
  },
42
- "version": "0.0.57",
42
+ "version": "0.0.59",
43
43
  "devDependencies": {
44
44
  "eslint": "^8.57.1",
45
45
  "eslint-config-etherpad": "^4.0.5",
@@ -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
- window.top.etherpadHooks = {};
10
- window.top.etherpadHooks.documentReady = Date.now();
17
+ hooks().documentReady = Date.now();
11
18
  };
12
- exports.aceAttribClasses = () => window.top.etherpadHooks.aceAttribClasses = Date.now();
13
- exports.aceEditorCSS = () => window.top.etherpadHooks.aceEditorCSS = Date.now();
19
+ exports.aceAttribClasses = () => hooks().aceAttribClasses = Date.now();
20
+ exports.aceEditorCSS = () => hooks().aceEditorCSS = Date.now();
14
21
  exports.aceInitInnerdocbodyHead =
15
- () => window.top.etherpadHooks.aceInitInnerdocbodyHead = Date.now();
16
- exports.aceInitialized = () => window.top.etherpadHooks.aceInitialized = Date.now();
17
- exports.postToolbarInit = () => window.top.etherpadHooks.postToolbarInit = Date.now();
18
- exports.postTimesliderInit = () => window.top.etherpadHooks.postTimesliderInit = Date.now();
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
- window.top.etherpadHooks.postAceInit = Date.now();
28
+ hooks().postAceInit = Date.now();
22
29
  const perf = {};
23
- perf.etherpadHooks = window.top.etherpadHooks;
30
+ perf.etherpadHooks = hooks();
24
31
  perf.etherpadHooksDuration = {};
25
32
 
26
33
  // Takes previous hook times and stores a duration