qyl-mcp-server 0.1.0
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/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/atomic-json-store.d.ts +37 -0
- package/dist/atomic-json-store.js +143 -0
- package/dist/atomic-json-store.js.map +1 -0
- package/dist/ci.d.ts +51 -0
- package/dist/ci.js +158 -0
- package/dist/ci.js.map +1 -0
- package/dist/collector.d.ts +43 -0
- package/dist/collector.js +163 -0
- package/dist/collector.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +39 -0
- package/dist/config.js.map +1 -0
- package/dist/contract-validation.d.ts +146 -0
- package/dist/contract-validation.js +350 -0
- package/dist/contract-validation.js.map +1 -0
- package/dist/data.d.ts +46 -0
- package/dist/data.js +138 -0
- package/dist/data.js.map +1 -0
- package/dist/demo.d.ts +23 -0
- package/dist/demo.js +836 -0
- package/dist/demo.js.map +1 -0
- package/dist/execution-evidence.d.ts +13 -0
- package/dist/execution-evidence.js +81 -0
- package/dist/execution-evidence.js.map +1 -0
- package/dist/execution-result.d.ts +5 -0
- package/dist/execution-result.js +17 -0
- package/dist/execution-result.js.map +1 -0
- package/dist/http-security.d.ts +36 -0
- package/dist/http-security.js +101 -0
- package/dist/http-security.js.map +1 -0
- package/dist/log-body.d.ts +3 -0
- package/dist/log-body.js +12 -0
- package/dist/log-body.js.map +1 -0
- package/dist/main.d.ts +51 -0
- package/dist/main.js +215 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp-app.html +218 -0
- package/dist/mcp-dashboard.html +223 -0
- package/dist/mcp-errors.d.ts +5 -0
- package/dist/mcp-errors.js +15 -0
- package/dist/mcp-errors.js.map +1 -0
- package/dist/mcp-semconv.d.ts +68 -0
- package/dist/mcp-semconv.js +208 -0
- package/dist/mcp-semconv.js.map +1 -0
- package/dist/native-execution.d.ts +132 -0
- package/dist/native-execution.js +508 -0
- package/dist/native-execution.js.map +1 -0
- package/dist/observability-suppression.d.ts +8 -0
- package/dist/observability-suppression.js +15 -0
- package/dist/observability-suppression.js.map +1 -0
- package/dist/secret-redactor.d.ts +30 -0
- package/dist/secret-redactor.js +255 -0
- package/dist/secret-redactor.js.map +1 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.js +207 -0
- package/dist/server.js.map +1 -0
- package/dist/stats.d.ts +18 -0
- package/dist/stats.js +141 -0
- package/dist/stats.js.map +1 -0
- package/dist/summaries.d.ts +14 -0
- package/dist/summaries.js +128 -0
- package/dist/summaries.js.map +1 -0
- package/dist/telemetry-redaction.d.ts +6 -0
- package/dist/telemetry-redaction.js +28 -0
- package/dist/telemetry-redaction.js.map +1 -0
- package/dist/telemetry.d.ts +72 -0
- package/dist/telemetry.js +362 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tools.d.ts +17 -0
- package/dist/tools.js +111 -0
- package/dist/tools.js.map +1 -0
- package/dist/wire.d.ts +10 -0
- package/dist/wire.js +7 -0
- package/dist/wire.js.map +1 -0
- package/package.json +91 -0
package/dist/demo.js
ADDED
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatically generated demo telemetry. Two independent sets:
|
|
3
|
+
*
|
|
4
|
+
* - 8 explorer traces + ~30 correlated logs + 3 sessions (trace explorer);
|
|
5
|
+
* - ~2 weeks of synthesized MCP passthrough spans (MCP dashboard), aggregated
|
|
6
|
+
* through the production aggregateMcpStats() path.
|
|
7
|
+
*
|
|
8
|
+
* Timestamps are anchored to now and the dataset is rebuilt when the anchor
|
|
9
|
+
* ages past a short TTL. The long-lived runner would otherwise let an
|
|
10
|
+
* import-time snapshot age out of the
|
|
11
|
+
* dashboard's 24h/168h windows entirely. Ids are seed-derived, so they stay
|
|
12
|
+
* stable across rebuilds; only the timestamps slide.
|
|
13
|
+
*/
|
|
14
|
+
import { collectorUrl } from "./config.js";
|
|
15
|
+
import packageMetadata from "../package.json" with { type: "json" };
|
|
16
|
+
/** Deterministic, valid lowercase-hex trace or span id. */
|
|
17
|
+
function hexId(seed, length) {
|
|
18
|
+
let hex = "";
|
|
19
|
+
let x = (seed ^ 0x5f3759df) >>> 0;
|
|
20
|
+
while (hex.length < length) {
|
|
21
|
+
x = (x * 1664525 + 1013904223) >>> 0;
|
|
22
|
+
hex += x.toString(16).padStart(8, "0");
|
|
23
|
+
}
|
|
24
|
+
return hex.slice(0, length);
|
|
25
|
+
}
|
|
26
|
+
/** The "now" every demo timestamp offsets from; refreshed by refreshDemo(). */
|
|
27
|
+
let demoAnchorMs = 0;
|
|
28
|
+
const minutesAgoMs = (minutes) => demoAnchorMs - minutes * 60_000;
|
|
29
|
+
const toNano = (absoluteMs) => Math.round(absoluteMs * 1e6);
|
|
30
|
+
const intAttribute = (value) => ({
|
|
31
|
+
type: "int",
|
|
32
|
+
value: String(value),
|
|
33
|
+
});
|
|
34
|
+
/** Per-service OTel resources shared by demo spans and logs. */
|
|
35
|
+
const DEMO_RESOURCES = {
|
|
36
|
+
"qyl-collector": {
|
|
37
|
+
"service.name": "qyl-collector",
|
|
38
|
+
"service.version": "0.4.2",
|
|
39
|
+
"telemetry.sdk.language": "dotnet",
|
|
40
|
+
},
|
|
41
|
+
"checkout-api": {
|
|
42
|
+
"service.name": "checkout-api",
|
|
43
|
+
"service.version": "2.11.0",
|
|
44
|
+
"telemetry.sdk.language": "dotnet",
|
|
45
|
+
"deployment.environment.name": "staging",
|
|
46
|
+
},
|
|
47
|
+
"agent-worker": {
|
|
48
|
+
"service.name": "agent-worker",
|
|
49
|
+
"service.version": "1.3.7",
|
|
50
|
+
"telemetry.sdk.language": "python",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
function buildDemoTrace(seq, startMs, specs) {
|
|
54
|
+
const traceId = hexId(seq * 7919 + 17, 32);
|
|
55
|
+
const spans = specs.map((spec, index) => {
|
|
56
|
+
const span = {
|
|
57
|
+
span_id: hexId(seq * 104_729 + index * 31 + 5, 16),
|
|
58
|
+
trace_id: traceId,
|
|
59
|
+
name: spec.name,
|
|
60
|
+
kind: spec.kind,
|
|
61
|
+
start_time_unix_nano: toNano(startMs + spec.start),
|
|
62
|
+
end_time_unix_nano: toNano(startMs + spec.end),
|
|
63
|
+
status: spec.status ?? { code: 0 },
|
|
64
|
+
resource: DEMO_RESOURCES[spec.service],
|
|
65
|
+
};
|
|
66
|
+
if (spec.attrs) {
|
|
67
|
+
span.attributes = Object.entries(spec.attrs).map(([key, value]) => ({
|
|
68
|
+
key,
|
|
69
|
+
value,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
if (spec.events) {
|
|
73
|
+
span.events = spec.events.map((event) => ({
|
|
74
|
+
name: event.name,
|
|
75
|
+
time_unix_nano: toNano(startMs + event.atMs),
|
|
76
|
+
...(event.attributes ? { attributes: event.attributes } : {}),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
return span;
|
|
80
|
+
});
|
|
81
|
+
// Wire up parent ids after all span ids exist.
|
|
82
|
+
specs.forEach((spec, index) => {
|
|
83
|
+
if (spec.parent !== undefined) {
|
|
84
|
+
spans[index].parent_span_id = spans[spec.parent].span_id;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const startNano = Math.min(...spans.map((s) => s.start_time_unix_nano));
|
|
88
|
+
const endNano = Math.max(...spans.map((s) => s.end_time_unix_nano));
|
|
89
|
+
const services = [
|
|
90
|
+
...new Set(spans.map((s) => String(s.resource["service.name"]))),
|
|
91
|
+
];
|
|
92
|
+
return {
|
|
93
|
+
trace_id: traceId,
|
|
94
|
+
spans,
|
|
95
|
+
root_span: spans.find((s) => !s.parent_span_id),
|
|
96
|
+
span_count: spans.length,
|
|
97
|
+
duration_ns: endNano - startNano,
|
|
98
|
+
start_time: new Date(startNano / 1e6).toISOString(),
|
|
99
|
+
end_time: new Date(endNano / 1e6).toISOString(),
|
|
100
|
+
services,
|
|
101
|
+
has_error: spans.some((s) => s.status.code === 2),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function buildDemoData() {
|
|
105
|
+
const NPGSQL_STACKTRACE = [
|
|
106
|
+
"Npgsql.NpgsqlException (0x80004005): Exception while writing to stream",
|
|
107
|
+
" ---> System.IO.IOException: Unable to write data to the transport connection: Connection reset by peer.",
|
|
108
|
+
" ---> System.Net.Sockets.SocketException (104): Connection reset by peer",
|
|
109
|
+
" at System.Net.Sockets.NetworkStream.Write(ReadOnlySpan`1 buffer)",
|
|
110
|
+
" --- End of inner exception stack trace ---",
|
|
111
|
+
" at Npgsql.Internal.NpgsqlConnector.Flush(Boolean async)",
|
|
112
|
+
" at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior)",
|
|
113
|
+
" at CheckoutApi.Orders.OrderRepository.InsertAsync(Order order) in /src/Orders/OrderRepository.cs:line 88",
|
|
114
|
+
" at CheckoutApi.Checkout.CheckoutHandler.Handle(CheckoutCommand cmd) in /src/Checkout/CheckoutHandler.cs:line 54",
|
|
115
|
+
].join("\n");
|
|
116
|
+
// --- Trace 1 (2 min ago): agentic GenAI run — plan, two model calls, a tool
|
|
117
|
+
// with a DuckDB lookup. agent-worker.
|
|
118
|
+
const agentRun = buildDemoTrace(1, minutesAgoMs(2), [
|
|
119
|
+
{
|
|
120
|
+
name: "POST /v1/agent/run",
|
|
121
|
+
kind: 2,
|
|
122
|
+
service: "agent-worker",
|
|
123
|
+
start: 0,
|
|
124
|
+
end: 4200,
|
|
125
|
+
status: { code: 1 },
|
|
126
|
+
attrs: {
|
|
127
|
+
"http.request.method": "POST",
|
|
128
|
+
"url.path": "/v1/agent/run",
|
|
129
|
+
"http.response.status_code": intAttribute(200),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
parent: 0,
|
|
134
|
+
name: "agent.plan",
|
|
135
|
+
kind: 1,
|
|
136
|
+
service: "agent-worker",
|
|
137
|
+
start: 15,
|
|
138
|
+
end: 70,
|
|
139
|
+
attrs: { "qyl.agent.step": "plan" },
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
parent: 0,
|
|
143
|
+
name: "chat claude-sonnet-5",
|
|
144
|
+
kind: 3,
|
|
145
|
+
service: "agent-worker",
|
|
146
|
+
start: 90,
|
|
147
|
+
end: 2350,
|
|
148
|
+
attrs: {
|
|
149
|
+
"gen_ai.system": "anthropic",
|
|
150
|
+
"gen_ai.request.model": "claude-sonnet-5",
|
|
151
|
+
"gen_ai.response.model": "claude-sonnet-5",
|
|
152
|
+
"gen_ai.usage.input_tokens": intAttribute(1874),
|
|
153
|
+
"gen_ai.usage.output_tokens": intAttribute(412),
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
parent: 0,
|
|
158
|
+
name: "tool.execute search_docs",
|
|
159
|
+
kind: 1,
|
|
160
|
+
service: "agent-worker",
|
|
161
|
+
start: 2400,
|
|
162
|
+
end: 2900,
|
|
163
|
+
attrs: { "qyl.tool.name": "search_docs" },
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
parent: 3,
|
|
167
|
+
name: "SELECT docs",
|
|
168
|
+
kind: 3,
|
|
169
|
+
service: "agent-worker",
|
|
170
|
+
start: 2410,
|
|
171
|
+
end: 2600,
|
|
172
|
+
attrs: {
|
|
173
|
+
"db.system": "duckdb",
|
|
174
|
+
"db.statement": "SELECT id, title, snippet FROM docs WHERE match_bm25(content, ?) LIMIT 10",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
parent: 0,
|
|
179
|
+
name: "chat claude-sonnet-5",
|
|
180
|
+
kind: 3,
|
|
181
|
+
service: "agent-worker",
|
|
182
|
+
start: 2950,
|
|
183
|
+
end: 4150,
|
|
184
|
+
attrs: {
|
|
185
|
+
"gen_ai.system": "anthropic",
|
|
186
|
+
"gen_ai.request.model": "claude-sonnet-5",
|
|
187
|
+
"gen_ai.response.model": "claude-sonnet-5",
|
|
188
|
+
"gen_ai.usage.input_tokens": intAttribute(2412),
|
|
189
|
+
"gen_ai.usage.output_tokens": intAttribute(655),
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
]);
|
|
193
|
+
// --- Trace 2 (5 min ago): GenAI summarize that calls the collector's own
|
|
194
|
+
// API cross-service (agent-worker → qyl-collector → DuckDB).
|
|
195
|
+
const agentSummarize = buildDemoTrace(2, minutesAgoMs(5), [
|
|
196
|
+
{
|
|
197
|
+
name: "POST /v1/agent/summarize",
|
|
198
|
+
kind: 2,
|
|
199
|
+
service: "agent-worker",
|
|
200
|
+
start: 0,
|
|
201
|
+
end: 2800,
|
|
202
|
+
status: { code: 1 },
|
|
203
|
+
attrs: {
|
|
204
|
+
"http.request.method": "POST",
|
|
205
|
+
"url.path": "/v1/agent/summarize",
|
|
206
|
+
"http.response.status_code": intAttribute(200),
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
parent: 0,
|
|
211
|
+
name: "chat claude-haiku-4-5",
|
|
212
|
+
kind: 3,
|
|
213
|
+
service: "agent-worker",
|
|
214
|
+
start: 40,
|
|
215
|
+
end: 1900,
|
|
216
|
+
attrs: {
|
|
217
|
+
"gen_ai.system": "anthropic",
|
|
218
|
+
"gen_ai.request.model": "claude-haiku-4-5",
|
|
219
|
+
"gen_ai.response.model": "claude-haiku-4-5-20251001",
|
|
220
|
+
"gen_ai.usage.input_tokens": intAttribute(932),
|
|
221
|
+
"gen_ai.usage.output_tokens": intAttribute(208),
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
parent: 0,
|
|
226
|
+
name: "GET /api/v1/traces",
|
|
227
|
+
kind: 3,
|
|
228
|
+
service: "agent-worker",
|
|
229
|
+
start: 1950,
|
|
230
|
+
end: 2100,
|
|
231
|
+
attrs: {
|
|
232
|
+
"http.request.method": "GET",
|
|
233
|
+
"url.full": `${collectorUrl()}/api/v1/traces?limit=20`,
|
|
234
|
+
"http.response.status_code": intAttribute(200),
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
parent: 2,
|
|
239
|
+
name: "GET /api/v1/traces",
|
|
240
|
+
kind: 2,
|
|
241
|
+
service: "qyl-collector",
|
|
242
|
+
start: 1960,
|
|
243
|
+
end: 2085,
|
|
244
|
+
attrs: {
|
|
245
|
+
"http.request.method": "GET",
|
|
246
|
+
"url.path": "/api/v1/traces",
|
|
247
|
+
"http.response.status_code": intAttribute(200),
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
parent: 3,
|
|
252
|
+
name: "SELECT traces",
|
|
253
|
+
kind: 3,
|
|
254
|
+
service: "qyl-collector",
|
|
255
|
+
start: 1970,
|
|
256
|
+
end: 2075,
|
|
257
|
+
attrs: {
|
|
258
|
+
"db.system": "duckdb",
|
|
259
|
+
"db.statement": "SELECT trace_id, min(start_time_unix_nano) FROM spans GROUP BY trace_id ORDER BY 2 DESC LIMIT 20",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
]);
|
|
263
|
+
// --- Trace 3 (8 min ago): failed checkout — Postgres insert dies with an
|
|
264
|
+
// exception event; root span carries the error status too.
|
|
265
|
+
const checkoutError = buildDemoTrace(3, minutesAgoMs(8), [
|
|
266
|
+
{
|
|
267
|
+
name: "POST /checkout",
|
|
268
|
+
kind: 2,
|
|
269
|
+
service: "checkout-api",
|
|
270
|
+
start: 0,
|
|
271
|
+
end: 430,
|
|
272
|
+
status: { code: 2, message: "checkout failed: order persistence error" },
|
|
273
|
+
attrs: {
|
|
274
|
+
"http.request.method": "POST",
|
|
275
|
+
"url.path": "/checkout",
|
|
276
|
+
"http.response.status_code": intAttribute(500),
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
parent: 0,
|
|
281
|
+
name: "validate cart",
|
|
282
|
+
kind: 1,
|
|
283
|
+
service: "checkout-api",
|
|
284
|
+
start: 5,
|
|
285
|
+
end: 35,
|
|
286
|
+
attrs: { "qyl.cart.items": intAttribute(3) },
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
parent: 0,
|
|
290
|
+
name: "reserve inventory",
|
|
291
|
+
kind: 1,
|
|
292
|
+
service: "checkout-api",
|
|
293
|
+
start: 40,
|
|
294
|
+
end: 160,
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
parent: 2,
|
|
298
|
+
name: "UPDATE inventory",
|
|
299
|
+
kind: 3,
|
|
300
|
+
service: "checkout-api",
|
|
301
|
+
start: 45,
|
|
302
|
+
end: 150,
|
|
303
|
+
attrs: {
|
|
304
|
+
"db.system": "postgresql",
|
|
305
|
+
"db.statement": "UPDATE inventory SET reserved = reserved + $1 WHERE sku = $2 AND available >= $1",
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
parent: 0,
|
|
310
|
+
name: "INSERT INTO orders",
|
|
311
|
+
kind: 3,
|
|
312
|
+
service: "checkout-api",
|
|
313
|
+
start: 170,
|
|
314
|
+
end: 410,
|
|
315
|
+
status: { code: 2, message: "connection reset by peer" },
|
|
316
|
+
attrs: {
|
|
317
|
+
"db.system": "postgresql",
|
|
318
|
+
"db.statement": "INSERT INTO orders (id, user_id, total_cents, status) VALUES ($1, $2, $3, 'pending')",
|
|
319
|
+
},
|
|
320
|
+
events: [
|
|
321
|
+
{
|
|
322
|
+
name: "exception",
|
|
323
|
+
atMs: 405,
|
|
324
|
+
attributes: [
|
|
325
|
+
{ key: "exception.type", value: "Npgsql.NpgsqlException" },
|
|
326
|
+
{
|
|
327
|
+
key: "exception.message",
|
|
328
|
+
value: "Exception while writing to stream: connection reset by peer",
|
|
329
|
+
},
|
|
330
|
+
{ key: "exception.stacktrace", value: NPGSQL_STACKTRACE },
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
]);
|
|
336
|
+
// --- Trace 4 (11 min ago): read-path order lookup, two Postgres queries.
|
|
337
|
+
const orderLookup = buildDemoTrace(4, minutesAgoMs(11), [
|
|
338
|
+
{
|
|
339
|
+
name: "GET /orders/{orderId}",
|
|
340
|
+
kind: 2,
|
|
341
|
+
service: "checkout-api",
|
|
342
|
+
start: 0,
|
|
343
|
+
end: 96,
|
|
344
|
+
status: { code: 1 },
|
|
345
|
+
attrs: {
|
|
346
|
+
"http.request.method": "GET",
|
|
347
|
+
"http.route": "/orders/{orderId}",
|
|
348
|
+
"http.response.status_code": intAttribute(200),
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
parent: 0,
|
|
353
|
+
name: "SELECT orders",
|
|
354
|
+
kind: 3,
|
|
355
|
+
service: "checkout-api",
|
|
356
|
+
start: 8,
|
|
357
|
+
end: 34,
|
|
358
|
+
attrs: {
|
|
359
|
+
"db.system": "postgresql",
|
|
360
|
+
"db.statement": "SELECT * FROM orders WHERE id = $1",
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
parent: 0,
|
|
365
|
+
name: "SELECT order_items",
|
|
366
|
+
kind: 3,
|
|
367
|
+
service: "checkout-api",
|
|
368
|
+
start: 38,
|
|
369
|
+
end: 71,
|
|
370
|
+
attrs: {
|
|
371
|
+
"db.system": "postgresql",
|
|
372
|
+
"db.statement": "SELECT sku, quantity, price_cents FROM order_items WHERE order_id = $1",
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
parent: 0,
|
|
377
|
+
name: "render response",
|
|
378
|
+
kind: 1,
|
|
379
|
+
service: "checkout-api",
|
|
380
|
+
start: 74,
|
|
381
|
+
end: 92,
|
|
382
|
+
},
|
|
383
|
+
]);
|
|
384
|
+
// --- Trace 5 (13 min ago): messaging — checkout publishes order.shipped,
|
|
385
|
+
// agent-worker consumes it and notifies the customer.
|
|
386
|
+
const orderShipped = buildDemoTrace(5, minutesAgoMs(13), [
|
|
387
|
+
{
|
|
388
|
+
name: "POST /orders/{orderId}/ship",
|
|
389
|
+
kind: 2,
|
|
390
|
+
service: "checkout-api",
|
|
391
|
+
start: 0,
|
|
392
|
+
end: 115,
|
|
393
|
+
status: { code: 1 },
|
|
394
|
+
attrs: {
|
|
395
|
+
"http.request.method": "POST",
|
|
396
|
+
"http.route": "/orders/{orderId}/ship",
|
|
397
|
+
"http.response.status_code": intAttribute(202),
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
parent: 0,
|
|
402
|
+
name: "publish order.shipped",
|
|
403
|
+
kind: 4,
|
|
404
|
+
service: "checkout-api",
|
|
405
|
+
start: 20,
|
|
406
|
+
end: 112,
|
|
407
|
+
attrs: {
|
|
408
|
+
"messaging.system": "kafka",
|
|
409
|
+
"messaging.destination.name": "order.events",
|
|
410
|
+
"messaging.operation.type": "publish",
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
parent: 1,
|
|
415
|
+
name: "process order.shipped",
|
|
416
|
+
kind: 5,
|
|
417
|
+
service: "agent-worker",
|
|
418
|
+
start: 60,
|
|
419
|
+
end: 108,
|
|
420
|
+
attrs: {
|
|
421
|
+
"messaging.system": "kafka",
|
|
422
|
+
"messaging.destination.name": "order.events",
|
|
423
|
+
"messaging.operation.type": "process",
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
parent: 2,
|
|
428
|
+
name: "notify customer",
|
|
429
|
+
kind: 1,
|
|
430
|
+
service: "agent-worker",
|
|
431
|
+
start: 65,
|
|
432
|
+
end: 100,
|
|
433
|
+
attrs: { "qyl.notification.channel": "email" },
|
|
434
|
+
},
|
|
435
|
+
]);
|
|
436
|
+
// --- Trace 6 (3 min ago): deep async pipeline — 13 spans, 4 levels, with a
|
|
437
|
+
// slow shard that needed a retry.
|
|
438
|
+
const pipeline = buildDemoTrace(6, minutesAgoMs(3), [
|
|
439
|
+
{
|
|
440
|
+
name: "pipeline.run nightly-eval",
|
|
441
|
+
kind: 1,
|
|
442
|
+
service: "agent-worker",
|
|
443
|
+
start: 0,
|
|
444
|
+
end: 8600,
|
|
445
|
+
status: { code: 1 },
|
|
446
|
+
attrs: { "qyl.pipeline.name": "nightly-eval" },
|
|
447
|
+
},
|
|
448
|
+
{ parent: 0, name: "stage.collect", kind: 1, service: "agent-worker", start: 20, end: 2100 },
|
|
449
|
+
{
|
|
450
|
+
parent: 1,
|
|
451
|
+
name: "fetch dataset shard-1",
|
|
452
|
+
kind: 3,
|
|
453
|
+
service: "agent-worker",
|
|
454
|
+
start: 40,
|
|
455
|
+
end: 900,
|
|
456
|
+
attrs: { "url.path": "/datasets/eval/shard-1" },
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
parent: 1,
|
|
460
|
+
name: "fetch dataset shard-2",
|
|
461
|
+
kind: 3,
|
|
462
|
+
service: "agent-worker",
|
|
463
|
+
start: 60,
|
|
464
|
+
end: 1400,
|
|
465
|
+
attrs: { "url.path": "/datasets/eval/shard-2" },
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
parent: 1,
|
|
469
|
+
name: "fetch dataset shard-3",
|
|
470
|
+
kind: 3,
|
|
471
|
+
service: "agent-worker",
|
|
472
|
+
start: 80,
|
|
473
|
+
end: 2050,
|
|
474
|
+
attrs: {
|
|
475
|
+
"url.path": "/datasets/eval/shard-3",
|
|
476
|
+
"http.request.resend_count": intAttribute(1),
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
parent: 4,
|
|
481
|
+
name: "GET shard-3 (retry)",
|
|
482
|
+
kind: 3,
|
|
483
|
+
service: "agent-worker",
|
|
484
|
+
start: 1100,
|
|
485
|
+
end: 2000,
|
|
486
|
+
attrs: { "url.path": "/datasets/eval/shard-3" },
|
|
487
|
+
},
|
|
488
|
+
{ parent: 0, name: "stage.transform", kind: 1, service: "agent-worker", start: 2150, end: 5100 },
|
|
489
|
+
{ parent: 6, name: "normalize records", kind: 1, service: "agent-worker", start: 2160, end: 3600 },
|
|
490
|
+
{ parent: 6, name: "dedupe records", kind: 1, service: "agent-worker", start: 3620, end: 5050 },
|
|
491
|
+
{ parent: 8, name: "hash partition 0", kind: 1, service: "agent-worker", start: 3630, end: 4300 },
|
|
492
|
+
{ parent: 8, name: "hash partition 1", kind: 1, service: "agent-worker", start: 3640, end: 5000 },
|
|
493
|
+
{ parent: 0, name: "stage.load", kind: 1, service: "agent-worker", start: 5150, end: 8550 },
|
|
494
|
+
{
|
|
495
|
+
parent: 11,
|
|
496
|
+
name: "INSERT eval_results",
|
|
497
|
+
kind: 3,
|
|
498
|
+
service: "agent-worker",
|
|
499
|
+
start: 5200,
|
|
500
|
+
end: 8500,
|
|
501
|
+
attrs: {
|
|
502
|
+
"db.system": "duckdb",
|
|
503
|
+
"db.statement": "INSERT INTO eval_results SELECT * FROM staging_eval",
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
]);
|
|
507
|
+
// --- Trace 7 (15 min ago): the collector ingesting an OTLP batch.
|
|
508
|
+
const otlpIngest = buildDemoTrace(7, minutesAgoMs(15), [
|
|
509
|
+
{
|
|
510
|
+
name: "POST /v1/traces",
|
|
511
|
+
kind: 2,
|
|
512
|
+
service: "qyl-collector",
|
|
513
|
+
start: 0,
|
|
514
|
+
end: 38,
|
|
515
|
+
status: { code: 1 },
|
|
516
|
+
attrs: {
|
|
517
|
+
"http.request.method": "POST",
|
|
518
|
+
"url.path": "/v1/traces",
|
|
519
|
+
"http.response.status_code": intAttribute(200),
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
parent: 0,
|
|
524
|
+
name: "parse otlp payload",
|
|
525
|
+
kind: 1,
|
|
526
|
+
service: "qyl-collector",
|
|
527
|
+
start: 2,
|
|
528
|
+
end: 9,
|
|
529
|
+
attrs: { "qyl.otlp.spans": intAttribute(142) },
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
parent: 0,
|
|
533
|
+
name: "INSERT INTO spans",
|
|
534
|
+
kind: 3,
|
|
535
|
+
service: "qyl-collector",
|
|
536
|
+
start: 12,
|
|
537
|
+
end: 33,
|
|
538
|
+
attrs: {
|
|
539
|
+
"db.system": "duckdb",
|
|
540
|
+
"db.statement": "INSERT INTO spans FROM read_otlp_batch(?)",
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
]);
|
|
544
|
+
// --- Trace 8 (1 min ago): quick single-turn chat.
|
|
545
|
+
const quickChat = buildDemoTrace(8, minutesAgoMs(1), [
|
|
546
|
+
{
|
|
547
|
+
name: "POST /v1/agent/chat",
|
|
548
|
+
kind: 2,
|
|
549
|
+
service: "agent-worker",
|
|
550
|
+
start: 0,
|
|
551
|
+
end: 1350,
|
|
552
|
+
status: { code: 1 },
|
|
553
|
+
attrs: {
|
|
554
|
+
"http.request.method": "POST",
|
|
555
|
+
"url.path": "/v1/agent/chat",
|
|
556
|
+
"http.response.status_code": intAttribute(200),
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
parent: 0,
|
|
561
|
+
name: "chat claude-sonnet-5",
|
|
562
|
+
kind: 3,
|
|
563
|
+
service: "agent-worker",
|
|
564
|
+
start: 25,
|
|
565
|
+
end: 1320,
|
|
566
|
+
attrs: {
|
|
567
|
+
"gen_ai.system": "anthropic",
|
|
568
|
+
"gen_ai.request.model": "claude-sonnet-5",
|
|
569
|
+
"gen_ai.response.model": "claude-sonnet-5",
|
|
570
|
+
"gen_ai.usage.input_tokens": intAttribute(154),
|
|
571
|
+
"gen_ai.usage.output_tokens": intAttribute(89),
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
]);
|
|
575
|
+
const traces = [
|
|
576
|
+
quickChat, // 1 min ago
|
|
577
|
+
agentRun, // 2 min
|
|
578
|
+
pipeline, // 3 min
|
|
579
|
+
agentSummarize, // 5 min
|
|
580
|
+
checkoutError, // 8 min
|
|
581
|
+
orderLookup, // 11 min
|
|
582
|
+
orderShipped, // 13 min
|
|
583
|
+
otlpIngest, // 15 min
|
|
584
|
+
];
|
|
585
|
+
// --- ~30 logs correlated to the traces above. Severity numbers follow
|
|
586
|
+
// OTel: TRACE=1, DEBUG=5, INFO=9, WARN=13, ERROR=17.
|
|
587
|
+
const log = (trace, spanIndex, offsetMs, severity, severityText, body, attrs) => {
|
|
588
|
+
const span = trace.spans[spanIndex];
|
|
589
|
+
const timeUnixNano = span.start_time_unix_nano + Math.round(offsetMs * 1e6);
|
|
590
|
+
return {
|
|
591
|
+
time_unix_nano: timeUnixNano,
|
|
592
|
+
observed_time_unix_nano: timeUnixNano,
|
|
593
|
+
severity_number: severity,
|
|
594
|
+
severity_text: severityText,
|
|
595
|
+
body: { string_value: body },
|
|
596
|
+
trace_id: trace.trace_id,
|
|
597
|
+
span_id: span.span_id,
|
|
598
|
+
resource: span.resource,
|
|
599
|
+
...(attrs
|
|
600
|
+
? {
|
|
601
|
+
attributes: Object.entries(attrs).map(([key, value]) => ({
|
|
602
|
+
key,
|
|
603
|
+
value,
|
|
604
|
+
})),
|
|
605
|
+
}
|
|
606
|
+
: {}),
|
|
607
|
+
};
|
|
608
|
+
};
|
|
609
|
+
const logs = [
|
|
610
|
+
// otlpIngest (15 min ago)
|
|
611
|
+
log(otlpIngest, 1, 1, 5, "DEBUG", "parsed OTLP batch: 142 spans, 3 resources"),
|
|
612
|
+
log(otlpIngest, 2, 2, 1, "TRACE", "duckdb appender flushed 142 rows into spans"),
|
|
613
|
+
// orderShipped (13 min)
|
|
614
|
+
log(orderShipped, 1, 2, 9, "INFO", "published order.shipped for order ord_8842 to order.events", { "messaging.kafka.offset": intAttribute(91204) }),
|
|
615
|
+
log(orderShipped, 2, 3, 9, "INFO", "consumed order.shipped for order ord_8842"),
|
|
616
|
+
log(orderShipped, 3, 5, 5, "DEBUG", "notification email queued for user user-77"),
|
|
617
|
+
// orderLookup (11 min)
|
|
618
|
+
log(orderLookup, 1, 3, 5, "DEBUG", "order ord_8842 loaded in 26ms (2 queries)"),
|
|
619
|
+
log(orderLookup, 0, 90, 9, "INFO", "GET /orders/ord_8842 -> 200 in 96ms"),
|
|
620
|
+
// checkoutError (8 min)
|
|
621
|
+
log(checkoutError, 0, 1, 9, "INFO", "checkout started for cart crt_5521 (3 items, total $84.97)"),
|
|
622
|
+
log(checkoutError, 1, 5, 5, "DEBUG", "cart crt_5521 validated: all SKUs in catalog"),
|
|
623
|
+
log(checkoutError, 3, 80, 13, "WARN", "inventory for sku KB-0042 low after reservation: 2 left"),
|
|
624
|
+
log(checkoutError, 4, 236, 17, "ERROR", "order insert failed: Npgsql.NpgsqlException: Exception while writing to stream: connection reset by peer\n" +
|
|
625
|
+
NPGSQL_STACKTRACE, { "db.system": "postgresql", "error.type": "Npgsql.NpgsqlException" }),
|
|
626
|
+
log(checkoutError, 0, 425, 17, "ERROR", "POST /checkout -> 500 (order persistence error, order not created)"),
|
|
627
|
+
log(checkoutError, 0, 428, 13, "WARN", "checkout for cart crt_5521 will be retried by the client"),
|
|
628
|
+
// agentSummarize (5 min)
|
|
629
|
+
log(agentSummarize, 0, 2, 9, "INFO", "summarize request received (target: last 20 traces)"),
|
|
630
|
+
log(agentSummarize, 1, 1855, 9, "INFO", "claude-haiku-4-5 responded: 932 in / 208 out tokens"),
|
|
631
|
+
log(agentSummarize, 3, 120, 5, "DEBUG", "served 20 trace summaries from duckdb in 105ms"),
|
|
632
|
+
// pipeline (3 min)
|
|
633
|
+
log(pipeline, 0, 5, 9, "INFO", "pipeline nightly-eval started (3 shards)"),
|
|
634
|
+
log(pipeline, 2, 855, 5, "DEBUG", "shard-1 fetched: 10,240 records"),
|
|
635
|
+
log(pipeline, 3, 1335, 5, "DEBUG", "shard-2 fetched: 10,240 records"),
|
|
636
|
+
log(pipeline, 4, 1015, 13, "WARN", "shard-3 fetch timed out after 1s, retrying (attempt 2)"),
|
|
637
|
+
log(pipeline, 5, 895, 5, "DEBUG", "shard-3 fetched on retry: 10,239 records"),
|
|
638
|
+
log(pipeline, 8, 10, 1, "TRACE", "dedupe: hashing 30,719 records into 2 partitions"),
|
|
639
|
+
log(pipeline, 12, 3295, 9, "INFO", "loaded 30,584 eval results into duckdb"),
|
|
640
|
+
log(pipeline, 0, 8590, 9, "INFO", "pipeline nightly-eval finished in 8.6s (135 duplicates dropped)"),
|
|
641
|
+
// agentRun (2 min)
|
|
642
|
+
log(agentRun, 0, 3, 9, "INFO", "agent run started for user user-42 (goal: summarize failing checkouts)"),
|
|
643
|
+
log(agentRun, 1, 50, 5, "DEBUG", "plan: [search_docs, analyze, respond]"),
|
|
644
|
+
log(agentRun, 2, 2255, 9, "INFO", "claude-sonnet-5 responded: 1874 in / 412 out tokens"),
|
|
645
|
+
log(agentRun, 4, 185, 1, "TRACE", "search_docs matched 10 documents (bm25)"),
|
|
646
|
+
log(agentRun, 5, 1195, 9, "INFO", "claude-sonnet-5 responded: 2412 in / 655 out tokens"),
|
|
647
|
+
log(agentRun, 0, 4195, 9, "INFO", "agent run completed in 4.2s"),
|
|
648
|
+
// quickChat (1 min)
|
|
649
|
+
log(quickChat, 0, 2, 9, "INFO", "chat request received (1 message)"),
|
|
650
|
+
log(quickChat, 1, 1290, 5, "DEBUG", "claude-sonnet-5 responded: 154 in / 89 out tokens"),
|
|
651
|
+
].sort((a, b) => a.time_unix_nano - b.time_unix_nano);
|
|
652
|
+
// --- 3 sessions grouping the traces.
|
|
653
|
+
const makeSession = (id, userId, sessionTraceList, state, genaiUsage) => {
|
|
654
|
+
const startMs = Math.min(...sessionTraceList.map((t) => Date.parse(t.start_time)));
|
|
655
|
+
const endMs = Math.max(...sessionTraceList.map((t) => Date.parse(t.end_time)));
|
|
656
|
+
const active = state === "active";
|
|
657
|
+
return {
|
|
658
|
+
"session.id": id,
|
|
659
|
+
...(userId ? { "user.id": userId } : {}),
|
|
660
|
+
start_time: new Date(startMs).toISOString(),
|
|
661
|
+
...(active ? {} : { end_time: new Date(endMs).toISOString(), duration_ms: endMs - startMs }),
|
|
662
|
+
trace_count: sessionTraceList.length,
|
|
663
|
+
span_count: sessionTraceList.reduce((sum, t) => sum + t.span_count, 0),
|
|
664
|
+
error_count: sessionTraceList.reduce((sum, t) => sum + t.spans.filter((s) => s.status.code === 2).length, 0),
|
|
665
|
+
services: [...new Set(sessionTraceList.flatMap((t) => t.services))],
|
|
666
|
+
state,
|
|
667
|
+
...(genaiUsage ? { genai_usage: genaiUsage } : {}),
|
|
668
|
+
};
|
|
669
|
+
};
|
|
670
|
+
const sessionTraces = {
|
|
671
|
+
"sess-demo-genai-01": [quickChat, agentRun, agentSummarize],
|
|
672
|
+
"sess-demo-pipeline-02": [pipeline, otlpIngest],
|
|
673
|
+
"sess-demo-checkout-03": [checkoutError, orderLookup, orderShipped],
|
|
674
|
+
};
|
|
675
|
+
const sessions = [
|
|
676
|
+
makeSession("sess-demo-genai-01", "user-42", sessionTraces["sess-demo-genai-01"], "ended", {
|
|
677
|
+
request_count: 4,
|
|
678
|
+
total_input_tokens: 5372,
|
|
679
|
+
total_output_tokens: 1364,
|
|
680
|
+
models_used: ["claude-sonnet-5", "claude-haiku-4-5"],
|
|
681
|
+
providers_used: ["anthropic"],
|
|
682
|
+
}),
|
|
683
|
+
makeSession("sess-demo-pipeline-02", undefined, sessionTraces["sess-demo-pipeline-02"], "active"),
|
|
684
|
+
makeSession("sess-demo-checkout-03", "user-77", sessionTraces["sess-demo-checkout-03"], "ended"),
|
|
685
|
+
];
|
|
686
|
+
return { traces, logs, sessions, sessionTraces };
|
|
687
|
+
}
|
|
688
|
+
const DEMO_REBUILD_INTERVAL_MS = 60_000;
|
|
689
|
+
let demoData;
|
|
690
|
+
let demoMcpSpans;
|
|
691
|
+
// Rebuild both datasets when the anchor ages past the TTL. Cheap enough to do
|
|
692
|
+
// lazily on access (~10k synthesized spans), and only demo mode ever gets here.
|
|
693
|
+
function refreshDemo() {
|
|
694
|
+
const now = Date.now();
|
|
695
|
+
if (demoData && now - demoAnchorMs < DEMO_REBUILD_INTERVAL_MS)
|
|
696
|
+
return;
|
|
697
|
+
demoAnchorMs = now;
|
|
698
|
+
demoData = buildDemoData();
|
|
699
|
+
demoMcpSpans = buildDemoMcpSpans();
|
|
700
|
+
}
|
|
701
|
+
export function getDemo() {
|
|
702
|
+
refreshDemo();
|
|
703
|
+
return demoData;
|
|
704
|
+
}
|
|
705
|
+
export function getDemoMcpSpans() {
|
|
706
|
+
refreshDemo();
|
|
707
|
+
return demoMcpSpans;
|
|
708
|
+
}
|
|
709
|
+
// =============================================================================
|
|
710
|
+
// Demo MCP spans (dashboard dataset — separate from the 8-trace explorer set)
|
|
711
|
+
//
|
|
712
|
+
// ~2 weeks of plausible qyl.mcp passthrough spans (service.name "qyl.mcp",
|
|
713
|
+
// `mcp.method.name` attribute): 4 tools with one failing-ish,
|
|
714
|
+
// 3 server names, stdio-dominant transports, a day/night traffic rhythm, and
|
|
715
|
+
// log-normal-ish durations with p95 tails. Aggregated through the production
|
|
716
|
+
// aggregateMcpStats() as live data.
|
|
717
|
+
// =============================================================================
|
|
718
|
+
/** Deterministic PRNG (mulberry32) so demo stats are stable within a run. */
|
|
719
|
+
function mulberry32(seed) {
|
|
720
|
+
let a = seed >>> 0;
|
|
721
|
+
return () => {
|
|
722
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
723
|
+
let t = a;
|
|
724
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
725
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
726
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
const QYL_MCP_RESOURCE = {
|
|
730
|
+
"service.name": "qyl.mcp",
|
|
731
|
+
"service.version": packageMetadata.version,
|
|
732
|
+
"telemetry.sdk.language": "nodejs",
|
|
733
|
+
};
|
|
734
|
+
/** Per-tool traffic profile: pick weight, log-normal duration, error rate. */
|
|
735
|
+
const DEMO_MCP_TOOLS = [
|
|
736
|
+
{ name: "display_traces", weight: 0.35, medianMs: 30, sigma: 0.7, errorRate: 0.01 },
|
|
737
|
+
{ name: "list_traces", weight: 0.3, medianMs: 14, sigma: 0.65, errorRate: 0.008 },
|
|
738
|
+
// The deliberately failing-ish tool — its error_rate must dominate.
|
|
739
|
+
{ name: "search_logs", weight: 0.2, medianMs: 20, sigma: 0.8, errorRate: 0.12 },
|
|
740
|
+
{ name: "get_trace", weight: 0.15, medianMs: 8, sigma: 0.6, errorRate: 0.018 },
|
|
741
|
+
];
|
|
742
|
+
const DEMO_MCP_SERVERS = [
|
|
743
|
+
{ name: "qyl-telemetry", weight: 0.6 },
|
|
744
|
+
{ name: "x-apps", weight: 0.25 },
|
|
745
|
+
{ name: "docs-mcp", weight: 0.15 },
|
|
746
|
+
];
|
|
747
|
+
function pickWeighted(items, roll) {
|
|
748
|
+
let acc = 0;
|
|
749
|
+
for (const item of items) {
|
|
750
|
+
acc += item.weight;
|
|
751
|
+
if (roll < acc)
|
|
752
|
+
return item;
|
|
753
|
+
}
|
|
754
|
+
return items[items.length - 1];
|
|
755
|
+
}
|
|
756
|
+
/** Requests per hour by hour-of-day: office-hours peak, quiet nights. */
|
|
757
|
+
function demoMcpHourlyRate(hourOfDay) {
|
|
758
|
+
if (hourOfDay >= 8 && hourOfDay <= 19)
|
|
759
|
+
return 48; // working day
|
|
760
|
+
if (hourOfDay >= 6 && hourOfDay <= 23)
|
|
761
|
+
return 26; // morning/evening shoulder
|
|
762
|
+
return 9; // night
|
|
763
|
+
}
|
|
764
|
+
function buildDemoMcpSpans() {
|
|
765
|
+
const rng = mulberry32(0x9e3779b9);
|
|
766
|
+
/** Log-normal-ish duration with an occasional heavier tail spike. */
|
|
767
|
+
const durationMs = (medianMs, sigma) => {
|
|
768
|
+
const u1 = Math.max(rng(), 1e-9);
|
|
769
|
+
const u2 = rng();
|
|
770
|
+
const normal = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
771
|
+
const base = medianMs * Math.exp(sigma * normal);
|
|
772
|
+
return rng() < 0.02 ? base * 4 : base; // p95 tail spikes
|
|
773
|
+
};
|
|
774
|
+
const spans = [];
|
|
775
|
+
const endMs = demoAnchorMs;
|
|
776
|
+
const startMs = endMs - 14 * 24 * 3_600_000;
|
|
777
|
+
let seq = 0;
|
|
778
|
+
for (let hourStart = startMs; hourStart < endMs; hourStart += 3_600_000) {
|
|
779
|
+
const rate = demoMcpHourlyRate(new Date(hourStart).getHours());
|
|
780
|
+
const count = Math.round(rate * (0.75 + rng() * 0.5));
|
|
781
|
+
for (let i = 0; i < count; i++) {
|
|
782
|
+
seq++;
|
|
783
|
+
const spanStartMs = hourStart + rng() * 3_600_000;
|
|
784
|
+
const server = pickWeighted(DEMO_MCP_SERVERS, rng()).name;
|
|
785
|
+
const transport = rng() < 0.78 ? "pipe" : "tcp";
|
|
786
|
+
const methodRoll = rng();
|
|
787
|
+
let method;
|
|
788
|
+
let name;
|
|
789
|
+
let median;
|
|
790
|
+
let sigma;
|
|
791
|
+
let errorRate;
|
|
792
|
+
const attrs = [];
|
|
793
|
+
if (methodRoll < 0.7) {
|
|
794
|
+
method = "tools/call";
|
|
795
|
+
const tool = pickWeighted(DEMO_MCP_TOOLS, rng());
|
|
796
|
+
name = `tools/call ${tool.name}`;
|
|
797
|
+
median = tool.medianMs;
|
|
798
|
+
sigma = tool.sigma;
|
|
799
|
+
errorRate = tool.errorRate;
|
|
800
|
+
attrs.push({ key: "gen_ai.tool.name", value: tool.name });
|
|
801
|
+
}
|
|
802
|
+
else if (methodRoll < 0.85) {
|
|
803
|
+
method = "tools/list";
|
|
804
|
+
name = "tools/list";
|
|
805
|
+
median = 3;
|
|
806
|
+
sigma = 0.5;
|
|
807
|
+
errorRate = 0.004;
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
method = "resources/read";
|
|
811
|
+
name = "resources/read";
|
|
812
|
+
median = 6;
|
|
813
|
+
sigma = 0.7;
|
|
814
|
+
errorRate = 0.01;
|
|
815
|
+
}
|
|
816
|
+
const isError = rng() < errorRate;
|
|
817
|
+
const ms = durationMs(median, sigma);
|
|
818
|
+
attrs.push({ key: "mcp.method.name", value: method }, { key: "service.peer.name", value: server }, { key: "network.transport", value: transport });
|
|
819
|
+
spans.push({
|
|
820
|
+
span_id: hexId(seq * 65_537 + 11, 16),
|
|
821
|
+
trace_id: hexId(seq * 92_821 + 3, 32),
|
|
822
|
+
name,
|
|
823
|
+
kind: 3,
|
|
824
|
+
start_time_unix_nano: toNano(spanStartMs),
|
|
825
|
+
end_time_unix_nano: toNano(spanStartMs + ms),
|
|
826
|
+
attributes: attrs,
|
|
827
|
+
status: isError
|
|
828
|
+
? { code: 2, message: "MCP request failed" }
|
|
829
|
+
: { code: 1 },
|
|
830
|
+
resource: QYL_MCP_RESOURCE,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return spans;
|
|
835
|
+
}
|
|
836
|
+
//# sourceMappingURL=demo.js.map
|