ep_performance_test_hooks 0.0.56 → 0.0.58

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.
@@ -29,7 +29,7 @@ jobs:
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:
@@ -19,7 +19,7 @@ jobs:
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:
@@ -23,7 +23,7 @@ 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
package/AGENTS.md ADDED
@@ -0,0 +1,60 @@
1
+ # Agent Guide — ep_performance_test_hooks
2
+
3
+ Etherpad plugin to show performance stats in /stats url.
4
+
5
+ ## Tech stack
6
+
7
+ * Etherpad plugin framework (hooks declared in `ep.json`)
8
+
9
+ ## Project structure
10
+
11
+ ```
12
+ ep_performance_test_hooks/
13
+ ├── AGENTS.md
14
+ ├── CONTRIBUTING.md
15
+ ├── LICENSE.md
16
+ ├── ep.json
17
+ ├── index.js
18
+ ├── package.json
19
+ ├── static/
20
+ │ ├── js/
21
+ │ ├── tests/
22
+ ```
23
+
24
+ ## Helpers used
25
+
26
+ _None — `ep_plugin_helpers` is not a dependency. Adoption is part of the helpers-adoption sweep (Phase 4)._
27
+
28
+
29
+ ## Helpers NOT used
30
+
31
+ _To be audited in the helpers-adoption sweep (Phase 4)._
32
+
33
+
34
+ ## Running tests locally
35
+
36
+ `ep_performance_test_hooks` runs inside Etherpad's test harness. From an etherpad checkout that has installed this plugin via `pnpm run plugins i --path ../ep_performance_test_hooks`:
37
+
38
+ ```bash
39
+ # Backend (Mocha) — harness boots its own server
40
+ pnpm --filter ep_etherpad-lite run test
41
+
42
+ # Playwright — needs `pnpm run dev` in a second terminal
43
+ pnpm --filter ep_etherpad-lite run test-ui
44
+ ```
45
+
46
+ ## Standing rules for agent edits
47
+
48
+ * PRs target `main`. Linear commits, no merge commits.
49
+ * Every bug fix includes a regression test in the same commit.
50
+ * All user-facing strings in `locales/`. No hardcoded English in templates.
51
+ * No hardcoded `aria-label` on icon-only controls — etherpad's html10n auto-populates `aria-label` from the localized string when (a) the element has a `data-l10n-id` and (b) no author-supplied `aria-label` is present. Adding a hardcoded English `aria-label` blocks that and leaves it untranslated. (See `etherpad-lite/src/static/js/vendors/html10n.ts:665-678`.)
52
+ * No nested interactive elements (no `<button>` inside `<a>`).
53
+ * LLM/Agent contributions are explicitly welcomed by maintainers.
54
+
55
+ ## Quick reference: hooks declared in `ep.json`
56
+
57
+ * Server: `handleMessage`
58
+ * Client: `postAceInit`, `documentReady`, `aceInitInnerdocbodyHead`, `aceInitialized`, `postToolbarInit`, `postTimesliderInit`
59
+
60
+ When adding a hook, register it in both `ep.json` *and* the matching `exports.<hook> = ...` in the JS file.
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.56",
42
+ "version": "0.0.58",
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