@hydranium/client-theia 1.0.0-next.10

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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/lib/browser/browser-capture.d.ts +21 -0
  4. package/lib/browser/browser-capture.d.ts.map +1 -0
  5. package/lib/browser/browser-capture.js +61 -0
  6. package/lib/browser/browser-capture.js.map +1 -0
  7. package/lib/browser/channel-logger.d.ts +87 -0
  8. package/lib/browser/channel-logger.d.ts.map +1 -0
  9. package/lib/browser/channel-logger.js +149 -0
  10. package/lib/browser/channel-logger.js.map +1 -0
  11. package/lib/browser/index.d.ts +13 -0
  12. package/lib/browser/index.d.ts.map +1 -0
  13. package/lib/browser/index.js +30 -0
  14. package/lib/browser/index.js.map +1 -0
  15. package/lib/browser/log-level-preference.d.ts +82 -0
  16. package/lib/browser/log-level-preference.d.ts.map +1 -0
  17. package/lib/browser/log-level-preference.js +146 -0
  18. package/lib/browser/log-level-preference.js.map +1 -0
  19. package/lib/browser/memory-diagnostics-contribution.d.ts +105 -0
  20. package/lib/browser/memory-diagnostics-contribution.d.ts.map +1 -0
  21. package/lib/browser/memory-diagnostics-contribution.js +229 -0
  22. package/lib/browser/memory-diagnostics-contribution.js.map +1 -0
  23. package/lib/index.d.ts +10 -0
  24. package/lib/index.d.ts.map +1 -0
  25. package/lib/index.js +11 -0
  26. package/lib/index.js.map +1 -0
  27. package/lib/node/abstract-socket-forwarding-connection-handler.d.ts +96 -0
  28. package/lib/node/abstract-socket-forwarding-connection-handler.d.ts.map +1 -0
  29. package/lib/node/abstract-socket-forwarding-connection-handler.js +224 -0
  30. package/lib/node/abstract-socket-forwarding-connection-handler.js.map +1 -0
  31. package/lib/node/index.d.ts +10 -0
  32. package/lib/node/index.d.ts.map +1 -0
  33. package/lib/node/index.js +26 -0
  34. package/lib/node/index.js.map +1 -0
  35. package/lib/testing/index.d.ts +11 -0
  36. package/lib/testing/index.d.ts.map +1 -0
  37. package/lib/testing/index.js +29 -0
  38. package/lib/testing/index.js.map +1 -0
  39. package/lib/testing/stub-inversify-context.d.ts +15 -0
  40. package/lib/testing/stub-inversify-context.d.ts.map +1 -0
  41. package/lib/testing/stub-inversify-context.js +32 -0
  42. package/lib/testing/stub-inversify-context.js.map +1 -0
  43. package/lib/testing/stub-output-channel.d.ts +25 -0
  44. package/lib/testing/stub-output-channel.d.ts.map +1 -0
  45. package/lib/testing/stub-output-channel.js +33 -0
  46. package/lib/testing/stub-output-channel.js.map +1 -0
  47. package/package.json +95 -0
  48. package/src/browser/browser-capture.ts +75 -0
  49. package/src/browser/channel-logger.ts +160 -0
  50. package/src/browser/index.ts +14 -0
  51. package/src/browser/log-level-preference.ts +127 -0
  52. package/src/browser/memory-diagnostics-contribution.ts +248 -0
  53. package/src/index.ts +19 -0
  54. package/src/node/abstract-socket-forwarding-connection-handler.ts +218 -0
  55. package/src/node/index.ts +10 -0
  56. package/src/testing/index.ts +14 -0
  57. package/src/testing/stub-inversify-context.ts +32 -0
  58. package/src/testing/stub-output-channel.ts +47 -0
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ /********************************************************************************
3
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
4
+ *
5
+ * This program and the accompanying materials are made available under the
6
+ * terms of the MIT License which is available in the project root.
7
+ *
8
+ * SPDX-License-Identifier: MIT
9
+ ********************************************************************************/
10
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
11
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
12
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
13
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
15
+ };
16
+ var __metadata = (this && this.__metadata) || function (k, v) {
17
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.LogLevelPreferenceContribution = exports.LogLevelPreference = void 0;
21
+ exports.bindLogLevelPreference = bindLogLevelPreference;
22
+ const protocol_1 = require("@hydranium/protocol");
23
+ // Narrow import paths, not the `@theia/core/lib/browser` barrel: it touches DOM
24
+ // globals at module load, which breaks node-environment unit tests. This module
25
+ // only needs the contribution token (a bare Symbol) and two common services.
26
+ const frontend_application_contribution_1 = require("@theia/core/lib/browser/frontend-application-contribution");
27
+ const logger_1 = require("@theia/core/lib/common/logger");
28
+ const preference_service_1 = require("@theia/core/lib/common/preferences/preference-service");
29
+ const inversify_1 = require("@theia/core/shared/inversify");
30
+ /** Inversify token for the preference id {@link LogLevelPreferenceContribution} watches. */
31
+ exports.LogLevelPreference = Symbol('LogLevelPreference');
32
+ /**
33
+ * Applies the framework log threshold from a Theia preference, once per
34
+ * application.
35
+ *
36
+ * **Why this is a `FrontendApplicationContribution` and not a logger concern.**
37
+ * The threshold is a process-global (`Logger.setLevel`, read by every
38
+ * `AbstractLogger` at emit time), so applying it is an *application* lifecycle
39
+ * event. Applying it from a logger's `@postConstruct` instead puts a global side
40
+ * effect on the construction of an object whose singleton scope is the
41
+ * per-diagram container, which costs two things:
42
+ *
43
+ * - the `onPreferenceChanged` subscription is never disposed AND is re-created
44
+ * for every diagram container, so opening N diagrams leaves N live listeners
45
+ * holding N loggers;
46
+ * - with no diagram open the preference is never applied at all, so anything
47
+ * logging before the first diagram runs at the default threshold.
48
+ *
49
+ * Bound by {@link bindLogLevelPreference} in the **frontend** container — a
50
+ * diagram container is a child and cannot contribute to the parent's
51
+ * multi-binding.
52
+ */
53
+ let LogLevelPreferenceContribution = class LogLevelPreferenceContribution {
54
+ preferenceName;
55
+ preferences;
56
+ logger;
57
+ /** Holds the single preference subscription. `DisposableCollection` disposes a
58
+ * push-after-dispose immediately, so a subscription that lands after
59
+ * {@link onStop} cannot outlive the application either. */
60
+ toDispose = new protocol_1.DisposableCollection();
61
+ /** Guards against a second subscription if `onStart` is ever invoked twice —
62
+ * a duplicated listener is exactly the failure this contribution exists to
63
+ * prevent. */
64
+ subscribed = false;
65
+ /**
66
+ * Returns `void` rather than the promise, so `FrontendApplication.start` does
67
+ * NOT await it.
68
+ *
69
+ * This is load-bearing: `PreferenceService.ready` resolves only once the
70
+ * preference providers are initialised, which happens as part of the same
71
+ * startup sequence that runs the contributions, so awaiting it here deadlocks
72
+ * the frontend on its preload splash. The threshold is therefore applied as
73
+ * soon as preferences are ready, which is early but not synchronously before
74
+ * the first possible log line.
75
+ */
76
+ onStart() {
77
+ void this.applyWhenReady();
78
+ }
79
+ /** The awaited body of {@link onStart}, separated so it can be driven directly
80
+ * in tests without going through the non-awaited lifecycle hook. */
81
+ async applyWhenReady() {
82
+ try {
83
+ await this.preferences.ready;
84
+ this.applyLevel();
85
+ if (this.subscribed) {
86
+ return;
87
+ }
88
+ this.subscribed = true;
89
+ this.toDispose.push(this.preferences.onPreferenceChanged(event => {
90
+ if (event.preferenceName === this.preferenceName) {
91
+ this.applyLevel();
92
+ }
93
+ }));
94
+ }
95
+ catch (err) {
96
+ // Reported, not swallowed: an unapplied threshold silently hides every
97
+ // diagnostic below the default, which reads as "the feature is broken".
98
+ this.logger.error(`Failed to apply the log threshold from '${this.preferenceName}'`, err);
99
+ }
100
+ }
101
+ onStop() {
102
+ this.toDispose.dispose();
103
+ }
104
+ /**
105
+ * Read the preference and apply it. An unparseable or unset value leaves the
106
+ * current threshold alone rather than resetting it to a default — the value may
107
+ * legitimately have been set by another source (an env baseline, a test).
108
+ *
109
+ * Override for a custom preference→level mapping.
110
+ */
111
+ applyLevel() {
112
+ const level = (0, protocol_1.parseLogLevel)(this.preferences.get(this.preferenceName));
113
+ if (level) {
114
+ protocol_1.Logger.setLevel(level);
115
+ }
116
+ }
117
+ };
118
+ exports.LogLevelPreferenceContribution = LogLevelPreferenceContribution;
119
+ __decorate([
120
+ (0, inversify_1.inject)(exports.LogLevelPreference),
121
+ __metadata("design:type", String)
122
+ ], LogLevelPreferenceContribution.prototype, "preferenceName", void 0);
123
+ __decorate([
124
+ (0, inversify_1.inject)(preference_service_1.PreferenceService),
125
+ __metadata("design:type", Object)
126
+ ], LogLevelPreferenceContribution.prototype, "preferences", void 0);
127
+ __decorate([
128
+ (0, inversify_1.inject)(logger_1.ILogger),
129
+ __metadata("design:type", Object)
130
+ ], LogLevelPreferenceContribution.prototype, "logger", void 0);
131
+ exports.LogLevelPreferenceContribution = LogLevelPreferenceContribution = __decorate([
132
+ (0, inversify_1.injectable)()
133
+ ], LogLevelPreferenceContribution);
134
+ /**
135
+ * Bind {@link LogLevelPreferenceContribution} for `preferenceName`.
136
+ *
137
+ * Call from a **frontend** container module. Adopters using
138
+ * `AbstractHydraniumGlspTheiaFrontendModule` set its `logLevelPreference` field
139
+ * instead and the base calls this for them.
140
+ */
141
+ function bindLogLevelPreference(bind, preferenceName) {
142
+ bind(exports.LogLevelPreference).toConstantValue(preferenceName);
143
+ bind(LogLevelPreferenceContribution).toSelf().inSingletonScope();
144
+ bind(frontend_application_contribution_1.FrontendApplicationContribution).toService(LogLevelPreferenceContribution);
145
+ }
146
+ //# sourceMappingURL=log-level-preference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-level-preference.js","sourceRoot":"","sources":["../../src/browser/log-level-preference.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;AAmHlF,wDAIC;AArHD,kDAAkF;AAClF,gFAAgF;AAChF,gFAAgF;AAChF,6EAA6E;AAC7E,iHAA4G;AAC5G,0DAAwD;AACxD,8FAA0F;AAC1F,4DAAmF;AAEnF,4FAA4F;AAC/E,QAAA,kBAAkB,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;GAoBG;AAEI,IAAM,8BAA8B,GAApC,MAAM,8BAA8B;IACO,cAAc,CAAU;IACzB,WAAW,CAAqB;IAC1C,MAAM,CAAW;IAErD;;gEAE4D;IACzC,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAC1D;;mBAEe;IACL,UAAU,GAAG,KAAK,CAAC;IAE7B;;;;;;;;;;OAUG;IACH,OAAO;QACJ,KAAK,IAAI,CAAC,cAAc,EAAE,CAAC;IAC9B,CAAC;IAED;yEACqE;IAC3D,KAAK,CAAC,cAAc;QAC3B,IAAI,CAAC;YACF,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;YAC7B,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACnB,OAAO;YACV,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,SAAS,CAAC,IAAI,CAChB,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE;gBAC1C,IAAI,KAAK,CAAC,cAAc,KAAK,IAAI,CAAC,cAAc,EAAE,CAAC;oBAChD,IAAI,CAAC,UAAU,EAAE,CAAC;gBACrB,CAAC;YACJ,CAAC,CAAC,CACJ,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACZ,uEAAuE;YACvE,wEAAwE;YACxE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,IAAI,CAAC,cAAc,GAAG,EAAE,GAAG,CAAC,CAAC;QAC7F,CAAC;IACJ,CAAC;IAED,MAAM;QACH,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IAC5B,CAAC;IAED;;;;;;OAMG;IACO,UAAU;QACjB,MAAM,KAAK,GAAG,IAAA,wBAAa,EAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAS,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;QAC/E,IAAI,KAAK,EAAE,CAAC;YACT,iBAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACJ,CAAC;CACH,CAAA;AAtEY,wEAA8B;AACO;IAA9C,IAAA,kBAAM,EAAC,0BAAkB,CAAC;;sEAA4C;AACzB;IAA7C,IAAA,kBAAM,EAAC,sCAAiB,CAAC;;mEAAoD;AAC1C;IAAnC,IAAA,kBAAM,EAAC,gBAAO,CAAC;;8DAAqC;yCAH3C,8BAA8B;IAD1C,IAAA,sBAAU,GAAE;GACA,8BAA8B,CAsE1C;AAED;;;;;;GAMG;AACH,SAAgB,sBAAsB,CAAC,IAAqB,EAAE,cAAsB;IACjF,IAAI,CAAC,0BAAkB,CAAC,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;IACzD,IAAI,CAAC,8BAA8B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACjE,IAAI,CAAC,mEAA+B,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;AACnF,CAAC"}
@@ -0,0 +1,105 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+ import { type DataServerDiagnosticsProtocol, type HostDiagnosticsProtocol, type StartProfilingArgs } from '@hydranium/protocol';
10
+ import { CommandContribution, MessageService, type Command, type CommandRegistry } from '@theia/core';
11
+ import { type interfaces } from '@theia/core/shared/inversify';
12
+ import { OutputChannelManager, type OutputChannel } from '@theia/output/lib/browser/output-channel';
13
+ /**
14
+ * Per-product branding for {@link MemoryDiagnosticsContribution}. Bound by the
15
+ * adopter so the command ids, palette category, and output channel carry the
16
+ * product name. The framework supplies the behaviour; only these strings vary.
17
+ */
18
+ export interface MemoryDiagnosticsOptions {
19
+ /** Prefix for the registered command ids: `<commandIdPrefix>.dumpServerState`. */
20
+ readonly commandIdPrefix: string;
21
+ /** Command-palette category. */
22
+ readonly category: string;
23
+ /** Output channel the full snapshots are appended to (View > Output). */
24
+ readonly channelName: string;
25
+ }
26
+ export declare const MemoryDiagnosticsOptions: unique symbol;
27
+ /**
28
+ * The server-process diagnostics the commands call. Equals
29
+ * {@link DataServerDiagnosticsProtocol} (`dumpServerState` / `writeHeapSnapshot`
30
+ * / `dumpPodMemory`); the adopter binds its data-server frontend proxy (which
31
+ * exposes those methods after connecting) to this symbol. Kept as a distinct
32
+ * symbol so the contribution stays decoupled from any concrete frontend type.
33
+ */
34
+ export type MemoryDiagnosticsService = DataServerDiagnosticsProtocol;
35
+ export declare const MemoryDiagnosticsService: unique symbol;
36
+ /**
37
+ * Optional host (parent) process diagnostics — the Theia backend itself, reached
38
+ * by an ordinary in-process RPC service ({@link HostDiagnosticsProtocol}), NOT
39
+ * the data-server socket. When bound, the contribution adds the "Dump Backend
40
+ * State" and "Write Heap Snapshot (Backend)" commands; left unbound, those two
41
+ * commands are simply not registered. `@hydranium/data-client-theia`'s
42
+ * `bindHostDiagnostics` binds the frontend proxy.
43
+ */
44
+ export type HostMemoryDiagnosticsService = HostDiagnosticsProtocol;
45
+ export declare const HostMemoryDiagnosticsService: unique symbol;
46
+ /**
47
+ * Memory-diagnostics commands, one per layer reachable from the frontend. Full
48
+ * multi-line snapshots are appended to the configured output channel; a one-line
49
+ * summary is toasted.
50
+ * - "Dump Server State" - the data-server process (heap/rss/event-loop/CPU/documents), via RPC.
51
+ * - "Dump Pod Memory" - cgroup current/peak/limit + process-tree RSS (the figure that OOMs a pod), via RPC.
52
+ * - "Dump Frontend State" - the browser tab's JS heap (Chrome `performance.memory`), client-side.
53
+ * - "Write Heap Snapshot" - a V8 heap snapshot of the data-server process, via RPC.
54
+ * - "Start/Stop Profiling" - a windowed CPU/allocation/GC/event-loop sampled profile of the data-server, via RPC.
55
+ * - "Record Performance Profile" - the same, wrapped over a fixed window (start, wait, stop, report).
56
+ * - "Dump RPC/LSP Latency" - per-method latency/throughput collected on the server, via RPC.
57
+ *
58
+ * Generic: the adopter binds {@link MemoryDiagnosticsOptions} (branding) and
59
+ * {@link MemoryDiagnosticsService} (its connected data-server frontend) and adds
60
+ * this class as a `CommandContribution` — see {@link bindMemoryDiagnostics}.
61
+ */
62
+ export declare class MemoryDiagnosticsContribution implements CommandContribution {
63
+ protected readonly diagnostics: MemoryDiagnosticsService;
64
+ protected readonly options: MemoryDiagnosticsOptions;
65
+ protected readonly messageService: MessageService;
66
+ protected readonly channels: OutputChannelManager;
67
+ protected readonly hostDiagnostics?: HostMemoryDiagnosticsService;
68
+ /** Dimensions the profiling commands capture — the server samples only what is named. */
69
+ protected readonly profileDimensions: StartProfilingArgs;
70
+ /** Window length for the "Record Performance Profile" command. */
71
+ protected readonly recordDurationMs = 10000;
72
+ /**
73
+ * Label of the stop-profiling command, and of the action its start toast
74
+ * offers. One field for both: two literals naming one command can be
75
+ * localized apart, leaving a button or an instruction that names a command
76
+ * the user cannot find.
77
+ */
78
+ protected readonly stopProfilingLabel: string;
79
+ registerCommands(registry: CommandRegistry): void;
80
+ protected command(id: string, label: string, category: string): Command;
81
+ /** Run a snapshot call, append the full result to the channel, toast the first matching summary line. */
82
+ protected report(what: string, produce: () => Promise<string>, summaryKeys: string[]): Promise<void>;
83
+ protected writeSnapshot(target: string, produce: (label: string) => Promise<string>): Promise<void>;
84
+ /** Begin an open-ended capture; the toast's stop action ends it and reports. */
85
+ protected startProfiling(): Promise<void>;
86
+ /** End the capture and report it. Shared, so the toast action and the command cannot diverge in behaviour. */
87
+ protected stopProfiling(): Promise<void>;
88
+ /** Capture a fixed-length window: start, wait, stop, and report the result. */
89
+ protected recordProfile(): Promise<void>;
90
+ /** Isolated so tests can drive the record window without a real timer. */
91
+ protected delay(ms: number): Promise<void>;
92
+ protected dumpFrontendState(): Promise<void>;
93
+ protected channel(): OutputChannel;
94
+ /** Pull the first line starting with one of `keys` for a one-line toast; full text is in the channel. */
95
+ protected summarize(snapshot: string, what: string, keys: string[]): string;
96
+ protected errorMessage(error: unknown): string;
97
+ }
98
+ /**
99
+ * Bind {@link MemoryDiagnosticsContribution} as a `CommandContribution` with the
100
+ * given branding. The adopter still binds {@link MemoryDiagnosticsService} to its
101
+ * connected data-server frontend separately (the framework cannot — the concrete
102
+ * frontend is adopter-defined). Call from a Theia frontend `ContainerModule`.
103
+ */
104
+ export declare function bindMemoryDiagnostics(bind: interfaces.Bind, options: MemoryDiagnosticsOptions): void;
105
+ //# sourceMappingURL=memory-diagnostics-contribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-diagnostics-contribution.d.ts","sourceRoot":"","sources":["../../src/browser/memory-diagnostics-contribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ /********************************************************************************
3
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
4
+ *
5
+ * This program and the accompanying materials are made available under the
6
+ * terms of the MIT License which is available in the project root.
7
+ *
8
+ * SPDX-License-Identifier: MIT
9
+ ********************************************************************************/
10
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
11
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
12
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
13
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
15
+ };
16
+ var __metadata = (this && this.__metadata) || function (k, v) {
17
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.MemoryDiagnosticsContribution = exports.HostMemoryDiagnosticsService = exports.MemoryDiagnosticsService = exports.MemoryDiagnosticsOptions = void 0;
21
+ exports.bindMemoryDiagnostics = bindMemoryDiagnostics;
22
+ const protocol_1 = require("@hydranium/protocol");
23
+ const browser_capture_1 = require("./browser-capture");
24
+ const core_1 = require("@theia/core");
25
+ const inversify_1 = require("@theia/core/shared/inversify");
26
+ const output_channel_1 = require("@theia/output/lib/browser/output-channel");
27
+ exports.MemoryDiagnosticsOptions = Symbol('MemoryDiagnosticsOptions');
28
+ exports.MemoryDiagnosticsService = Symbol('MemoryDiagnosticsService');
29
+ exports.HostMemoryDiagnosticsService = Symbol('HostMemoryDiagnosticsService');
30
+ /**
31
+ * Memory-diagnostics commands, one per layer reachable from the frontend. Full
32
+ * multi-line snapshots are appended to the configured output channel; a one-line
33
+ * summary is toasted.
34
+ * - "Dump Server State" - the data-server process (heap/rss/event-loop/CPU/documents), via RPC.
35
+ * - "Dump Pod Memory" - cgroup current/peak/limit + process-tree RSS (the figure that OOMs a pod), via RPC.
36
+ * - "Dump Frontend State" - the browser tab's JS heap (Chrome `performance.memory`), client-side.
37
+ * - "Write Heap Snapshot" - a V8 heap snapshot of the data-server process, via RPC.
38
+ * - "Start/Stop Profiling" - a windowed CPU/allocation/GC/event-loop sampled profile of the data-server, via RPC.
39
+ * - "Record Performance Profile" - the same, wrapped over a fixed window (start, wait, stop, report).
40
+ * - "Dump RPC/LSP Latency" - per-method latency/throughput collected on the server, via RPC.
41
+ *
42
+ * Generic: the adopter binds {@link MemoryDiagnosticsOptions} (branding) and
43
+ * {@link MemoryDiagnosticsService} (its connected data-server frontend) and adds
44
+ * this class as a `CommandContribution` — see {@link bindMemoryDiagnostics}.
45
+ */
46
+ let MemoryDiagnosticsContribution = class MemoryDiagnosticsContribution {
47
+ diagnostics;
48
+ options;
49
+ messageService;
50
+ channels;
51
+ hostDiagnostics;
52
+ /** Dimensions the profiling commands capture — the server samples only what is named. */
53
+ profileDimensions = { cpu: true, allocation: true, gc: true, eventLoopDelay: true };
54
+ /** Window length for the "Record Performance Profile" command. */
55
+ recordDurationMs = 10_000;
56
+ /**
57
+ * Label of the stop-profiling command, and of the action its start toast
58
+ * offers. One field for both: two literals naming one command can be
59
+ * localized apart, leaving a button or an instruction that names a command
60
+ * the user cannot find.
61
+ */
62
+ stopProfilingLabel = 'Stop Profiling (Server)';
63
+ registerCommands(registry) {
64
+ const { category } = this.options;
65
+ registry.registerCommand(this.command('dumpServerState', 'Dump Server State', category), {
66
+ execute: () => this.report('server state', () => this.diagnostics.dumpServerState({ label: new Date().toISOString() }), ['heap'])
67
+ });
68
+ registry.registerCommand(this.command('dumpPodMemory', 'Dump Pod Memory', category), {
69
+ execute: () => this.report('pod memory', () => this.diagnostics.dumpPodMemory(), ['current', 'rss sum'])
70
+ });
71
+ registry.registerCommand(this.command('dumpFrontendState', 'Dump Frontend State', category), {
72
+ execute: () => this.dumpFrontendState()
73
+ });
74
+ registry.registerCommand(this.command('writeHeapSnapshot', 'Write Heap Snapshot (Server)', category), {
75
+ execute: () => this.writeSnapshot('server', label => this.diagnostics.writeHeapSnapshot({ label }))
76
+ });
77
+ // Sampled profiling of the server process — sampling does NOT pause it (unlike
78
+ // the heap snapshot). Start/Stop are the manual pair; Record wraps a fixed window.
79
+ registry.registerCommand(this.command('startProfiling', 'Start Profiling (Server)', category), {
80
+ execute: () => this.startProfiling()
81
+ });
82
+ registry.registerCommand(this.command('stopProfiling', this.stopProfilingLabel, category), {
83
+ execute: () => this.stopProfiling()
84
+ });
85
+ registry.registerCommand(this.command('recordProfile', `Record Performance Profile (Server, ${Math.round(this.recordDurationMs / 1000)}s)`, category), {
86
+ execute: () => this.recordProfile()
87
+ });
88
+ registry.registerCommand(this.command('dumpLatency', 'Dump RPC/LSP Latency (Server)', category), {
89
+ execute: () => this.report('RPC/LSP latency', async () => (0, protocol_1.formatLatencyReport)(await this.diagnostics.getLatency()), ['window'])
90
+ });
91
+ // Host (Theia backend) process commands — registered only when the
92
+ // optional host-diagnostics service is bound (see HostMemoryDiagnosticsService).
93
+ const hostDiagnostics = this.hostDiagnostics;
94
+ if (hostDiagnostics) {
95
+ registry.registerCommand(this.command('dumpBackendState', 'Dump Backend State', category), {
96
+ execute: () => this.report('backend state', () => hostDiagnostics.dumpHostState({ label: new Date().toISOString() }), ['heap'])
97
+ });
98
+ registry.registerCommand(this.command('writeBackendHeapSnapshot', 'Write Heap Snapshot (Backend)', category), {
99
+ execute: () => this.writeSnapshot('backend', label => hostDiagnostics.writeHostHeapSnapshot({ label }))
100
+ });
101
+ }
102
+ }
103
+ command(id, label, category) {
104
+ return { id: `${this.options.commandIdPrefix}.${id}`, label, category };
105
+ }
106
+ /** Run a snapshot call, append the full result to the channel, toast the first matching summary line. */
107
+ async report(what, produce, summaryKeys) {
108
+ try {
109
+ const snapshot = await produce();
110
+ this.channel().appendLine(snapshot);
111
+ this.channel().appendLine('');
112
+ this.messageService.info(this.summarize(snapshot, what, summaryKeys), { timeout: 5000 });
113
+ }
114
+ catch (error) {
115
+ this.messageService.error(`Failed to dump ${what}: ${this.errorMessage(error)}`);
116
+ }
117
+ }
118
+ async writeSnapshot(target, produce) {
119
+ try {
120
+ this.messageService.info(`Writing ${target} heap snapshot — this briefly pauses that process...`, { timeout: 3000 });
121
+ const filePath = await produce(new Date().toISOString());
122
+ this.channel().appendLine(`Heap snapshot (${target}) written to ${filePath}`);
123
+ this.messageService.info(`Heap snapshot (${target}) written to ${filePath}`, { timeout: 8000 });
124
+ }
125
+ catch (error) {
126
+ this.messageService.error(`Failed to write ${target} heap snapshot: ${this.errorMessage(error)}`);
127
+ }
128
+ }
129
+ /** Begin an open-ended capture; the toast's stop action ends it and reports. */
130
+ async startProfiling() {
131
+ try {
132
+ await this.diagnostics.startProfiling(this.profileDimensions);
133
+ }
134
+ catch (error) {
135
+ this.messageService.error(`Failed to start profiling: ${this.errorMessage(error)}`);
136
+ return;
137
+ }
138
+ // No timeout: the capture runs as long as the user wants it to, and an
139
+ // auto-dismissing toast takes the stop affordance with it. Awaiting the
140
+ // toast is what keeps the action live, so this resolves only once the
141
+ // user acts on it or dismisses it.
142
+ const chosen = await this.messageService.info('Server profiling started — sampling does not stop the process.', { timeout: 0 }, this.stopProfilingLabel);
143
+ if (chosen === this.stopProfilingLabel) {
144
+ await this.stopProfiling();
145
+ }
146
+ }
147
+ /** End the capture and report it. Shared, so the toast action and the command cannot diverge in behaviour. */
148
+ stopProfiling() {
149
+ return this.report('profile', () => this.diagnostics.stopProfiling({ label: new Date().toISOString() }), ['duration']);
150
+ }
151
+ /** Capture a fixed-length window: start, wait, stop, and report the result. */
152
+ async recordProfile() {
153
+ try {
154
+ await this.diagnostics.startProfiling(this.profileDimensions);
155
+ }
156
+ catch (error) {
157
+ this.messageService.error(`Failed to start profiling: ${this.errorMessage(error)}`);
158
+ return;
159
+ }
160
+ this.messageService.info(`Recording a ${Math.round(this.recordDurationMs / 1000)}s server performance profile...`, { timeout: 4000 });
161
+ await this.delay(this.recordDurationMs);
162
+ await this.stopProfiling();
163
+ }
164
+ /** Isolated so tests can drive the record window without a real timer. */
165
+ delay(ms) {
166
+ return new Promise(resolve => setTimeout(resolve, ms));
167
+ }
168
+ async dumpFrontendState() {
169
+ const text = (0, browser_capture_1.formatBrowserRuntime)(await (0, browser_capture_1.captureBrowserRuntime)());
170
+ this.channel().appendLine(text);
171
+ this.messageService.info(text, { timeout: 6000 });
172
+ }
173
+ channel() {
174
+ const channel = this.channels.getChannel(this.options.channelName);
175
+ channel.show({ preserveFocus: true });
176
+ return channel;
177
+ }
178
+ /** Pull the first line starting with one of `keys` for a one-line toast; full text is in the channel. */
179
+ summarize(snapshot, what, keys) {
180
+ const lines = snapshot.split('\n');
181
+ for (const key of keys) {
182
+ const line = lines.find(entry => entry.trim().startsWith(key));
183
+ if (line) {
184
+ return `Captured ${what} —${line.replace(new RegExp(`^\\s*${key}\\s*`), ` ${key} `)}`;
185
+ }
186
+ }
187
+ return `Captured ${what} (see the ${this.options.channelName} output channel)`;
188
+ }
189
+ errorMessage(error) {
190
+ return error instanceof Error ? error.message : String(error);
191
+ }
192
+ };
193
+ exports.MemoryDiagnosticsContribution = MemoryDiagnosticsContribution;
194
+ __decorate([
195
+ (0, inversify_1.inject)(exports.MemoryDiagnosticsService),
196
+ __metadata("design:type", Object)
197
+ ], MemoryDiagnosticsContribution.prototype, "diagnostics", void 0);
198
+ __decorate([
199
+ (0, inversify_1.inject)(exports.MemoryDiagnosticsOptions),
200
+ __metadata("design:type", Object)
201
+ ], MemoryDiagnosticsContribution.prototype, "options", void 0);
202
+ __decorate([
203
+ (0, inversify_1.inject)(core_1.MessageService),
204
+ __metadata("design:type", core_1.MessageService)
205
+ ], MemoryDiagnosticsContribution.prototype, "messageService", void 0);
206
+ __decorate([
207
+ (0, inversify_1.inject)(output_channel_1.OutputChannelManager),
208
+ __metadata("design:type", output_channel_1.OutputChannelManager)
209
+ ], MemoryDiagnosticsContribution.prototype, "channels", void 0);
210
+ __decorate([
211
+ (0, inversify_1.inject)(exports.HostMemoryDiagnosticsService),
212
+ (0, inversify_1.optional)(),
213
+ __metadata("design:type", Object)
214
+ ], MemoryDiagnosticsContribution.prototype, "hostDiagnostics", void 0);
215
+ exports.MemoryDiagnosticsContribution = MemoryDiagnosticsContribution = __decorate([
216
+ (0, inversify_1.injectable)()
217
+ ], MemoryDiagnosticsContribution);
218
+ /**
219
+ * Bind {@link MemoryDiagnosticsContribution} as a `CommandContribution` with the
220
+ * given branding. The adopter still binds {@link MemoryDiagnosticsService} to its
221
+ * connected data-server frontend separately (the framework cannot — the concrete
222
+ * frontend is adopter-defined). Call from a Theia frontend `ContainerModule`.
223
+ */
224
+ function bindMemoryDiagnostics(bind, options) {
225
+ bind(exports.MemoryDiagnosticsOptions).toConstantValue(options);
226
+ bind(MemoryDiagnosticsContribution).toSelf().inSingletonScope();
227
+ bind(core_1.CommandContribution).toService(MemoryDiagnosticsContribution);
228
+ }
229
+ //# sourceMappingURL=memory-diagnostics-contribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-diagnostics-contribution.js","sourceRoot":"","sources":["../../src/browser/memory-diagnostics-contribution.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+ export {};
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAWlF,OAAO,EAAE,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /********************************************************************************
3
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
4
+ *
5
+ * This program and the accompanying materials are made available under the
6
+ * terms of the MIT License which is available in the project root.
7
+ *
8
+ * SPDX-License-Identifier: MIT
9
+ ********************************************************************************/
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF"}
@@ -0,0 +1,96 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+ import { type Channel, CommandService, type ConnectionHandler, type Disposable, ILogger, type MessageProvider, MessageService } from '@theia/core';
10
+ import * as net from 'node:net';
11
+ /** Resolved configuration for a {@link AbstractSocketForwardingConnectionHandler}.
12
+ * A head-specific subclass derives these from its own (adopter-facing) options
13
+ * and passes them to `super(...)`. */
14
+ export interface SocketForwardingConnectionHandlerOptions {
15
+ /** Theia service path the browser frontend opens a channel to. */
16
+ readonly path: string;
17
+ /** Command id whose return value is the target server's listening port (the
18
+ * server publishes it on the LSP connection at startup). */
19
+ readonly portCommand: string;
20
+ /** Short bracket prefix for this head's diagnostic logs, rendered as
21
+ * `[<logComponent>] …`. */
22
+ readonly logComponent: string;
23
+ /** Human-readable name of the server this head connects to — used in log and
24
+ * error messages. */
25
+ readonly serverName: string;
26
+ readonly findPortTimeout?: number;
27
+ readonly findPortAttempts?: number;
28
+ readonly connectTimeoutMs?: number;
29
+ /**
30
+ * Optional diagnostic hook called immediately after the outbound
31
+ * `net.Socket` to the server is created, BEFORE `socket.connect()` is
32
+ * invoked. Adopters attach `'data'` / `'close'` listeners here for
33
+ * byte-level observability when debugging wire-level issues; attaching
34
+ * before `connect()` guarantees the very first bytes are observed.
35
+ */
36
+ readonly onSocketCreated?: (socket: net.Socket) => void;
37
+ }
38
+ /**
39
+ * Cross-head base for the Theia backend half of a protocol head's transport:
40
+ * it bridges a Theia browser-frontend {@link Channel} to a server's TCP socket
41
+ * by relaying bytes between the two.
42
+ *
43
+ * Theia delivers each frontend connection to a registered `ConnectionHandler`
44
+ * keyed by its `path`. The handler asks the language server (via a registered
45
+ * command) for the target server's listening port, opens a `net.Socket` to that
46
+ * port, and forwards the Theia channel onto the socket. The frontend terminates
47
+ * the same wire protocol the server speaks, so the backend performs no semantic
48
+ * re-proxy — it relays bytes only and stays oblivious to the server's method set.
49
+ *
50
+ * The single per-head variation — *which* byte forwarder bridges the channel and
51
+ * the socket — is the abstract {@link forwardToSocketConnection} hook: the GLSP
52
+ * head plugs in `@eclipse-glsp/theia-integration`'s `SocketConnectionForwarder`,
53
+ * the data-server head its own `SocketChannelForwarder`. Keeping the forwarder
54
+ * behind the hook is what lets the data head stay GLSP-free while sharing the
55
+ * port-discovery + buffer-and-replay race fix + connect orchestration here.
56
+ */
57
+ export declare abstract class AbstractSocketForwardingConnectionHandler implements ConnectionHandler {
58
+ protected messageService: MessageService;
59
+ protected commandService: CommandService;
60
+ protected readonly logger: ILogger;
61
+ readonly path: string;
62
+ protected readonly portCommand: string;
63
+ protected readonly logComponent: string;
64
+ protected readonly serverName: string;
65
+ protected readonly findPortTimeout: number;
66
+ protected readonly findPortAttempts: number;
67
+ protected readonly connectTimeoutMs: number;
68
+ protected readonly onSocketCreated?: (socket: net.Socket) => void;
69
+ constructor(options: SocketForwardingConnectionHandlerOptions);
70
+ onConnection(connection: Channel): void;
71
+ protected initializeServerConnection(channel: Channel): Promise<void>;
72
+ protected findPort(): Promise<number>;
73
+ protected connectToServer(channel: Channel, port: number, preForwardBuffer?: {
74
+ bufferSub: Disposable;
75
+ buffered: MessageProvider[];
76
+ }): Promise<void>;
77
+ /**
78
+ * Bridge the Theia frontend `clientChannel` and the server `socket` so bytes
79
+ * relay both ways. Called once, after the socket is created and the
80
+ * pre-forward buffer is about to be replayed.
81
+ */
82
+ protected abstract forwardToSocketConnection(clientChannel: Channel, socket: net.Socket): Disposable;
83
+ /**
84
+ * Re-fire pre-forward buffered `MessageProvider`s on the channel's internal
85
+ * `onMessageEmitter` so the now-subscribed forwarder picks them up in arrival
86
+ * order. `MessageProvider` is a thunk (`() => ReadBuffer`) — buffering does
87
+ * not consume the read position, so replay produces the same bytes the
88
+ * forwarder would have seen if wired earlier.
89
+ *
90
+ * `AbstractChannel.onMessageEmitter` is `protected` in `@theia/core` but
91
+ * reachable at runtime; the cast is the workaround. A future Theia rename
92
+ * surfaces as a clear warning here.
93
+ */
94
+ protected replayBufferedMessages(channel: Channel, buffered: MessageProvider[]): void;
95
+ }
96
+ //# sourceMappingURL=abstract-socket-forwarding-connection-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract-socket-forwarding-connection-handler.d.ts","sourceRoot":"","sources":["../../src/node/abstract-socket-forwarding-connection-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,KAAK,OAAO,EACZ,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,OAAO,EACP,KAAK,eAAe,EACpB,cAAc,EAChB,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC;;uCAEuC;AACvC,MAAM,WAAW,wCAAwC;IACtD,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;iEAC6D;IAC7D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;gCAC4B;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;0BACsB;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,8BACsB,yCAA0C,YAAW,iBAAiB;IACjE,SAAS,CAAC,cAAc,EAAG,cAAc,CAAC;IAC1C,SAAS,CAAC,cAAc,EAAG,cAAc,CAAC;IAIjD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAG,OAAO,CAAC;IAErD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IACvC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IACxC,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAC3C,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAC5C,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAC5C,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC;gBAEtD,OAAO,EAAE,wCAAwC;IAW7D,YAAY,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI;cAIvB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;cAyB3D,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;cAwB3B,eAAe,CAC5B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,eAAe,EAAE,CAAA;KAAE,GACzE,OAAO,CAAC,IAAI,CAAC;IAyBhB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,yBAAyB,CAAC,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,UAAU;IAEpG;;;;;;;;;;OAUG;IACH,SAAS,CAAC,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,IAAI;CAiBvF"}