@composed-di/observability 0.5.0-alpha
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.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +50 -0
- package/dist/cli.js.map +1 -0
- package/dist/dashboardClient.d.ts +67 -0
- package/dist/dashboardClient.d.ts.map +1 -0
- package/dist/dashboardClient.js +168 -0
- package/dist/dashboardClient.js.map +1 -0
- package/dist/dashboardEventListener.d.ts +32 -0
- package/dist/dashboardEventListener.d.ts.map +1 -0
- package/dist/dashboardEventListener.js +88 -0
- package/dist/dashboardEventListener.js.map +1 -0
- package/dist/dashboardHtml.d.ts +11 -0
- package/dist/dashboardHtml.d.ts.map +1 -0
- package/dist/dashboardHtml.js +662 -0
- package/dist/dashboardHtml.js.map +1 -0
- package/dist/dashboardServer.d.ts +99 -0
- package/dist/dashboardServer.d.ts.map +1 -0
- package/dist/dashboardServer.js +360 -0
- package/dist/dashboardServer.js.map +1 -0
- package/dist/events.d.ts +80 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +7 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/moduleGraph.d.ts +13 -0
- package/dist/moduleGraph.d.ts.map +1 -0
- package/dist/moduleGraph.js +25 -0
- package/dist/moduleGraph.js.map +1 -0
- package/package.json +47 -0
- package/src/cli.ts +42 -0
- package/src/dashboardClient.ts +189 -0
- package/src/dashboardEventListener.ts +104 -0
- package/src/dashboardHtml.ts +659 -0
- package/src/dashboardServer.ts +391 -0
- package/src/events.ts +94 -0
- package/src/index.ts +5 -0
- package/src/moduleGraph.ts +37 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ServiceModule } from '@composed-di/core';
|
|
2
|
+
import { DashboardEventListener } from './dashboardEventListener';
|
|
3
|
+
import { ModuleGraph } from './moduleGraph';
|
|
4
|
+
import { DashboardSnapshot, SpanEvent } from './events';
|
|
5
|
+
export interface ServiceDashboardOptions {
|
|
6
|
+
/** How many recent events to keep for late-joining clients. Default 200. */
|
|
7
|
+
recentEventLimit?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A realtime dashboard server for observed ServiceModules.
|
|
11
|
+
*
|
|
12
|
+
* Serves a self-contained HTML page that renders the dependency graph and
|
|
13
|
+
* streams service initialization, disposal, and method-call activity to it
|
|
14
|
+
* over Server-Sent Events. Uses only Node.js built-ins.
|
|
15
|
+
*
|
|
16
|
+
* Two ways to feed it, mirroring how OpenTelemetry separates SDK and
|
|
17
|
+
* collector:
|
|
18
|
+
*
|
|
19
|
+
* **Standalone (recommended):** run the server on its own — `npx
|
|
20
|
+
* composed-di-dashboard` or `new ServiceDashboard().listen(4321)` — and have
|
|
21
|
+
* the application export to it with a {@link DashboardClient}. The server
|
|
22
|
+
* accepts `POST /v1/graph` (the dependency graph) and `POST /v1/events`
|
|
23
|
+
* (batched span events).
|
|
24
|
+
*
|
|
25
|
+
* **In-process:** create the module with `dashboard.listener`, call
|
|
26
|
+
* `dashboard.attach(module)`, and `listen` from the same process.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* // dashboard process
|
|
31
|
+
* const dashboard = new ServiceDashboard();
|
|
32
|
+
* await dashboard.listen(4321);
|
|
33
|
+
*
|
|
34
|
+
* // application process
|
|
35
|
+
* const client = new DashboardClient({ url: 'http://localhost:4321' });
|
|
36
|
+
* const module = ServiceModule.from(factories, client.listener);
|
|
37
|
+
* client.attach(module);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare class ServiceDashboard {
|
|
41
|
+
/** Pass this as the second argument to ServiceModule.from. */
|
|
42
|
+
readonly listener: DashboardEventListener;
|
|
43
|
+
private nodes;
|
|
44
|
+
private edges;
|
|
45
|
+
private services;
|
|
46
|
+
/** Open spans, so end events can be resolved to their start metadata. */
|
|
47
|
+
private openSpans;
|
|
48
|
+
private recent;
|
|
49
|
+
private readonly recentEventLimit;
|
|
50
|
+
/** Whether any graph was registered; ingest is refused (409) before that. */
|
|
51
|
+
private graphRegistered;
|
|
52
|
+
private clients;
|
|
53
|
+
private server;
|
|
54
|
+
private heartbeat;
|
|
55
|
+
constructor({ recentEventLimit }?: ServiceDashboardOptions);
|
|
56
|
+
/**
|
|
57
|
+
* In-process mode: reads the dependency graph out of the module. Call
|
|
58
|
+
* this with the module that was created with this dashboard's listener.
|
|
59
|
+
*/
|
|
60
|
+
attach(module: ServiceModule): this;
|
|
61
|
+
/**
|
|
62
|
+
* Registers a dependency graph, resetting all aggregated state (a new
|
|
63
|
+
* registration means a new application run). Remote applications call this
|
|
64
|
+
* through `POST /v1/graph`.
|
|
65
|
+
*/
|
|
66
|
+
registerGraph(graph: ModuleGraph): void;
|
|
67
|
+
/**
|
|
68
|
+
* Applies span events, e.g. exported by a remote DashboardClient through
|
|
69
|
+
* `POST /v1/events`. Services not present in the registered graph are
|
|
70
|
+
* added as standalone nodes rather than dropped.
|
|
71
|
+
*/
|
|
72
|
+
ingest(events: SpanEvent[]): void;
|
|
73
|
+
/** The current full dashboard state, as sent to newly connected clients. */
|
|
74
|
+
snapshot(): DashboardSnapshot;
|
|
75
|
+
/**
|
|
76
|
+
* Starts the HTTP server.
|
|
77
|
+
*
|
|
78
|
+
* @param port The port to listen on; 0 picks a free port. Default 4321.
|
|
79
|
+
* @param host The host to bind. Default 127.0.0.1.
|
|
80
|
+
* @returns The URL the dashboard is reachable at.
|
|
81
|
+
*/
|
|
82
|
+
listen(port?: number, host?: string): Promise<string>;
|
|
83
|
+
/** Stops the server and disconnects all clients. */
|
|
84
|
+
close(): Promise<void>;
|
|
85
|
+
private route;
|
|
86
|
+
private acceptEventStream;
|
|
87
|
+
/**
|
|
88
|
+
* Applies a raw span event to the aggregated per-service state and
|
|
89
|
+
* broadcasts the enriched event to connected clients.
|
|
90
|
+
*/
|
|
91
|
+
private onSpanEvent;
|
|
92
|
+
private applyStart;
|
|
93
|
+
private applyEnd;
|
|
94
|
+
/** Walks to the parent span to find which service triggered this one. */
|
|
95
|
+
private parentServiceOf;
|
|
96
|
+
/** Pushes the full state to all connected clients, e.g. on graph changes. */
|
|
97
|
+
private broadcastSnapshot;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=dashboardServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboardServer.d.ts","sourceRoot":"","sources":["../src/dashboardServer.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,WAAW,EAAe,MAAM,eAAe,CAAC;AAEzD,OAAO,EACL,iBAAiB,EAIjB,SAAS,EAIV,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,gBAAgB;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,yBAAgC;IAEjD,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,QAAQ,CAAmC;IACnD,yEAAyE;IACzE,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,6EAA6E;IAC7E,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAA+B;gBAEpC,EAAE,gBAAsB,EAAE,GAAE,uBAA4B;IAKpE;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAKnC;;;;OAIG;IACH,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAYvC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI;IAejC,4EAA4E;IAC5E,QAAQ,IAAI,iBAAiB;IAS7B;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,SAAO,EAAE,IAAI,SAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAyBxD,oDAAoD;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAgBd,KAAK;IA6DnB,OAAO,CAAC,iBAAiB;IAWzB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAqCnB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,QAAQ;IA2BhB,yEAAyE;IACzE,OAAO,CAAC,eAAe;IAKvB,6EAA6E;IAC7E,OAAO,CAAC,iBAAiB;CAI1B"}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ServiceDashboard = void 0;
|
|
13
|
+
const node_http_1 = require("node:http");
|
|
14
|
+
const dashboardEventListener_1 = require("./dashboardEventListener");
|
|
15
|
+
const moduleGraph_1 = require("./moduleGraph");
|
|
16
|
+
const dashboardHtml_1 = require("./dashboardHtml");
|
|
17
|
+
const HEARTBEAT_INTERVAL_MS = 15000;
|
|
18
|
+
/**
|
|
19
|
+
* A realtime dashboard server for observed ServiceModules.
|
|
20
|
+
*
|
|
21
|
+
* Serves a self-contained HTML page that renders the dependency graph and
|
|
22
|
+
* streams service initialization, disposal, and method-call activity to it
|
|
23
|
+
* over Server-Sent Events. Uses only Node.js built-ins.
|
|
24
|
+
*
|
|
25
|
+
* Two ways to feed it, mirroring how OpenTelemetry separates SDK and
|
|
26
|
+
* collector:
|
|
27
|
+
*
|
|
28
|
+
* **Standalone (recommended):** run the server on its own — `npx
|
|
29
|
+
* composed-di-dashboard` or `new ServiceDashboard().listen(4321)` — and have
|
|
30
|
+
* the application export to it with a {@link DashboardClient}. The server
|
|
31
|
+
* accepts `POST /v1/graph` (the dependency graph) and `POST /v1/events`
|
|
32
|
+
* (batched span events).
|
|
33
|
+
*
|
|
34
|
+
* **In-process:** create the module with `dashboard.listener`, call
|
|
35
|
+
* `dashboard.attach(module)`, and `listen` from the same process.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* // dashboard process
|
|
40
|
+
* const dashboard = new ServiceDashboard();
|
|
41
|
+
* await dashboard.listen(4321);
|
|
42
|
+
*
|
|
43
|
+
* // application process
|
|
44
|
+
* const client = new DashboardClient({ url: 'http://localhost:4321' });
|
|
45
|
+
* const module = ServiceModule.from(factories, client.listener);
|
|
46
|
+
* client.attach(module);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
class ServiceDashboard {
|
|
50
|
+
constructor({ recentEventLimit = 200 } = {}) {
|
|
51
|
+
/** Pass this as the second argument to ServiceModule.from. */
|
|
52
|
+
this.listener = new dashboardEventListener_1.DashboardEventListener();
|
|
53
|
+
this.nodes = [];
|
|
54
|
+
this.edges = [];
|
|
55
|
+
this.services = new Map();
|
|
56
|
+
/** Open spans, so end events can be resolved to their start metadata. */
|
|
57
|
+
this.openSpans = new Map();
|
|
58
|
+
this.recent = [];
|
|
59
|
+
/** Whether any graph was registered; ingest is refused (409) before that. */
|
|
60
|
+
this.graphRegistered = false;
|
|
61
|
+
this.clients = new Set();
|
|
62
|
+
this.server = null;
|
|
63
|
+
this.heartbeat = null;
|
|
64
|
+
this.recentEventLimit = recentEventLimit;
|
|
65
|
+
this.listener.subscribe((event) => this.onSpanEvent(event));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* In-process mode: reads the dependency graph out of the module. Call
|
|
69
|
+
* this with the module that was created with this dashboard's listener.
|
|
70
|
+
*/
|
|
71
|
+
attach(module) {
|
|
72
|
+
this.registerGraph((0, moduleGraph_1.moduleGraph)(module));
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Registers a dependency graph, resetting all aggregated state (a new
|
|
77
|
+
* registration means a new application run). Remote applications call this
|
|
78
|
+
* through `POST /v1/graph`.
|
|
79
|
+
*/
|
|
80
|
+
registerGraph(graph) {
|
|
81
|
+
this.graphRegistered = true;
|
|
82
|
+
this.nodes = graph.nodes;
|
|
83
|
+
this.edges = graph.edges;
|
|
84
|
+
this.services = new Map(this.nodes.map((node) => [node.name, freshStats()]));
|
|
85
|
+
this.openSpans.clear();
|
|
86
|
+
this.recent = [];
|
|
87
|
+
this.broadcastSnapshot();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Applies span events, e.g. exported by a remote DashboardClient through
|
|
91
|
+
* `POST /v1/events`. Services not present in the registered graph are
|
|
92
|
+
* added as standalone nodes rather than dropped.
|
|
93
|
+
*/
|
|
94
|
+
ingest(events) {
|
|
95
|
+
for (const event of events) {
|
|
96
|
+
if (event.type === 'start' &&
|
|
97
|
+
event.service !== null &&
|
|
98
|
+
!this.services.has(event.service)) {
|
|
99
|
+
this.nodes.push({ name: event.service });
|
|
100
|
+
this.services.set(event.service, freshStats());
|
|
101
|
+
this.broadcastSnapshot();
|
|
102
|
+
}
|
|
103
|
+
this.onSpanEvent(event);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** The current full dashboard state, as sent to newly connected clients. */
|
|
107
|
+
snapshot() {
|
|
108
|
+
return {
|
|
109
|
+
nodes: this.nodes,
|
|
110
|
+
edges: this.edges,
|
|
111
|
+
services: Object.fromEntries(this.services),
|
|
112
|
+
recent: this.recent,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Starts the HTTP server.
|
|
117
|
+
*
|
|
118
|
+
* @param port The port to listen on; 0 picks a free port. Default 4321.
|
|
119
|
+
* @param host The host to bind. Default 127.0.0.1.
|
|
120
|
+
* @returns The URL the dashboard is reachable at.
|
|
121
|
+
*/
|
|
122
|
+
listen(port = 4321, host = '127.0.0.1') {
|
|
123
|
+
if (this.server) {
|
|
124
|
+
throw new Error('Dashboard server is already listening');
|
|
125
|
+
}
|
|
126
|
+
const server = (0, node_http_1.createServer)((request, response) => {
|
|
127
|
+
void this.route(request, response);
|
|
128
|
+
});
|
|
129
|
+
this.server = server;
|
|
130
|
+
this.heartbeat = setInterval(() => {
|
|
131
|
+
this.clients.forEach((client) => client.write(': ping\n\n'));
|
|
132
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
133
|
+
// Don't let an idle dashboard keep the process alive on its own.
|
|
134
|
+
this.heartbeat.unref();
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
server.once('error', reject);
|
|
137
|
+
server.listen(port, host, () => {
|
|
138
|
+
const address = server.address();
|
|
139
|
+
const shownHost = host === '0.0.0.0' || host === '::' ? 'localhost' : host;
|
|
140
|
+
resolve(`http://${shownHost}:${address.port}`);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** Stops the server and disconnects all clients. */
|
|
145
|
+
close() {
|
|
146
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
147
|
+
if (this.heartbeat) {
|
|
148
|
+
clearInterval(this.heartbeat);
|
|
149
|
+
this.heartbeat = null;
|
|
150
|
+
}
|
|
151
|
+
this.clients.forEach((client) => client.end());
|
|
152
|
+
this.clients.clear();
|
|
153
|
+
const server = this.server;
|
|
154
|
+
this.server = null;
|
|
155
|
+
if (server) {
|
|
156
|
+
yield new Promise((resolve, reject) => {
|
|
157
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
route(request, response) {
|
|
163
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
164
|
+
var _a;
|
|
165
|
+
const path = (_a = request.url) === null || _a === void 0 ? void 0 : _a.split('?')[0];
|
|
166
|
+
const route = `${request.method} ${path}`;
|
|
167
|
+
try {
|
|
168
|
+
switch (route) {
|
|
169
|
+
case 'GET /':
|
|
170
|
+
response.writeHead(200, {
|
|
171
|
+
'content-type': 'text/html; charset=utf-8',
|
|
172
|
+
});
|
|
173
|
+
response.end((0, dashboardHtml_1.renderDashboardHtml)());
|
|
174
|
+
return;
|
|
175
|
+
case 'GET /events':
|
|
176
|
+
this.acceptEventStream(response);
|
|
177
|
+
return;
|
|
178
|
+
case 'GET /snapshot':
|
|
179
|
+
response.writeHead(200, { 'content-type': 'application/json' });
|
|
180
|
+
response.end(JSON.stringify(this.snapshot()));
|
|
181
|
+
return;
|
|
182
|
+
case 'POST /v1/graph': {
|
|
183
|
+
const graph = yield readJsonBody(request);
|
|
184
|
+
if (!Array.isArray(graph === null || graph === void 0 ? void 0 : graph.nodes) || !Array.isArray(graph === null || graph === void 0 ? void 0 : graph.edges)) {
|
|
185
|
+
throw new BadRequestError('expected { nodes: [], edges: [] }');
|
|
186
|
+
}
|
|
187
|
+
this.registerGraph(graph);
|
|
188
|
+
response.writeHead(204);
|
|
189
|
+
response.end();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
case 'POST /v1/events': {
|
|
193
|
+
const body = yield readJsonBody(request);
|
|
194
|
+
if (!Array.isArray(body === null || body === void 0 ? void 0 : body.events)) {
|
|
195
|
+
throw new BadRequestError('expected { events: [] }');
|
|
196
|
+
}
|
|
197
|
+
// A restarted server has no graph; tell the client to re-register
|
|
198
|
+
// before it sends events, so context isn't rendered nodeless.
|
|
199
|
+
if (!this.graphRegistered) {
|
|
200
|
+
response.writeHead(409, { 'content-type': 'text/plain' });
|
|
201
|
+
response.end('no graph registered; POST /v1/graph first');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this.ingest(body.events);
|
|
205
|
+
response.writeHead(204);
|
|
206
|
+
response.end();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
default:
|
|
210
|
+
response.writeHead(404, { 'content-type': 'text/plain' });
|
|
211
|
+
response.end('Not found');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
const badRequest = error instanceof BadRequestError;
|
|
216
|
+
response.writeHead(badRequest ? 400 : 500, {
|
|
217
|
+
'content-type': 'text/plain',
|
|
218
|
+
});
|
|
219
|
+
response.end(badRequest ? error.message : 'Internal error');
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
acceptEventStream(response) {
|
|
224
|
+
response.writeHead(200, {
|
|
225
|
+
'content-type': 'text/event-stream',
|
|
226
|
+
'cache-control': 'no-cache',
|
|
227
|
+
connection: 'keep-alive',
|
|
228
|
+
});
|
|
229
|
+
response.write(sseMessage('snapshot', this.snapshot()));
|
|
230
|
+
this.clients.add(response);
|
|
231
|
+
response.on('close', () => this.clients.delete(response));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Applies a raw span event to the aggregated per-service state and
|
|
235
|
+
* broadcasts the enriched event to connected clients.
|
|
236
|
+
*/
|
|
237
|
+
onSpanEvent(event) {
|
|
238
|
+
var _a, _b, _c;
|
|
239
|
+
let wire;
|
|
240
|
+
if (event.type === 'start') {
|
|
241
|
+
this.openSpans.set(event.id, event);
|
|
242
|
+
const stats = this.applyStart(event);
|
|
243
|
+
wire = {
|
|
244
|
+
span: event,
|
|
245
|
+
service: event.service,
|
|
246
|
+
method: event.method,
|
|
247
|
+
kind: event.kind,
|
|
248
|
+
parentService: this.parentServiceOf(event),
|
|
249
|
+
// Copy so the recent-events buffer keeps at-the-time values.
|
|
250
|
+
stats: stats ? Object.assign({}, stats) : null,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const start = this.openSpans.get(event.id);
|
|
255
|
+
this.openSpans.delete(event.id);
|
|
256
|
+
const stats = start ? this.applyEnd(start, event) : null;
|
|
257
|
+
wire = {
|
|
258
|
+
span: event,
|
|
259
|
+
service: (_a = start === null || start === void 0 ? void 0 : start.service) !== null && _a !== void 0 ? _a : null,
|
|
260
|
+
method: (_b = start === null || start === void 0 ? void 0 : start.method) !== null && _b !== void 0 ? _b : '?',
|
|
261
|
+
kind: (_c = start === null || start === void 0 ? void 0 : start.kind) !== null && _c !== void 0 ? _c : 'call',
|
|
262
|
+
parentService: start ? this.parentServiceOf(start) : null,
|
|
263
|
+
stats: stats ? Object.assign({}, stats) : null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
this.recent.push(wire);
|
|
267
|
+
if (this.recent.length > this.recentEventLimit) {
|
|
268
|
+
this.recent.splice(0, this.recent.length - this.recentEventLimit);
|
|
269
|
+
}
|
|
270
|
+
const message = sseMessage('span', wire);
|
|
271
|
+
this.clients.forEach((client) => client.write(message));
|
|
272
|
+
}
|
|
273
|
+
applyStart(event) {
|
|
274
|
+
const stats = event.service ? this.services.get(event.service) : undefined;
|
|
275
|
+
if (!stats)
|
|
276
|
+
return null;
|
|
277
|
+
if (event.kind === 'initialize') {
|
|
278
|
+
stats.status = 'initializing';
|
|
279
|
+
}
|
|
280
|
+
return stats;
|
|
281
|
+
}
|
|
282
|
+
applyEnd(start, event) {
|
|
283
|
+
const stats = start.service ? this.services.get(start.service) : undefined;
|
|
284
|
+
if (!stats)
|
|
285
|
+
return null;
|
|
286
|
+
if (event.error !== null) {
|
|
287
|
+
stats.errors += 1;
|
|
288
|
+
}
|
|
289
|
+
const transitions = {
|
|
290
|
+
initialize: () => {
|
|
291
|
+
stats.status = event.error !== null ? 'error' : 'ready';
|
|
292
|
+
if (event.error === null)
|
|
293
|
+
stats.initMs = event.durationMs;
|
|
294
|
+
},
|
|
295
|
+
dispose: () => {
|
|
296
|
+
if (event.error === null)
|
|
297
|
+
stats.status = 'disposed';
|
|
298
|
+
},
|
|
299
|
+
call: () => {
|
|
300
|
+
stats.calls += 1;
|
|
301
|
+
stats.totalCallMs += event.durationMs;
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
transitions[start.kind]();
|
|
305
|
+
return stats;
|
|
306
|
+
}
|
|
307
|
+
/** Walks to the parent span to find which service triggered this one. */
|
|
308
|
+
parentServiceOf(event) {
|
|
309
|
+
var _a, _b;
|
|
310
|
+
if (event.parentId === null)
|
|
311
|
+
return null;
|
|
312
|
+
return (_b = (_a = this.openSpans.get(event.parentId)) === null || _a === void 0 ? void 0 : _a.service) !== null && _b !== void 0 ? _b : null;
|
|
313
|
+
}
|
|
314
|
+
/** Pushes the full state to all connected clients, e.g. on graph changes. */
|
|
315
|
+
broadcastSnapshot() {
|
|
316
|
+
const message = sseMessage('snapshot', this.snapshot());
|
|
317
|
+
this.clients.forEach((client) => client.write(message));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
exports.ServiceDashboard = ServiceDashboard;
|
|
321
|
+
function freshStats() {
|
|
322
|
+
return {
|
|
323
|
+
status: 'pending',
|
|
324
|
+
initMs: null,
|
|
325
|
+
calls: 0,
|
|
326
|
+
errors: 0,
|
|
327
|
+
totalCallMs: 0,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
class BadRequestError extends Error {
|
|
331
|
+
}
|
|
332
|
+
const MAX_BODY_BYTES = 5 * 1024 * 1024;
|
|
333
|
+
function readJsonBody(request) {
|
|
334
|
+
return new Promise((resolve, reject) => {
|
|
335
|
+
const chunks = [];
|
|
336
|
+
let size = 0;
|
|
337
|
+
request.on('data', (chunk) => {
|
|
338
|
+
size += chunk.length;
|
|
339
|
+
if (size > MAX_BODY_BYTES) {
|
|
340
|
+
reject(new BadRequestError('request body too large'));
|
|
341
|
+
request.destroy();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
chunks.push(chunk);
|
|
345
|
+
});
|
|
346
|
+
request.on('end', () => {
|
|
347
|
+
try {
|
|
348
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
349
|
+
}
|
|
350
|
+
catch (_a) {
|
|
351
|
+
reject(new BadRequestError('invalid JSON body'));
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
request.on('error', reject);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function sseMessage(event, data) {
|
|
358
|
+
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
359
|
+
}
|
|
360
|
+
//# sourceMappingURL=dashboardServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboardServer.js","sourceRoot":"","sources":["../src/dashboardServer.ts"],"names":[],"mappings":"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"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event and state types shared between the dashboard event listener, the
|
|
3
|
+
* dashboard server, and the browser client (over the SSE wire).
|
|
4
|
+
*/
|
|
5
|
+
/** Classifies what a span represents in the service lifecycle. */
|
|
6
|
+
export type SpanKind = 'initialize' | 'dispose' | 'call';
|
|
7
|
+
/** Emitted when a traced function starts executing. */
|
|
8
|
+
export interface SpanStart {
|
|
9
|
+
type: 'start';
|
|
10
|
+
/** Monotonically increasing span id, unique per tracer. */
|
|
11
|
+
id: number;
|
|
12
|
+
/** Id of the span that was active when this one started, if any. */
|
|
13
|
+
parentId: number | null;
|
|
14
|
+
/** The qualified span name, e.g. "Database.query". */
|
|
15
|
+
name: string;
|
|
16
|
+
/** The service the span belongs to, or null if it could not be attributed. */
|
|
17
|
+
service: string | null;
|
|
18
|
+
/** The method or lifecycle step, e.g. "query" or "initialize". */
|
|
19
|
+
method: string;
|
|
20
|
+
kind: SpanKind;
|
|
21
|
+
/** Epoch milliseconds. */
|
|
22
|
+
time: number;
|
|
23
|
+
}
|
|
24
|
+
/** Emitted when a traced function finishes (or throws/rejects). */
|
|
25
|
+
export interface SpanEnd {
|
|
26
|
+
type: 'end';
|
|
27
|
+
/** Matches the id of the corresponding SpanStart. */
|
|
28
|
+
id: number;
|
|
29
|
+
/** Epoch milliseconds. */
|
|
30
|
+
time: number;
|
|
31
|
+
durationMs: number;
|
|
32
|
+
/** The error message when the traced function threw or rejected. */
|
|
33
|
+
error: string | null;
|
|
34
|
+
}
|
|
35
|
+
export type SpanEvent = SpanStart | SpanEnd;
|
|
36
|
+
export type ServiceStatus = 'pending' | 'initializing' | 'ready' | 'error' | 'disposed';
|
|
37
|
+
/** Aggregated per-service counters maintained by the dashboard. */
|
|
38
|
+
export interface ServiceStats {
|
|
39
|
+
status: ServiceStatus;
|
|
40
|
+
/** Duration of the last successful initialization, if any. */
|
|
41
|
+
initMs: number | null;
|
|
42
|
+
/** Number of completed method-call spans. */
|
|
43
|
+
calls: number;
|
|
44
|
+
/** Number of spans (of any kind) that ended with an error. */
|
|
45
|
+
errors: number;
|
|
46
|
+
/** Sum of completed method-call durations, for averaging. */
|
|
47
|
+
totalCallMs: number;
|
|
48
|
+
}
|
|
49
|
+
export interface GraphNode {
|
|
50
|
+
name: string;
|
|
51
|
+
}
|
|
52
|
+
/** A dependency edge: `from` depends on `to`. */
|
|
53
|
+
export interface GraphEdge {
|
|
54
|
+
from: string;
|
|
55
|
+
to: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The enriched event broadcast to dashboard clients. The server resolves
|
|
59
|
+
* cross-span context (parent service, span metadata on end events) and the
|
|
60
|
+
* updated per-service stats so the client needs no correlation logic.
|
|
61
|
+
*/
|
|
62
|
+
export interface WireEvent {
|
|
63
|
+
span: SpanEvent;
|
|
64
|
+
/** Service of the span; on end events resolved from the matching start. */
|
|
65
|
+
service: string | null;
|
|
66
|
+
method: string;
|
|
67
|
+
kind: SpanKind;
|
|
68
|
+
/** Service of the parent span, when the span was started by another service. */
|
|
69
|
+
parentService: string | null;
|
|
70
|
+
/** Updated stats for `service`, present when the event changed them. */
|
|
71
|
+
stats: ServiceStats | null;
|
|
72
|
+
}
|
|
73
|
+
/** Full state sent to a client when it connects. */
|
|
74
|
+
export interface DashboardSnapshot {
|
|
75
|
+
nodes: GraphNode[];
|
|
76
|
+
edges: GraphEdge[];
|
|
77
|
+
services: Record<string, ServiceStats>;
|
|
78
|
+
recent: WireEvent[];
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,kEAAkE;AAClE,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD,uDAAuD;AACvD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;IACf,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,KAAK,CAAC;IACZ,qDAAqD;IACrD,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5C,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,cAAc,GACd,OAAO,GACP,OAAO,GACP,UAAU,CAAC;AAEf,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,8DAA8D;IAC9D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;IACf,gFAAgF;IAChF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wEAAwE;IACxE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CAC5B;AAED,oDAAoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACvC,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./events"), exports);
|
|
18
|
+
__exportStar(require("./dashboardEventListener"), exports);
|
|
19
|
+
__exportStar(require("./dashboardClient"), exports);
|
|
20
|
+
__exportStar(require("./dashboardServer"), exports);
|
|
21
|
+
__exportStar(require("./moduleGraph"), exports);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,2DAAyC;AACzC,oDAAkC;AAClC,oDAAkC;AAClC,gDAA8B"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ServiceModule } from '@composed-di/core';
|
|
2
|
+
import { GraphEdge, GraphNode } from './events';
|
|
3
|
+
export interface ModuleGraph {
|
|
4
|
+
nodes: GraphNode[];
|
|
5
|
+
edges: GraphEdge[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Extracts the dependency graph of a module: one node per factory, one edge
|
|
9
|
+
* per dependency. Selector dependencies expand to an edge per grouped key
|
|
10
|
+
* ("may resolve any of these").
|
|
11
|
+
*/
|
|
12
|
+
export declare function moduleGraph(module: ServiceModule): ModuleGraph;
|
|
13
|
+
//# sourceMappingURL=moduleGraph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moduleGraph.d.ts","sourceRoot":"","sources":["../src/moduleGraph.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAmB9D"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.moduleGraph = moduleGraph;
|
|
4
|
+
const core_1 = require("@composed-di/core");
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the dependency graph of a module: one node per factory, one edge
|
|
7
|
+
* per dependency. Selector dependencies expand to an edge per grouped key
|
|
8
|
+
* ("may resolve any of these").
|
|
9
|
+
*/
|
|
10
|
+
function moduleGraph(module) {
|
|
11
|
+
const nodes = module.factories.map((factory) => ({
|
|
12
|
+
name: factory.provides.name,
|
|
13
|
+
}));
|
|
14
|
+
const edges = module.factories.flatMap((factory) => factory.dependsOn.flatMap((dependency) => {
|
|
15
|
+
const targets = dependency instanceof core_1.ServiceSelectorKey
|
|
16
|
+
? dependency.values
|
|
17
|
+
: [dependency];
|
|
18
|
+
return targets.map((target) => ({
|
|
19
|
+
from: factory.provides.name,
|
|
20
|
+
to: target.name,
|
|
21
|
+
}));
|
|
22
|
+
}));
|
|
23
|
+
return { nodes, edges };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=moduleGraph.js.map
|