@downtrace/protocol 0.7.1 → 0.9.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/README.md +25 -1
- package/dist/index.d.ts +435 -20
- package/dist/index.js +325 -23
- package/package.json +1 -1
- package/schema/v0/aggregates.schema.json +108 -13
- package/schema/v0/capture-evidence.schema.json +159 -4
- package/schema/v0/fixtures/evidence/invalid/coarse-second-without-its-calls.json +20 -0
- package/schema/v0/fixtures/evidence/invalid/dependencies-truncated-negative.json +14 -0
- package/schema/v0/fixtures/evidence/invalid/dependencies-truncated-not-a-boolean.json +24 -0
- package/schema/v0/fixtures/evidence/invalid/operation-of-an-unknown-kind.json +33 -0
- package/schema/v0/fixtures/evidence/invalid/shed-with-an-extra-field.json +14 -0
- package/schema/v0/fixtures/evidence/invalid/shed-with-unknown-reason.json +14 -0
- package/schema/v0/fixtures/evidence/invalid/shed-with-zero-ms.json +14 -0
- package/schema/v0/fixtures/evidence/valid/with-coarse.json +40 -0
- package/schema/v0/fixtures/evidence/valid/with-dependencies-truncated.json +32 -0
- package/schema/v0/fixtures/evidence/valid/with-operation-kinds.json +57 -0
- package/schema/v0/fixtures/evidence/valid/with-shed.json +31 -0
- package/schema/v0/fixtures/invalid/a-running-total-of-nothing.json +27 -0
- package/schema/v0/fixtures/invalid/an-ending-nobody-declared.json +46 -0
- package/schema/v0/fixtures/invalid/call-with-a-path.json +38 -0
- package/schema/v0/fixtures/invalid/call-with-a-query-string.json +38 -0
- package/schema/v0/fixtures/invalid/command-with-its-key.json +38 -0
- package/schema/v0/fixtures/invalid/context-that-is-not-flat.json +40 -0
- package/schema/v0/fixtures/invalid/error-sources-with-a-call.json +49 -0
- package/schema/v0/fixtures/invalid/error-sources-with-a-query.json +49 -0
- package/schema/v0/fixtures/invalid/error-sources-with-a-repeat.json +49 -0
- package/schema/v0/fixtures/invalid/error-sources-with-an-unknown-value.json +49 -0
- package/schema/v0/fixtures/invalid/operation-of-an-unknown-kind.json +37 -0
- package/schema/v0/fixtures/valid/calls-and-commands.json +59 -0
- package/schema/v0/fixtures/valid/dropped-exceptions.json +56 -0
- package/schema/v0/fixtures/valid/error-sources.json +49 -0
- package/schema/v0/fixtures/valid/exceptions-with-a-running-total.json +38 -0
- package/schema/v0/fixtures/valid/exceptions-without-intervals.json +37 -0
- package/schema/v0/fixtures/valid/reported-errors.json +97 -0
- package/schema/v0/fixtures/valid/the-last-batch-of-a-process.json +59 -0
- package/schema/v0/fixtures/valid/triggers-without-intervals.json +28 -0
package/dist/index.js
CHANGED
|
@@ -24,7 +24,9 @@ var aggregates_schema_default = {
|
|
|
24
24
|
"0.4.0",
|
|
25
25
|
"0.5.0",
|
|
26
26
|
"0.6.0",
|
|
27
|
-
"0.7.0"
|
|
27
|
+
"0.7.0",
|
|
28
|
+
"0.8.0",
|
|
29
|
+
"0.9.0"
|
|
28
30
|
],
|
|
29
31
|
"description": "Protocol version this batch conforms to. Every published minor of v0 stays acceptable."
|
|
30
32
|
},
|
|
@@ -36,7 +38,7 @@ var aggregates_schema_default = {
|
|
|
36
38
|
"minItems": 0,
|
|
37
39
|
"maxItems": 6,
|
|
38
40
|
"items": { "$ref": "#/$defs/Interval" },
|
|
39
|
-
"description": "The intervals this batch carries. May be empty
|
|
41
|
+
"description": "The intervals this batch carries. May be empty whenever the batch carries anything else: a process that serves no requests closes no interval, and nothing else a batch carries hangs off one — tying delivery to an interval lost the profile at every shutdown (gh-375) and every exception of a process with no traffic (gh-627). A batch that carries nothing at all is refused by the cloud with a reason, which the schema cannot say without making the generated types unusable."
|
|
40
42
|
},
|
|
41
43
|
"profile": { "$ref": "#/$defs/Profile" },
|
|
42
44
|
"captures": {
|
|
@@ -55,7 +57,15 @@ var aggregates_schema_default = {
|
|
|
55
57
|
"type": "array",
|
|
56
58
|
"maxItems": 4,
|
|
57
59
|
"items": { "$ref": "#/$defs/LocalTrigger" },
|
|
58
|
-
"description": "Local signals that ask for a capture. `product.md:124` gives the instrumentation «
|
|
60
|
+
"description": "Local signals that ask for a capture. `product.md:124` gives the instrumentation «trigger on local signals»: it sees a process in trouble before any aggregate crosses the cloud, and asking in the batch costs no channel of its own — the answer already carries the orders (ADR 0071). Absent from every sender that predates it and from every batch with nothing to ask. The cloud decides: the same budget, cooldown and concurrency as any other capture (`product.md:122`), and a refusal is silence, because there is nothing the instrumentation would do differently."
|
|
61
|
+
},
|
|
62
|
+
"ending": {
|
|
63
|
+
"enum": [
|
|
64
|
+
"signal",
|
|
65
|
+
"exit",
|
|
66
|
+
"idle"
|
|
67
|
+
],
|
|
68
|
+
"description": "This batch is the last one: the process is leaving deliberately and has already been handed everything the instrumentation was holding. `signal`, a SIGTERM or SIGINT the instrumentation's own handler saw; `exit`, an application that awaited `shutdown()` before calling `process.exit()`; `idle`, an event loop that emptied. Absent means **this sender did not say**, the same reading as `observers` — never that it ended badly: an instrumentation older than this never says it, and a process an exception killed says nothing because it cannot. It is the half of ERR-04 the cloud cannot infer — from outside, every ending looks like batches stopping — and it carries no instant, because the one that matters is when the batch arrived, which the cloud measures."
|
|
59
69
|
}
|
|
60
70
|
},
|
|
61
71
|
$defs: {
|
|
@@ -96,10 +106,55 @@ var aggregates_schema_default = {
|
|
|
96
106
|
},
|
|
97
107
|
"resources": {
|
|
98
108
|
"$ref": "#/$defs/AgentResources",
|
|
99
|
-
"description": "What the instrumentation costs and what it has lost, measured by itself. Absent means **this sender did not say**, the same reading as `observers` and `withholding`. `product.md:239` asks for it by name: «
|
|
109
|
+
"description": "What the instrumentation costs and what it has lost, measured by itself. Absent means **this sender did not say**, the same reading as `observers` and `withholding`. `product.md:239` asks for it by name: «measured internal resources»."
|
|
110
|
+
},
|
|
111
|
+
"errorSources": {
|
|
112
|
+
"$ref": "#/$defs/ErrorSources",
|
|
113
|
+
"description": "Which of the sources of error this process has connected, as of the moment this batch is sealed. A source is on its list when the process has, at that moment, the instrumentation that produces it installed; the application calling `captureException` does not make it one without the hook connected. Absent means **this sender did not say**, the same reading as `observers` and `resources`: an instrumentation older than protocol 0.9.0 says nothing, and the two lists left empty are not the same thing, because they are a statement — the sender says it has connected no source of error."
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"ErrorSources": {
|
|
118
|
+
"title": "ErrorSources",
|
|
119
|
+
"type": "object",
|
|
120
|
+
"additionalProperties": false,
|
|
121
|
+
"required": ["inRequest", "outsideRequest"],
|
|
122
|
+
"properties": {
|
|
123
|
+
"inRequest": {
|
|
124
|
+
"type": "array",
|
|
125
|
+
"uniqueItems": true,
|
|
126
|
+
"items": { "$ref": "#/$defs/InRequestErrorSource" },
|
|
127
|
+
"description": "The sources of the errors this process sees inside a request, by the value of `Operation.kind` they arrive as, among the values `x-error` marks as errors: `error`, an instrumented operation failed; `framework`, the exception reached the framework's error path and was turned into a 5xx; `explicit`, the application handed it over itself. No value appears twice."
|
|
128
|
+
},
|
|
129
|
+
"outsideRequest": {
|
|
130
|
+
"type": "array",
|
|
131
|
+
"uniqueItems": true,
|
|
132
|
+
"items": { "$ref": "#/$defs/OutsideRequestErrorSource" },
|
|
133
|
+
"description": "The sources of the errors this process sees outside any request, by the value of `ProcessException.kind`: `uncaught`, an exception nothing caught; `unhandled-rejection`, a promise rejected with nothing to handle it; `framework`, one that reached a framework's error path; `explicit`, one the application handed over itself. No value appears twice."
|
|
100
134
|
}
|
|
101
135
|
}
|
|
102
136
|
},
|
|
137
|
+
"InRequestErrorSource": {
|
|
138
|
+
"title": "InRequestErrorSource",
|
|
139
|
+
"type": "string",
|
|
140
|
+
"enum": [
|
|
141
|
+
"error",
|
|
142
|
+
"framework",
|
|
143
|
+
"explicit"
|
|
144
|
+
],
|
|
145
|
+
"description": "A value of `Operation.kind` that its `x-error` marks as an error: the ways an error inside a request arrives, and so the sources that can produce one. A query, an outgoing call and a Redis command are things a route ran, never a source of error."
|
|
146
|
+
},
|
|
147
|
+
"OutsideRequestErrorSource": {
|
|
148
|
+
"title": "OutsideRequestErrorSource",
|
|
149
|
+
"type": "string",
|
|
150
|
+
"enum": [
|
|
151
|
+
"uncaught",
|
|
152
|
+
"unhandled-rejection",
|
|
153
|
+
"framework",
|
|
154
|
+
"explicit"
|
|
155
|
+
],
|
|
156
|
+
"description": "A value of `ProcessException.kind`: the ways an error outside any request arrives, and so the sources that can produce one."
|
|
157
|
+
},
|
|
103
158
|
"InstanceInfo": {
|
|
104
159
|
"title": "InstanceInfo",
|
|
105
160
|
"type": "object",
|
|
@@ -557,8 +612,31 @@ var aggregates_schema_default = {
|
|
|
557
612
|
],
|
|
558
613
|
"properties": {
|
|
559
614
|
"kind": {
|
|
560
|
-
"enum": [
|
|
561
|
-
|
|
615
|
+
"enum": [
|
|
616
|
+
"query",
|
|
617
|
+
"error",
|
|
618
|
+
"framework",
|
|
619
|
+
"explicit",
|
|
620
|
+
"call",
|
|
621
|
+
"command"
|
|
622
|
+
],
|
|
623
|
+
"x-since": {
|
|
624
|
+
"query": "0.6.0",
|
|
625
|
+
"error": "0.6.0",
|
|
626
|
+
"framework": "0.9.0",
|
|
627
|
+
"explicit": "0.9.0",
|
|
628
|
+
"call": "0.9.0",
|
|
629
|
+
"command": "0.9.0"
|
|
630
|
+
},
|
|
631
|
+
"x-error": {
|
|
632
|
+
"query": false,
|
|
633
|
+
"error": true,
|
|
634
|
+
"framework": true,
|
|
635
|
+
"explicit": true,
|
|
636
|
+
"call": false,
|
|
637
|
+
"command": false
|
|
638
|
+
},
|
|
639
|
+
"description": "What kind of operation this is. Three are things the route ran: `query`, a query to a database; `call`, an outgoing HTTP call, identified by its method and its host; `command`, a Redis command, identified by its name and its server. The other three are errors, and which one it is says how the instrumentation came to see it: `error`, an instrumented operation failed; `framework`, the exception reached the framework's error path and was turned into a 5xx; `explicit`, the application handed it over itself (ERR-02). `x-since` says which minor first carries each value, which is how a reader tells a kind a sender cannot send from one it did not; `x-error` says which values are errors, which is what a list of errors takes and the rest never reach (ERR-01)."
|
|
562
640
|
},
|
|
563
641
|
"hash": {
|
|
564
642
|
"type": "string",
|
|
@@ -569,7 +647,7 @@ var aggregates_schema_default = {
|
|
|
569
647
|
"text": {
|
|
570
648
|
"type": "string",
|
|
571
649
|
"maxLength": 1024,
|
|
572
|
-
"description": "Normalised text, e.g. `SELECT id FROM products WHERE id =
|
|
650
|
+
"description": "Normalised text, e.g. `SELECT id FROM products WHERE id = ?` for a query, `POST api.stripe.com` for a call and `HGETALL cache:6379` for a command. A call's and a command's are a word and the place it went — the method and the host, the command's name and its server — and never a path, a query string, a key or an argument (invariant 5): the schema refuses one with anything after the place, or a slash, a question mark or a hash in it. Optional: a user who would rather not send it keeps the whole analysis, only without the label, and a sender in minimal mode, or one withholding the name of the dependency, sends none."
|
|
573
651
|
},
|
|
574
652
|
"count": {
|
|
575
653
|
"type": "integer",
|
|
@@ -591,6 +669,7 @@ var aggregates_schema_default = {
|
|
|
591
669
|
"minimum": 0,
|
|
592
670
|
"description": "Only on the `(other)` bucket: how many distinct operations it merges, so a cap does not become a lie by omission."
|
|
593
671
|
},
|
|
672
|
+
"context": { "$ref": "#/$defs/ReportedContext" },
|
|
594
673
|
"class": {
|
|
595
674
|
"enum": [
|
|
596
675
|
"select",
|
|
@@ -599,7 +678,7 @@ var aggregates_schema_default = {
|
|
|
599
678
|
"delete",
|
|
600
679
|
"other"
|
|
601
680
|
],
|
|
602
|
-
"description": "What kind of statement this is, when the sender could not normalise it safely. Its presence is the reason the text is absent: `product.md:104` says a query the normaliser does not understand travels «
|
|
681
|
+
"description": "What kind of statement this is, when the sender could not normalise it safely. Its presence is the reason the text is absent: `product.md:104` says a query the normaliser does not understand travels «only as hash and class», and this is the class. Never sent together with `text` — they are two answers to the same question. Our word and not the service's: it is decided from the first keyword, which is all that can be told without understanding the rest (gh-344)."
|
|
603
682
|
}
|
|
604
683
|
},
|
|
605
684
|
"not": {
|
|
@@ -608,7 +687,12 @@ var aggregates_schema_default = {
|
|
|
608
687
|
"class": {},
|
|
609
688
|
"text": {}
|
|
610
689
|
}
|
|
611
|
-
}
|
|
690
|
+
},
|
|
691
|
+
"if": { "properties": { "kind": { "enum": ["call", "command"] } } },
|
|
692
|
+
"then": { "properties": { "text": {
|
|
693
|
+
"type": "string",
|
|
694
|
+
"pattern": "^[A-Z][A-Z0-9._-]* [^\\s/?#]+$"
|
|
695
|
+
} } }
|
|
612
696
|
},
|
|
613
697
|
"ObserverState": {
|
|
614
698
|
"title": "ObserverState",
|
|
@@ -683,11 +767,22 @@ var aggregates_schema_default = {
|
|
|
683
767
|
"hash",
|
|
684
768
|
"count"
|
|
685
769
|
],
|
|
686
|
-
"description": "Something thrown outside any instrumented operation: an exception nobody caught, or a promise rejected with no `catch`. `product.md:77` asks for «
|
|
770
|
+
"description": "Something thrown outside any instrumented operation: an exception nobody caught, or a promise rejected with no `catch`. `product.md:77` asks for «errors and exceptions», and these are the two the word «excepciones» names. They live on the batch and not in the profile because they have **no route** — they happen outside a request's life, or after it ended — and filing them under a route that is not theirs would be worse than not having them (gh-341, ADR 0102).",
|
|
687
771
|
"properties": {
|
|
688
772
|
"kind": {
|
|
689
|
-
"enum": [
|
|
690
|
-
|
|
773
|
+
"enum": [
|
|
774
|
+
"uncaught",
|
|
775
|
+
"unhandled-rejection",
|
|
776
|
+
"framework",
|
|
777
|
+
"explicit"
|
|
778
|
+
],
|
|
779
|
+
"x-since": {
|
|
780
|
+
"uncaught": "0.7.0",
|
|
781
|
+
"unhandled-rejection": "0.7.0",
|
|
782
|
+
"framework": "0.9.0",
|
|
783
|
+
"explicit": "0.9.0"
|
|
784
|
+
},
|
|
785
|
+
"description": "How the process came to see it. `uncaught` and `unhandled-rejection` are two different failures with two different fixes, and counting them together hides which one is happening. `explicit` is an error the application handed over while no request was being served, and `framework` one that reached a framework's error path outside any observed request — a process that is not opening request contexts at all (ERR-02). `x-since` says which minor first carries each value."
|
|
691
786
|
},
|
|
692
787
|
"hash": {
|
|
693
788
|
"type": "string",
|
|
@@ -703,10 +798,27 @@ var aggregates_schema_default = {
|
|
|
703
798
|
"count": {
|
|
704
799
|
"type": "integer",
|
|
705
800
|
"minimum": 1,
|
|
706
|
-
"description": "How many times this signature happened. An instant per occurrence would be a row per crash of a process that crashes in a loop; what a reader asks is how many."
|
|
707
|
-
}
|
|
801
|
+
"description": "How many times this signature happened since the last delivery this sender heard land. An instant per occurrence would be a row per crash of a process that crashes in a loop; what a reader asks is how many."
|
|
802
|
+
},
|
|
803
|
+
"total": {
|
|
804
|
+
"type": "integer",
|
|
805
|
+
"minimum": 1,
|
|
806
|
+
"description": "Since 0.9.0: how many times this signature has happened in this instance since it started, a running total every delivery of the signature carries again. It is what lets the cloud tell a delivery it already applied from one that happened again (gh-625): `total - count` is what the sender knows was applied, the cloud keeps the largest total it applied per instance and signature, and it counts only what this delivery adds beyond both — so a delivery sent again after its answer was lost, sent twice, or overtaken by a later one adds nothing, and one that never arrived is counted by the next. It never goes down while `instance.id` stays the same, which is why that id is generated once per process. Absent from every sender older than this, and from a signature past the number a sender keeps totals for: the cloud then counts `count` as delivered, and says so."
|
|
807
|
+
},
|
|
808
|
+
"context": { "$ref": "#/$defs/ReportedContext" }
|
|
708
809
|
}
|
|
709
810
|
},
|
|
811
|
+
"ReportedContext": {
|
|
812
|
+
"title": "ReportedContext",
|
|
813
|
+
"type": "object",
|
|
814
|
+
"maxProperties": 8,
|
|
815
|
+
"propertyNames": { "maxLength": 32 },
|
|
816
|
+
"additionalProperties": {
|
|
817
|
+
"type": "string",
|
|
818
|
+
"maxLength": 64
|
|
819
|
+
},
|
|
820
|
+
"description": "The structural context an application attached to an error it reported (ERR-02, since 0.9.0). Flat and small on purpose: what travels is the shape of the failure, never its contents (invariant 5). Every value has been through the same sanitising as an error message before it got here, so anything that looked like a value is already a `?`, and there is nothing per user in it (IMP-01). It is the context of the **first** occurrence of the signature in this window, because what travels is counted per signature and not per occurrence. Absent is the normal case: an error nobody attached a context to carries none."
|
|
821
|
+
},
|
|
710
822
|
"LocalTrigger": {
|
|
711
823
|
"type": "object",
|
|
712
824
|
"title": "LocalTrigger",
|
|
@@ -767,6 +879,11 @@ var aggregates_schema_default = {
|
|
|
767
879
|
"minimum": 0,
|
|
768
880
|
"description": "Errors inside the instrumentation itself, since the previous batch. It disables itself after enough of them, and a sender that has gone quiet with these climbing is not a quiet service."
|
|
769
881
|
},
|
|
882
|
+
"droppedExceptions": {
|
|
883
|
+
"type": "integer",
|
|
884
|
+
"minimum": 0,
|
|
885
|
+
"description": "Exception events the instrumentation discarded because they did not fit in its caps of distinct signatures — the register's window, and the sender's accumulation while batches do not land — since the previous batch. An occurrence, never a signature: the sender cannot name what it did not admit without remembering it, and remembering is the growth the cap exists to stop. This is what happened and is gone, the same kind of number as droppedBatches, for the errors a process throws outside any request: a batch that says 32 and was handed 35 must not leave the other 3 to be read as errors that did not happen (COB-01, invariant 14)."
|
|
886
|
+
},
|
|
770
887
|
"bufferBytes": {
|
|
771
888
|
"type": "integer",
|
|
772
889
|
"minimum": 0,
|
|
@@ -784,7 +901,7 @@ var aggregates_schema_default = {
|
|
|
784
901
|
"fine",
|
|
785
902
|
"profile"
|
|
786
903
|
],
|
|
787
|
-
"description": "What the instrumentation has given up because it was costing too much, cheapest loss first. `product.md:241` asks for both halves —«
|
|
904
|
+
"description": "What the instrumentation has given up because it was costing too much, cheapest loss first. `product.md:241` asks for both halves —«it throttles itself» and «records it as a loss of coverage»— and this is the second. An enum and not free text: it is ours, and it has to be comparable between senders."
|
|
788
905
|
},
|
|
789
906
|
"shedReason": {
|
|
790
907
|
"type": "string",
|
|
@@ -801,7 +918,7 @@ var capture_evidence_schema_default = {
|
|
|
801
918
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
802
919
|
$id: "https://downtrace.io/schema/v0/capture-evidence.schema.json",
|
|
803
920
|
title: "CaptureEvidence",
|
|
804
|
-
description: "What an instrumentation sends back for a capture the cloud asked for (POST /v0/captures/{id}/evidence). It is the contents of the black box
|
|
921
|
+
description: "What an instrumentation sends back for a capture the cloud asked for (POST /v0/captures/{id}/evidence). It is the contents of the black box, frozen: the fine register's requests with their operations, in order, with starts and ends, and the coarse summary of the previous minutes, second by second. Hashes only — the text of a query never travels here (invariant 5). Its own path and not the aggregates batch, because a batch is what goes out every ten seconds and this is a rare event several orders of magnitude larger.",
|
|
805
922
|
"x-evidence-path": "/v0/captures/{id}/evidence",
|
|
806
923
|
type: "object",
|
|
807
924
|
additionalProperties: false,
|
|
@@ -824,7 +941,9 @@ var capture_evidence_schema_default = {
|
|
|
824
941
|
"0.4.0",
|
|
825
942
|
"0.5.0",
|
|
826
943
|
"0.6.0",
|
|
827
|
-
"0.7.0"
|
|
944
|
+
"0.7.0",
|
|
945
|
+
"0.8.0",
|
|
946
|
+
"0.9.0"
|
|
828
947
|
]
|
|
829
948
|
},
|
|
830
949
|
"instance": {
|
|
@@ -865,7 +984,8 @@ var capture_evidence_schema_default = {
|
|
|
865
984
|
"items": { "$ref": "#/$defs/CapturedRequest" },
|
|
866
985
|
"description": "The requests observed, oldest first. An empty array is a real answer and the commonest one: a route nobody called during the window. It means the capture is `empty`, and `product.md:192` is explicit that this must never read as an invitation to ask again unchanged."
|
|
867
986
|
},
|
|
868
|
-
"reference": { "$ref": "#/$defs/Reference" }
|
|
987
|
+
"reference": { "$ref": "#/$defs/Reference" },
|
|
988
|
+
"coarse": { "$ref": "#/$defs/CoarseSummary" }
|
|
869
989
|
},
|
|
870
990
|
$defs: {
|
|
871
991
|
"Coverage": {
|
|
@@ -899,6 +1019,31 @@ var capture_evidence_schema_default = {
|
|
|
899
1019
|
"type": "integer",
|
|
900
1020
|
"minimum": 0,
|
|
901
1021
|
"description": "Requests that ran more operations than the register keeps per request. Their count is real and their list is not complete."
|
|
1022
|
+
},
|
|
1023
|
+
"shed": { "$ref": "#/$defs/Shed" },
|
|
1024
|
+
"dependenciesTruncated": {
|
|
1025
|
+
"type": "integer",
|
|
1026
|
+
"minimum": 0,
|
|
1027
|
+
"description": "Requests whose list of dependencies was truncated. Their count is real and their list is not complete."
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
"Shed": {
|
|
1032
|
+
"type": "object",
|
|
1033
|
+
"title": "Shed",
|
|
1034
|
+
"additionalProperties": false,
|
|
1035
|
+
"required": ["ms", "reason"],
|
|
1036
|
+
"description": "The time inside [startedAt, endedAt] when the instrumentation was not writing the fine detail — neither the register's ring nor the reserve — because it was shedding it. Present when some part of the window was shed, absent when there was no shedding, when the shedding came from configuration (`DOWNTRACE_SHED`, the benchmark) and so the instrumentation never decided it, or when the instrumentation predates the field: a sender older than the field sends nothing, and the cloud stores the absence as such (ADR 0008). The capture is still taken, not refused: the window can end before the shedding does, the coarse register keeps measuring, and the protocol has no way for an instance to refuse an order — saying it in the evidence is what CAP-01 asks for.",
|
|
1037
|
+
"properties": {
|
|
1038
|
+
"ms": {
|
|
1039
|
+
"type": "integer",
|
|
1040
|
+
"minimum": 1,
|
|
1041
|
+
"description": "How long the shedding lasted, in whole milliseconds, rounded up: one millisecond of shedding is still shedding, and the field says so rather than rounding it into silence."
|
|
1042
|
+
},
|
|
1043
|
+
"reason": {
|
|
1044
|
+
"type": "string",
|
|
1045
|
+
"enum": ["latency", "memory"],
|
|
1046
|
+
"description": "The last reason in force inside the window, the same enum as `agent.resources.shedReason` in the batch: why it gave ground. One value and not a list: a window that shed for two reasons is read as the one in force when it ended, which is the one still changing what arrives."
|
|
902
1047
|
}
|
|
903
1048
|
}
|
|
904
1049
|
},
|
|
@@ -938,6 +1083,11 @@ var capture_evidence_schema_default = {
|
|
|
938
1083
|
"type": "number",
|
|
939
1084
|
"minimum": 0
|
|
940
1085
|
},
|
|
1086
|
+
"poolWaitMs": {
|
|
1087
|
+
"type": "number",
|
|
1088
|
+
"minimum": 0,
|
|
1089
|
+
"description": "How long this request waited for a connection from a pool before it could talk to the dependency at all. **Absent means this request asked no pool**, which is not a wait of zero: zero is a request that asked and was served at once. The pool-saturation trigger compares wait per request, so a route that never queues must not read as one that queues for nothing (invariant 14)."
|
|
1090
|
+
},
|
|
941
1091
|
"operations": {
|
|
942
1092
|
"type": "array",
|
|
943
1093
|
"maxItems": 256,
|
|
@@ -951,6 +1101,10 @@ var capture_evidence_schema_default = {
|
|
|
951
1101
|
"detailLost": {
|
|
952
1102
|
"type": "boolean",
|
|
953
1103
|
"description": "This request's operations were overwritten before they were read. Absent means false. Its timing is still true."
|
|
1104
|
+
},
|
|
1105
|
+
"dependenciesTruncated": {
|
|
1106
|
+
"type": "boolean",
|
|
1107
|
+
"description": "This request's list of dependencies did not fit and was truncated to what the register keeps per request. A dependency not in the list is not proven not to have been used (invariant 14). Absent means false."
|
|
954
1108
|
}
|
|
955
1109
|
}
|
|
956
1110
|
},
|
|
@@ -965,6 +1119,17 @@ var capture_evidence_schema_default = {
|
|
|
965
1119
|
],
|
|
966
1120
|
"description": "One operation inside a request. Starts and ends and not durations, because the whole reason for capturing detail is order and overlap, which a duration cannot express (ADR 0068).",
|
|
967
1121
|
"properties": {
|
|
1122
|
+
"kind": {
|
|
1123
|
+
"enum": [
|
|
1124
|
+
"query",
|
|
1125
|
+
"error",
|
|
1126
|
+
"framework",
|
|
1127
|
+
"explicit",
|
|
1128
|
+
"call",
|
|
1129
|
+
"command"
|
|
1130
|
+
],
|
|
1131
|
+
"description": "What kind of operation this is, in the words of the batch's `Operation.kind`, whose enum this borrows: a query, an outgoing call or a Redis command the request ran, or one of the kinds of error recorded in it beside what failed. Since 0.9.0. **Absent means the sender did not say**, never a query: an instrumentation older than the field wrote every operation it recorded here without one, the error signatures beside a failed query among them, and a reader that took them for queries would be naming what it was not told."
|
|
1132
|
+
},
|
|
968
1133
|
"hash": {
|
|
969
1134
|
"type": "string",
|
|
970
1135
|
"minLength": 1,
|
|
@@ -991,7 +1156,7 @@ var capture_evidence_schema_default = {
|
|
|
991
1156
|
"population",
|
|
992
1157
|
"samples"
|
|
993
1158
|
],
|
|
994
|
-
"description": "A few requests of each endpoint kept as something to compare the captured ones against. `product.md:100`: «
|
|
1159
|
+
"description": "A few requests of each endpoint kept as something to compare the captured ones against. `product.md:100`: «Comparing degraded requests against reference requests requires keeping both; the fine detail of an hour ago no longer exists». Being earlier does not certify health, which is why every field here is about how they were chosen and none of them says «healthy».",
|
|
995
1160
|
"properties": {
|
|
996
1161
|
"selection": {
|
|
997
1162
|
"type": "string",
|
|
@@ -1006,7 +1171,7 @@ var capture_evidence_schema_default = {
|
|
|
1006
1171
|
"routesDropped": {
|
|
1007
1172
|
"type": "integer",
|
|
1008
1173
|
"minimum": 0,
|
|
1009
|
-
"description": "
|
|
1174
|
+
"description": "Routes the register had no row for, folded into one shared row, counted once each. At the register's cap for how many it may remember it is a lower bound: a bounded memory does not count distinct names past its bound, and the volume of what was folded is what the shared row keeps."
|
|
1010
1175
|
},
|
|
1011
1176
|
"renewalPaused": {
|
|
1012
1177
|
"type": "boolean",
|
|
@@ -1019,6 +1184,128 @@ var capture_evidence_schema_default = {
|
|
|
1019
1184
|
"description": "The kept requests, in the same shape as the captured ones. An empty array is a real answer: a process that has just started has nothing to compare with yet."
|
|
1020
1185
|
}
|
|
1021
1186
|
}
|
|
1187
|
+
},
|
|
1188
|
+
"CoarseSummary": {
|
|
1189
|
+
"type": "object",
|
|
1190
|
+
"title": "CoarseSummary",
|
|
1191
|
+
"additionalProperties": false,
|
|
1192
|
+
"required": [
|
|
1193
|
+
"windowSeconds",
|
|
1194
|
+
"routes",
|
|
1195
|
+
"eventLoop",
|
|
1196
|
+
"routesDropped"
|
|
1197
|
+
],
|
|
1198
|
+
"description": "The coarse half of the black box, frozen: the last few minutes, second by second, so a reader can see how something detected late began (`product.md:94`). Sums and a maximum, never percentiles: the interval aggregate answers the percentile question with exactness, and this answers the shape of the change (`product.md:122`). What an older instrumentation did not send is absent from the evidence, and stored as absent.",
|
|
1199
|
+
"properties": {
|
|
1200
|
+
"windowSeconds": {
|
|
1201
|
+
"type": "integer",
|
|
1202
|
+
"minimum": 1,
|
|
1203
|
+
"description": "How many seconds the summary covers: the length of the register's window, and the most any of the lists below may hold."
|
|
1204
|
+
},
|
|
1205
|
+
"routesDropped": {
|
|
1206
|
+
"type": "integer",
|
|
1207
|
+
"minimum": 0,
|
|
1208
|
+
"description": "Routes the register had no row for, folded into one shared row, counted once each. At the register's cap for how many it may remember it is a lower bound: a bounded memory does not count distinct names past its bound, and the volume of what was folded is what the shared row keeps."
|
|
1209
|
+
},
|
|
1210
|
+
"routes": {
|
|
1211
|
+
"type": "array",
|
|
1212
|
+
"maxItems": 129,
|
|
1213
|
+
"items": { "$ref": "#/$defs/CoarseRoute" },
|
|
1214
|
+
"description": "One entry per route with a row of its own, the last being the shared row for what did not fit, when there is one. A route's series starts at the first second this process saw the route: before it, nobody was watching this route, and inventing silence there would say the route was quiet when the truth is that it was unwatched (invariant 14)."
|
|
1215
|
+
},
|
|
1216
|
+
"eventLoop": {
|
|
1217
|
+
"type": "array",
|
|
1218
|
+
"maxItems": 300,
|
|
1219
|
+
"items": { "$ref": "#/$defs/CoarseEventLoopSecond" },
|
|
1220
|
+
"description": "The process's event loop delay, second by second, in its own series and not attributed to any route: it is the process's, and attributing it to a route would be inventing a measurement. Only the seconds somebody sampled are here: a second with no reading says nothing about the loop, and a zero would say it was idle. An empty array is a real answer: the process had no event loop to sample, or nobody did."
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
"CoarseRoute": {
|
|
1225
|
+
"type": "object",
|
|
1226
|
+
"title": "CoarseRoute",
|
|
1227
|
+
"additionalProperties": false,
|
|
1228
|
+
"required": [
|
|
1229
|
+
"method",
|
|
1230
|
+
"route",
|
|
1231
|
+
"seconds"
|
|
1232
|
+
],
|
|
1233
|
+
"properties": {
|
|
1234
|
+
"method": {
|
|
1235
|
+
"type": "string",
|
|
1236
|
+
"maxLength": 16
|
|
1237
|
+
},
|
|
1238
|
+
"route": {
|
|
1239
|
+
"type": "string",
|
|
1240
|
+
"maxLength": 512,
|
|
1241
|
+
"description": "The route template, never the path with its values in it (invariant 5)."
|
|
1242
|
+
},
|
|
1243
|
+
"seconds": {
|
|
1244
|
+
"type": "array",
|
|
1245
|
+
"maxItems": 300,
|
|
1246
|
+
"items": { "$ref": "#/$defs/CoarseSecond" }
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1250
|
+
"CoarseSecond": {
|
|
1251
|
+
"type": "object",
|
|
1252
|
+
"title": "CoarseSecond",
|
|
1253
|
+
"additionalProperties": false,
|
|
1254
|
+
"required": [
|
|
1255
|
+
"second",
|
|
1256
|
+
"requests",
|
|
1257
|
+
"errors",
|
|
1258
|
+
"latencySumMs",
|
|
1259
|
+
"latencyMaxMs",
|
|
1260
|
+
"calls"
|
|
1261
|
+
],
|
|
1262
|
+
"description": "One second of one route. Sums and a maximum, never a histogram: a histogram per second per route would multiply the memory by its buckets to answer a question the interval already answers.",
|
|
1263
|
+
"properties": {
|
|
1264
|
+
"second": {
|
|
1265
|
+
"type": "integer",
|
|
1266
|
+
"description": "The Unix second this row covers, an integer instant like the ones a batch writes: a reader aligns it with the dates the evidence writes by flooring them."
|
|
1267
|
+
},
|
|
1268
|
+
"requests": {
|
|
1269
|
+
"type": "integer",
|
|
1270
|
+
"minimum": 0,
|
|
1271
|
+
"description": "Requests this route answered in this second. Zero is a real answer: a watched second with no traffic, and not an absence."
|
|
1272
|
+
},
|
|
1273
|
+
"errors": {
|
|
1274
|
+
"type": "integer",
|
|
1275
|
+
"minimum": 0,
|
|
1276
|
+
"description": "Requests that answered 5xx."
|
|
1277
|
+
},
|
|
1278
|
+
"latencySumMs": {
|
|
1279
|
+
"type": "number",
|
|
1280
|
+
"minimum": 0
|
|
1281
|
+
},
|
|
1282
|
+
"latencyMaxMs": {
|
|
1283
|
+
"type": "number",
|
|
1284
|
+
"minimum": 0
|
|
1285
|
+
},
|
|
1286
|
+
"calls": {
|
|
1287
|
+
"type": "integer",
|
|
1288
|
+
"minimum": 0,
|
|
1289
|
+
"description": "Operations the requests of this second ran."
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
"CoarseEventLoopSecond": {
|
|
1294
|
+
"type": "object",
|
|
1295
|
+
"title": "CoarseEventLoopSecond",
|
|
1296
|
+
"additionalProperties": false,
|
|
1297
|
+
"required": ["second", "maxDelayMs"],
|
|
1298
|
+
"properties": {
|
|
1299
|
+
"second": {
|
|
1300
|
+
"type": "integer",
|
|
1301
|
+
"description": "The Unix second this reading covers."
|
|
1302
|
+
},
|
|
1303
|
+
"maxDelayMs": {
|
|
1304
|
+
"type": "number",
|
|
1305
|
+
"minimum": 0,
|
|
1306
|
+
"description": "The worst delay sampled in this second: a second that stalled once stalled, and averaging it away is how a stall becomes invisible."
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1022
1309
|
}
|
|
1023
1310
|
}
|
|
1024
1311
|
};
|
|
@@ -1169,6 +1456,19 @@ function captureEvidencePath(id) {
|
|
|
1169
1456
|
return CAPTURE_EVIDENCE_PATH.replace("{id}", encodeURIComponent(id));
|
|
1170
1457
|
}
|
|
1171
1458
|
//#endregion
|
|
1459
|
+
//#region src/generated/caps.ts
|
|
1460
|
+
/** The most dependencies one endpoint may carry in a batch. */
|
|
1461
|
+
const DEPENDENCIES_MAX_ITEMS_V0 = 64;
|
|
1462
|
+
/** The most requests a capture's evidence may carry: the ring is sized as this minus the reserves' room (gh-901). */
|
|
1463
|
+
const EVIDENCE_REQUESTS_MAX_ITEMS_V0 = 4096;
|
|
1464
|
+
/** The dependency kinds the schema names, in the order it names them. */
|
|
1465
|
+
const DEPENDENCY_KINDS_V0 = [
|
|
1466
|
+
"postgres",
|
|
1467
|
+
"mysql",
|
|
1468
|
+
"redis",
|
|
1469
|
+
"http"
|
|
1470
|
+
];
|
|
1471
|
+
//#endregion
|
|
1172
1472
|
//#region src/generated/versions.ts
|
|
1173
1473
|
/** Every published minor of v0, oldest first. The cloud never stops accepting one it published (ADR 0008). */
|
|
1174
1474
|
const ACCEPTED_PROTOCOL_VERSIONS_V0 = [
|
|
@@ -1178,10 +1478,12 @@ const ACCEPTED_PROTOCOL_VERSIONS_V0 = [
|
|
|
1178
1478
|
"0.4.0",
|
|
1179
1479
|
"0.5.0",
|
|
1180
1480
|
"0.6.0",
|
|
1181
|
-
"0.7.0"
|
|
1481
|
+
"0.7.0",
|
|
1482
|
+
"0.8.0",
|
|
1483
|
+
"0.9.0"
|
|
1182
1484
|
];
|
|
1183
1485
|
/** The newest published minor: what an up-to-date agent stamps on every batch. */
|
|
1184
|
-
const PROTOCOL_VERSION = "0.
|
|
1486
|
+
const PROTOCOL_VERSION = "0.9.0";
|
|
1185
1487
|
//#endregion
|
|
1186
1488
|
//#region src/index.ts
|
|
1187
1489
|
/** The JSON Schema (draft 2020-12) for AggregatesBatch, for validators on either side. */
|
|
@@ -1216,4 +1518,4 @@ function callsPerRequestBucket(calls) {
|
|
|
1216
1518
|
return n;
|
|
1217
1519
|
}
|
|
1218
1520
|
//#endregion
|
|
1219
|
-
export { ACCEPTED_PROTOCOL_VERSIONS_V0, AGGREGATES_PATH, AGGREGATES_SCHEMA_V0, CALLS_PER_REQUEST_BOUNDARIES_V0, CALLS_PER_REQUEST_BUCKETS_V0, CAPTURE_EVIDENCE_PATH, CAPTURE_EVIDENCE_SCHEMA_V0, INGEST_RESPONSE_SCHEMA_V0, LATENCY_BOUNDARIES_V0, LATENCY_BUCKETS_V0, PROTOCOL_VERSION, callsPerRequestBucket, captureEvidencePath, latencyBucket };
|
|
1521
|
+
export { ACCEPTED_PROTOCOL_VERSIONS_V0, AGGREGATES_PATH, AGGREGATES_SCHEMA_V0, CALLS_PER_REQUEST_BOUNDARIES_V0, CALLS_PER_REQUEST_BUCKETS_V0, CAPTURE_EVIDENCE_PATH, CAPTURE_EVIDENCE_SCHEMA_V0, DEPENDENCIES_MAX_ITEMS_V0, DEPENDENCY_KINDS_V0, EVIDENCE_REQUESTS_MAX_ITEMS_V0, INGEST_RESPONSE_SCHEMA_V0, LATENCY_BOUNDARIES_V0, LATENCY_BUCKETS_V0, PROTOCOL_VERSION, callsPerRequestBucket, captureEvidencePath, latencyBucket };
|