@downtrace/protocol 0.1.0 → 0.2.1

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/README.md CHANGED
@@ -4,13 +4,19 @@ The ingestion contract between Downtrace agents and the Downtrace cloud, as a JS
4
4
 
5
5
  - `schema/v0/aggregates.schema.json` — the contract. Everything else derives from it.
6
6
  - `schema/v0/fixtures/{valid,invalid}/` — examples every implementation must accept and reject.
7
- - Exports: `PROTOCOL_VERSION`, `AGGREGATES_PATH`, `AGGREGATES_SCHEMA_V0`, `LATENCY_BOUNDARIES_V0`, `LATENCY_BUCKETS_V0`, `latencyBucket()`, and the types `AggregatesBatch`, `Interval`, `Endpoint`, `LatencyHistogram`, …
7
+ - Exports: `PROTOCOL_VERSION`, `AGGREGATES_PATH`, `AGGREGATES_SCHEMA_V0`, `LATENCY_BOUNDARIES_V0`, `LATENCY_BUCKETS_V0`, `latencyBucket()`, `QUERIES_PER_REQUEST_BOUNDARIES_V0`, `QUERIES_PER_REQUEST_BUCKETS_V0`, `queriesPerRequestBucket()`, and the types `AggregatesBatch`, `Interval`, `Endpoint`, `LatencyHistogram`, `PostgresStats`, …
8
8
 
9
9
  ## v0 in one sentence
10
10
 
11
11
  Every 10 seconds, for every route, an agent sends how many requests there were, how many failed, how statuses split, and a **35-bucket fixed latency histogram** (0.5 ms → 60 s). Fixed buckets add up across instances, so the cloud can compute fleet-wide percentiles.
12
12
 
13
- `POST {DOWNTRACE_URL}/v0/aggregates` · `Authorization: Bearer <token>` · JSON · 202 accepted, 400 invalid, 401 bad token.
13
+ Since **0.2.0** an endpoint may also carry `postgres`: how many queries each request made, as an **8-bucket histogram** (0, 1, 2, 3–5, 6–10, 11–20, 21–50, 51+), plus the total and the slowest query time. It is optional, so an agent that does not observe queries omits it. That is what turns "this endpoint got slower" into "it went from 12 queries per request to 65".
14
+
15
+ `POST {DOWNTRACE_URL}/v0/aggregates` · `Authorization: Bearer <token>` · JSON · 202 accepted, 400 invalid, 401 bad token, 429 rate limit or daily budget.
16
+
17
+ ## How the protocol changes
18
+
19
+ Fields are only ever **added**, and always optional. Each addition bumps the minor version, and the batch's `protocol` field is an enum of every published minor, so a cloud that speaks 0.2 still accepts an agent that speaks 0.1. Removing or renaming a field, or moving a histogram's bucket bounds, would be a major version on a new path. Bucket bounds are declared in the schema itself (`x-latency-boundaries-ms`, `x-queries-per-request-boundaries`), so they are part of the generated contract rather than a constant someone has to keep in sync.
14
20
 
15
21
  ## Source
16
22
 
package/dist/index.d.ts CHANGED
@@ -4,9 +4,9 @@
4
4
  */
5
5
  interface AggregatesBatch {
6
6
  /**
7
- * Protocol version this batch conforms to.
7
+ * Protocol version this batch conforms to. Every published minor of v0 stays acceptable.
8
8
  */
9
- protocol: "0.1.0";
9
+ protocol: "0.1.0" | "0.2.0";
10
10
  agent: AgentInfo;
11
11
  instance: InstanceInfo;
12
12
  deploy: DeployInfo;
@@ -61,6 +61,7 @@ interface Endpoint {
61
61
  errors: number;
62
62
  status: StatusClasses;
63
63
  latency: LatencyHistogram;
64
+ postgres?: PostgresStats;
64
65
  }
65
66
  interface StatusClasses {
66
67
  /**
@@ -98,16 +99,43 @@ interface LatencyHistogram {
98
99
  */
99
100
  max: number;
100
101
  }
102
+ /**
103
+ * Postgres work done per request on this route during the interval. Optional: agents that do not observe queries omit it.
104
+ */
105
+ interface PostgresStats {
106
+ /**
107
+ * Requests per bucket of queries-per-request; bounds in x-queries-per-request-boundaries, last bucket open-ended.
108
+ *
109
+ * @minItems 8
110
+ * @maxItems 8
111
+ */
112
+ queriesPerRequest: [number, number, number, number, number, number, number, number];
113
+ /**
114
+ * Sum of query durations (ms) across every request on this route.
115
+ */
116
+ totalMs: number;
117
+ /**
118
+ * Slowest single query (ms).
119
+ */
120
+ max: number;
121
+ }
101
122
  //#endregion
102
123
  //#region src/generated/boundaries.d.ts
103
124
  /** Upper bounds (ms) of the fixed latency buckets; the last bucket is open-ended. */
104
125
  declare const LATENCY_BOUNDARIES_V0: readonly number[];
105
126
  /** Number of buckets: boundaries + 1 open-ended bucket. */
106
127
  declare const LATENCY_BUCKETS_V0 = 35;
128
+ /** Upper bounds of the fixed queries-per-request buckets; the last bucket is open-ended. */
129
+ declare const QUERIES_PER_REQUEST_BOUNDARIES_V0: readonly number[];
130
+ /** Number of queries-per-request buckets: boundaries + 1 open-ended bucket. */
131
+ declare const QUERIES_PER_REQUEST_BUCKETS_V0 = 8;
107
132
  //#endregion
108
133
  //#region src/index.d.ts
109
- /** Version of the ingestion protocol spoken by agents and the cloud. */
110
- declare const PROTOCOL_VERSION = "0.1.0";
134
+ /**
135
+ * Version of the ingestion protocol this package speaks. Every published minor of v0 stays acceptable to the
136
+ * cloud, so an agent on an older minor keeps working: fields are only ever added, and always optional (ADR 0008).
137
+ */
138
+ declare const PROTOCOL_VERSION = "0.2.0";
111
139
  /** Path, relative to the ingest URL, that receives AggregatesBatch payloads. */
112
140
  declare const AGGREGATES_PATH = "/v0/aggregates";
113
141
  /** The JSON Schema (draft 2020-12) for AggregatesBatch, for validators on either side. */
@@ -122,7 +150,7 @@ declare const AGGREGATES_SCHEMA_V0: {
122
150
  properties: {
123
151
  protocol: {
124
152
  type: string;
125
- const: string;
153
+ enum: string[];
126
154
  description: string;
127
155
  };
128
156
  agent: {
@@ -267,6 +295,9 @@ declare const AGGREGATES_SCHEMA_V0: {
267
295
  latency: {
268
296
  $ref: string;
269
297
  };
298
+ postgres: {
299
+ $ref: string;
300
+ };
270
301
  };
271
302
  };
272
303
  StatusClasses: {
@@ -326,9 +357,41 @@ declare const AGGREGATES_SCHEMA_V0: {
326
357
  };
327
358
  };
328
359
  };
360
+ PostgresStats: {
361
+ title: string;
362
+ description: string;
363
+ type: string;
364
+ additionalProperties: boolean;
365
+ required: string[];
366
+ "x-queries-per-request-boundaries": number[];
367
+ properties: {
368
+ queriesPerRequest: {
369
+ type: string;
370
+ description: string;
371
+ minItems: number;
372
+ maxItems: number;
373
+ items: {
374
+ type: string;
375
+ minimum: number;
376
+ };
377
+ };
378
+ totalMs: {
379
+ type: string;
380
+ minimum: number;
381
+ description: string;
382
+ };
383
+ max: {
384
+ type: string;
385
+ minimum: number;
386
+ description: string;
387
+ };
388
+ };
389
+ };
329
390
  };
330
391
  };
331
392
  /** Index of the bucket a latency (ms) falls into: first boundary >= latency, else the open-ended last bucket. */
332
393
  declare function latencyBucket(ms: number): number;
394
+ /** Index of the bucket a query count falls into: first boundary >= count, else the open-ended last bucket. */
395
+ declare function queriesPerRequestBucket(queries: number): number;
333
396
  //#endregion
334
- export { AGGREGATES_PATH, AGGREGATES_SCHEMA_V0, type AgentInfo, type AggregatesBatch, type DeployInfo, type Endpoint, type InstanceInfo, type Interval, LATENCY_BOUNDARIES_V0, LATENCY_BUCKETS_V0, type LatencyHistogram, PROTOCOL_VERSION, type StatusClasses, latencyBucket };
397
+ export { AGGREGATES_PATH, AGGREGATES_SCHEMA_V0, type AgentInfo, type AggregatesBatch, type DeployInfo, type Endpoint, type InstanceInfo, type Interval, LATENCY_BOUNDARIES_V0, LATENCY_BUCKETS_V0, type LatencyHistogram, PROTOCOL_VERSION, type PostgresStats, QUERIES_PER_REQUEST_BOUNDARIES_V0, QUERIES_PER_REQUEST_BUCKETS_V0, type StatusClasses, latencyBucket, queriesPerRequestBucket };
package/dist/index.js CHANGED
@@ -16,8 +16,8 @@ var aggregates_schema_default = {
16
16
  properties: {
17
17
  "protocol": {
18
18
  "type": "string",
19
- "const": "0.1.0",
20
- "description": "Protocol version this batch conforms to."
19
+ "enum": ["0.1.0", "0.2.0"],
20
+ "description": "Protocol version this batch conforms to. Every published minor of v0 stays acceptable."
21
21
  },
22
22
  "agent": { "$ref": "#/$defs/AgentInfo" },
23
23
  "instance": { "$ref": "#/$defs/InstanceInfo" },
@@ -171,7 +171,8 @@ var aggregates_schema_default = {
171
171
  "description": "Responses with a 5xx status."
172
172
  },
173
173
  "status": { "$ref": "#/$defs/StatusClasses" },
174
- "latency": { "$ref": "#/$defs/LatencyHistogram" }
174
+ "latency": { "$ref": "#/$defs/LatencyHistogram" },
175
+ "postgres": { "$ref": "#/$defs/PostgresStats" }
175
176
  }
176
177
  },
177
178
  "StatusClasses": {
@@ -274,6 +275,48 @@ var aggregates_schema_default = {
274
275
  "description": "Maximum latency in ms."
275
276
  }
276
277
  }
278
+ },
279
+ "PostgresStats": {
280
+ "title": "PostgresStats",
281
+ "description": "Postgres work done per request on this route during the interval. Optional: agents that do not observe queries omit it.",
282
+ "type": "object",
283
+ "additionalProperties": false,
284
+ "required": [
285
+ "queriesPerRequest",
286
+ "totalMs",
287
+ "max"
288
+ ],
289
+ "x-queries-per-request-boundaries": [
290
+ 0,
291
+ 1,
292
+ 2,
293
+ 5,
294
+ 10,
295
+ 20,
296
+ 50
297
+ ],
298
+ "properties": {
299
+ "queriesPerRequest": {
300
+ "type": "array",
301
+ "description": "Requests per bucket of queries-per-request; bounds in x-queries-per-request-boundaries, last bucket open-ended.",
302
+ "minItems": 8,
303
+ "maxItems": 8,
304
+ "items": {
305
+ "type": "integer",
306
+ "minimum": 0
307
+ }
308
+ },
309
+ "totalMs": {
310
+ "type": "number",
311
+ "minimum": 0,
312
+ "description": "Sum of query durations (ms) across every request on this route."
313
+ },
314
+ "max": {
315
+ "type": "number",
316
+ "minimum": 0,
317
+ "description": "Slowest single query (ms)."
318
+ }
319
+ }
277
320
  }
278
321
  }
279
322
  };
@@ -318,10 +361,25 @@ const LATENCY_BOUNDARIES_V0 = [
318
361
  ];
319
362
  /** Number of buckets: boundaries + 1 open-ended bucket. */
320
363
  const LATENCY_BUCKETS_V0 = 35;
364
+ /** Upper bounds of the fixed queries-per-request buckets; the last bucket is open-ended. */
365
+ const QUERIES_PER_REQUEST_BOUNDARIES_V0 = [
366
+ 0,
367
+ 1,
368
+ 2,
369
+ 5,
370
+ 10,
371
+ 20,
372
+ 50
373
+ ];
374
+ /** Number of queries-per-request buckets: boundaries + 1 open-ended bucket. */
375
+ const QUERIES_PER_REQUEST_BUCKETS_V0 = 8;
321
376
  //#endregion
322
377
  //#region src/index.ts
323
- /** Version of the ingestion protocol spoken by agents and the cloud. */
324
- const PROTOCOL_VERSION = "0.1.0";
378
+ /**
379
+ * Version of the ingestion protocol this package speaks. Every published minor of v0 stays acceptable to the
380
+ * cloud, so an agent on an older minor keeps working: fields are only ever added, and always optional (ADR 0008).
381
+ */
382
+ const PROTOCOL_VERSION = "0.2.0";
325
383
  /** Path, relative to the ingest URL, that receives AggregatesBatch payloads. */
326
384
  const AGGREGATES_PATH = "/v0/aggregates";
327
385
  /** The JSON Schema (draft 2020-12) for AggregatesBatch, for validators on either side. */
@@ -335,5 +393,14 @@ function latencyBucket(ms) {
335
393
  }
336
394
  return n;
337
395
  }
396
+ /** Index of the bucket a query count falls into: first boundary >= count, else the open-ended last bucket. */
397
+ function queriesPerRequestBucket(queries) {
398
+ const n = QUERIES_PER_REQUEST_BOUNDARIES_V0.length;
399
+ for (let i = 0; i < n; i++) {
400
+ const bound = QUERIES_PER_REQUEST_BOUNDARIES_V0[i];
401
+ if (bound !== void 0 && queries <= bound) return i;
402
+ }
403
+ return n;
404
+ }
338
405
  //#endregion
339
- export { AGGREGATES_PATH, AGGREGATES_SCHEMA_V0, LATENCY_BOUNDARIES_V0, LATENCY_BUCKETS_V0, PROTOCOL_VERSION, latencyBucket };
406
+ export { AGGREGATES_PATH, AGGREGATES_SCHEMA_V0, LATENCY_BOUNDARIES_V0, LATENCY_BUCKETS_V0, PROTOCOL_VERSION, QUERIES_PER_REQUEST_BOUNDARIES_V0, QUERIES_PER_REQUEST_BUCKETS_V0, latencyBucket, queriesPerRequestBucket };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@downtrace/protocol",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Downtrace ingestion protocol: JSON Schema, fixtures and generated TypeScript types",
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,8 +9,8 @@
9
9
  "properties": {
10
10
  "protocol": {
11
11
  "type": "string",
12
- "const": "0.1.0",
13
- "description": "Protocol version this batch conforms to."
12
+ "enum": ["0.1.0", "0.2.0"],
13
+ "description": "Protocol version this batch conforms to. Every published minor of v0 stays acceptable."
14
14
  },
15
15
  "agent": {
16
16
  "$ref": "#/$defs/AgentInfo"
@@ -153,6 +153,9 @@
153
153
  },
154
154
  "latency": {
155
155
  "$ref": "#/$defs/LatencyHistogram"
156
+ },
157
+ "postgres": {
158
+ "$ref": "#/$defs/PostgresStats"
156
159
  }
157
160
  }
158
161
  },
@@ -215,6 +218,36 @@
215
218
  "description": "Maximum latency in ms."
216
219
  }
217
220
  }
221
+ },
222
+ "PostgresStats": {
223
+ "title": "PostgresStats",
224
+ "description": "Postgres work done per request on this route during the interval. Optional: agents that do not observe queries omit it.",
225
+ "type": "object",
226
+ "additionalProperties": false,
227
+ "required": ["queriesPerRequest", "totalMs", "max"],
228
+ "x-queries-per-request-boundaries": [0, 1, 2, 5, 10, 20, 50],
229
+ "properties": {
230
+ "queriesPerRequest": {
231
+ "type": "array",
232
+ "description": "Requests per bucket of queries-per-request; bounds in x-queries-per-request-boundaries, last bucket open-ended.",
233
+ "minItems": 8,
234
+ "maxItems": 8,
235
+ "items": {
236
+ "type": "integer",
237
+ "minimum": 0
238
+ }
239
+ },
240
+ "totalMs": {
241
+ "type": "number",
242
+ "minimum": 0,
243
+ "description": "Sum of query durations (ms) across every request on this route."
244
+ },
245
+ "max": {
246
+ "type": "number",
247
+ "minimum": 0,
248
+ "description": "Slowest single query (ms)."
249
+ }
250
+ }
218
251
  }
219
252
  }
220
253
  }
@@ -0,0 +1,50 @@
1
+ {
2
+ "protocol": "0.2.0",
3
+ "agent": {
4
+ "name": "@downtrace/agent",
5
+ "version": "0.0.0",
6
+ "runtime": "node",
7
+ "runtimeVersion": "v24.0.0"
8
+ },
9
+ "instance": {
10
+ "id": "7d1e9c6a-4f2b-4c1e-9b0a-2f6d3e8a1c55",
11
+ "hostname": "web-1",
12
+ "pid": 4242
13
+ },
14
+ "deploy": {
15
+ "version": "8f71ac2",
16
+ "environment": "production"
17
+ },
18
+ "intervals": [
19
+ {
20
+ "start": 1756900000000,
21
+ "durationMs": 10000,
22
+ "endpoints": [
23
+ {
24
+ "method": "POST",
25
+ "route": "/checkout",
26
+ "count": 3,
27
+ "errors": 0,
28
+ "status": {
29
+ "success": 3,
30
+ "redirect": 0,
31
+ "clientError": 0,
32
+ "serverError": 0
33
+ },
34
+ "latency": {
35
+ "counts": [
36
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
37
+ ],
38
+ "sum": 2.4,
39
+ "max": 0.9
40
+ },
41
+ "postgres": {
42
+ "queriesPerRequest": [0, 0, 0, 0, 0, 3, 0],
43
+ "totalMs": 41.25,
44
+ "max": 9.5
45
+ }
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "protocol": "0.2.0",
3
+ "agent": {
4
+ "name": "@downtrace/agent",
5
+ "version": "0.0.0",
6
+ "runtime": "node",
7
+ "runtimeVersion": "v24.0.0"
8
+ },
9
+ "instance": {
10
+ "id": "7d1e9c6a-4f2b-4c1e-9b0a-2f6d3e8a1c55",
11
+ "hostname": "web-1",
12
+ "pid": 4242
13
+ },
14
+ "deploy": {
15
+ "version": "8f71ac2",
16
+ "environment": "production"
17
+ },
18
+ "intervals": [
19
+ {
20
+ "start": 1756900000000,
21
+ "durationMs": 10000,
22
+ "endpoints": [
23
+ {
24
+ "method": "POST",
25
+ "route": "/checkout",
26
+ "count": 3,
27
+ "errors": 0,
28
+ "status": {
29
+ "success": 3,
30
+ "redirect": 0,
31
+ "clientError": 0,
32
+ "serverError": 0
33
+ },
34
+ "latency": {
35
+ "counts": [
36
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
37
+ ],
38
+ "sum": 2.4,
39
+ "max": 0.9
40
+ },
41
+ "postgres": {
42
+ "queriesPerRequest": [0, 0, 0, 0, 0, 3, 0, 0],
43
+ "totalMs": 41.25,
44
+ "max": 9.5
45
+ }
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ }