@comprehend/telemetry-node 0.1.4 → 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.
@@ -1,15 +1,33 @@
1
- import { ObservationInputMessage } from './wire-protocol';
1
+ import { AttributeType, CustomMetricSpecification, ObservationInputMessage, InitAck } from './wire-protocol';
2
2
  export declare class WebSocketConnection {
3
3
  private readonly organization;
4
4
  private readonly token;
5
5
  private readonly logger?;
6
+ private readonly onAuthorized?;
7
+ private readonly onCustomMetricChange?;
6
8
  private readonly unacknowledgedObserved;
7
- private readonly unacknowledgedObservations;
9
+ private readonly seqQueues;
10
+ private readonly observationsQueue;
11
+ private readonly timeseriesQueue;
12
+ private readonly cumulativeQueue;
13
+ private readonly traceSpansQueue;
14
+ private readonly dbQueryQueue;
15
+ private readonly contextQueue;
8
16
  private socket;
9
17
  private reconnectDelay;
10
18
  private shouldReconnect;
11
19
  private authorized;
12
- constructor(organization: string, token: string, logger?: (message: string) => void);
20
+ private processContext;
21
+ private ingestionId;
22
+ private _seq;
23
+ constructor(options: {
24
+ organization: string;
25
+ token: string;
26
+ logger?: (message: string) => void;
27
+ onAuthorized?: (ack: InitAck) => void;
28
+ onCustomMetricChange?: (specs: CustomMetricSpecification[]) => void;
29
+ });
30
+ nextSeq(): number;
13
31
  private log;
14
32
  private connect;
15
33
  private onOpen;
@@ -17,6 +35,8 @@ export declare class WebSocketConnection {
17
35
  private onClose;
18
36
  private onError;
19
37
  private sendRaw;
38
+ private sendContextStart;
39
+ setProcessContext(serviceEntityHash: string, resources: Record<string, AttributeType>): void;
20
40
  sendMessage(message: ObservationInputMessage): void;
21
41
  close(): void;
22
42
  }
@@ -5,20 +5,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WebSocketConnection = void 0;
7
7
  const ws_1 = __importDefault(require("ws"));
8
- const INGESTION_ENDPOINT = 'wss://ingestion.comprehend.dev';
8
+ const crypto_1 = require("crypto");
9
+ const util_1 = require("./util");
10
+ const INGESTION_ENDPOINT = process.env.COMPREHEND_INGESTION_ENDPOINT || 'wss://ingestion.comprehend.dev';
11
+ /** A queue of unacknowledged messages keyed by sequence number, with an associated ack type. */
12
+ class SeqQueue {
13
+ constructor(ackType) {
14
+ this.pending = new Map();
15
+ this.ackType = ackType;
16
+ }
17
+ add(message) {
18
+ this.pending.set(message.seq, message);
19
+ }
20
+ ack(seq) {
21
+ this.pending.delete(seq);
22
+ }
23
+ values() {
24
+ return this.pending.values();
25
+ }
26
+ }
9
27
  class WebSocketConnection {
10
- constructor(organization, token, logger) {
28
+ constructor(options) {
11
29
  this.unacknowledgedObserved = new Map();
12
- this.unacknowledgedObservations = new Map();
30
+ this.observationsQueue = new SeqQueue('ack-observations');
31
+ this.timeseriesQueue = new SeqQueue('ack-timeseries');
32
+ this.cumulativeQueue = new SeqQueue('ack-cumulative');
33
+ this.traceSpansQueue = new SeqQueue('ack-tracespans');
34
+ this.dbQueryQueue = new SeqQueue('ack-db-query');
35
+ this.contextQueue = new SeqQueue('ack-context');
13
36
  this.socket = null;
14
37
  this.reconnectDelay = 1000;
15
38
  this.shouldReconnect = true;
16
39
  this.authorized = false;
17
- this.organization = organization;
18
- this.token = token;
19
- this.logger = logger;
40
+ this.processContext = null;
41
+ this.ingestionId = (0, crypto_1.randomUUID)();
42
+ this._seq = 1;
43
+ this.organization = options.organization;
44
+ this.token = options.token;
45
+ this.logger = options.logger;
46
+ this.onAuthorized = options.onAuthorized;
47
+ this.onCustomMetricChange = options.onCustomMetricChange;
48
+ // Build a lookup from ack type to queue for dispatch in onMessage
49
+ this.seqQueues = {};
50
+ for (const q of [this.observationsQueue, this.timeseriesQueue, this.cumulativeQueue,
51
+ this.traceSpansQueue, this.dbQueryQueue, this.contextQueue]) {
52
+ this.seqQueues[q.ackType] = q;
53
+ }
20
54
  this.connect();
21
55
  }
56
+ nextSeq() {
57
+ return this._seq++;
58
+ }
22
59
  log(message) {
23
60
  if (this.logger) {
24
61
  this.logger(message);
@@ -35,9 +72,10 @@ class WebSocketConnection {
35
72
  }
36
73
  onOpen() {
37
74
  this.log('WebSocket connected. Sending init/auth message.');
75
+ this.ingestionId = (0, crypto_1.randomUUID)();
38
76
  const init = {
39
77
  event: 'init',
40
- protocolVersion: 1,
78
+ protocolVersion: 2,
41
79
  token: this.token,
42
80
  };
43
81
  this.sendRaw(init);
@@ -48,18 +86,40 @@ class WebSocketConnection {
48
86
  if (msg.type === 'ack-authorized') {
49
87
  this.authorized = true;
50
88
  this.log('Authorization acknowledged by server.');
89
+ if (this.onAuthorized) {
90
+ this.onAuthorized(msg);
91
+ }
92
+ // Send context first if we have one
93
+ if (this.processContext) {
94
+ this.sendContextStart();
95
+ }
96
+ // Replay entities and interactions
51
97
  for (const message of this.unacknowledgedObserved.values()) {
52
98
  this.sendRaw(message);
53
99
  }
54
- for (const message of this.unacknowledgedObservations.values()) {
55
- this.sendRaw(message);
100
+ // Replay all seq-based queues
101
+ for (const q of Object.values(this.seqQueues)) {
102
+ if (q === this.contextQueue)
103
+ continue; // already sent above
104
+ for (const message of q.values()) {
105
+ this.sendRaw(message);
106
+ }
56
107
  }
57
108
  }
58
109
  else if (msg.type === 'ack-observed') {
59
110
  this.unacknowledgedObserved.delete(msg.hash);
60
111
  }
61
- else if (msg.type === 'ack-observations') {
62
- this.unacknowledgedObservations.delete(msg.seq);
112
+ else if (msg.type === 'custom-metric-change') {
113
+ if (this.onCustomMetricChange) {
114
+ this.onCustomMetricChange(msg.customMetrics);
115
+ }
116
+ }
117
+ else if ('seq' in msg) {
118
+ // Dispatch seq-based acks to the appropriate queue
119
+ const queue = this.seqQueues[msg.type];
120
+ if (queue) {
121
+ queue.ack(msg.seq);
122
+ }
63
123
  }
64
124
  }
65
125
  catch (e) {
@@ -81,12 +141,46 @@ class WebSocketConnection {
81
141
  this.socket.send(JSON.stringify(message));
82
142
  }
83
143
  }
144
+ sendContextStart() {
145
+ if (!this.processContext)
146
+ return;
147
+ const seq = this.nextSeq();
148
+ const contextMsg = {
149
+ event: 'context-start',
150
+ seq,
151
+ timestamp: (0, util_1.hrTimeNow)(),
152
+ ingestionId: this.ingestionId,
153
+ type: 'process',
154
+ serviceEntityHash: this.processContext.serviceEntityHash,
155
+ resources: this.processContext.resources,
156
+ };
157
+ this.contextQueue.add(contextMsg);
158
+ this.sendRaw(contextMsg);
159
+ }
160
+ setProcessContext(serviceEntityHash, resources) {
161
+ this.processContext = { serviceEntityHash, resources };
162
+ if (this.authorized) {
163
+ this.sendContextStart();
164
+ }
165
+ }
84
166
  sendMessage(message) {
85
167
  if (message.event === 'new-entity' || message.event === 'new-interaction') {
86
168
  this.unacknowledgedObserved.set(message.hash, message);
87
169
  }
88
170
  else if (message.event === 'observations') {
89
- this.unacknowledgedObservations.set(message.seq, message);
171
+ this.observationsQueue.add(message);
172
+ }
173
+ else if (message.event === 'timeseries') {
174
+ this.timeseriesQueue.add(message);
175
+ }
176
+ else if (message.event === 'cumulative') {
177
+ this.cumulativeQueue.add(message);
178
+ }
179
+ else if (message.event === 'tracespans') {
180
+ this.traceSpansQueue.add(message);
181
+ }
182
+ else if (message.event === 'db-query') {
183
+ this.dbQueryQueue.add(message);
90
184
  }
91
185
  if (this.authorized) {
92
186
  this.sendRaw(message);