@agoric/telemetry 0.6.3-other-dev-1f26562.0 → 0.6.3-other-dev-3eb1a1d.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/CHANGELOG.md +0 -9
- package/package.json +27 -19
- package/scripts/ingest.sh +2 -2
- package/src/context-aware-slog-file.js +42 -0
- package/src/context-aware-slog.js +383 -0
- package/src/flight-recorder.js +152 -88
- package/src/frcat-entrypoint.js +13 -9
- package/src/index.js +9 -7
- package/src/ingest-slog-entrypoint.js +46 -15
- package/src/make-slog-sender.js +3 -3
- package/src/otel-and-flight-recorder.js +4 -1
- package/src/otel-context-aware-slog.js +131 -0
- package/src/slog-file.js +1 -1
- package/src/slog-sender-pipe-entrypoint.js +12 -7
- package/src/slog-sender-pipe.js +3 -4
- package/src/slog-to-otel.js +26 -11
- package/test/flight-recorder.test.js +83 -0
- package/test/prepare-test-env-ava.js +0 -2
- package/{jsconfig.json → tsconfig.json} +1 -0
- package/test/test-flight-recorder.js +0 -53
- /package/test/{test-import.js → import.test.js} +0 -0
package/src/flight-recorder.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/* eslint-env node */
|
|
1
3
|
/// <reference types="ses" />
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// pointers, with some sequencing to make sure the record gets written before
|
|
8
|
-
// the pointer is updated. Then, no mattter how abruptly the process is
|
|
9
|
-
// terminated, as long as the host computer itself is still running, the on-disk
|
|
10
|
-
// file would contain the most recent state, and anybody who reads the file will
|
|
11
|
-
// get the most recent state. The host kernel (linux) is under no obligation to
|
|
12
|
-
// flush it to disk any particular time, but knows when reads happen, so there's
|
|
13
|
-
// no coherency problem, and the speed is unaffected by disk write speeds.
|
|
14
|
-
|
|
15
|
-
import BufferFromFile from 'bufferfromfile';
|
|
16
|
-
import { promises as fsPromises } from 'fs';
|
|
17
|
-
import path from 'path';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import fsp from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { Fail } from '@endo/errors';
|
|
18
9
|
import { serializeSlogObj } from './serialize-slog-obj.js';
|
|
19
10
|
|
|
20
|
-
const { Fail } = assert;
|
|
21
|
-
|
|
22
11
|
export const DEFAULT_CBUF_SIZE = 100 * 1024 * 1024;
|
|
23
12
|
export const DEFAULT_CBUF_FILE = 'flight-recorder.bin';
|
|
24
13
|
export const SLOG_MAGIC = 0x41472d534c4f4721n; // 'AG-SLOG!'
|
|
@@ -31,12 +20,16 @@ const I_ARENA_START = 4 * BigUint64Array.BYTES_PER_ELEMENT;
|
|
|
31
20
|
|
|
32
21
|
const RECORD_HEADER_SIZE = BigUint64Array.BYTES_PER_ELEMENT;
|
|
33
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Initializes a circular buffer with the given size, creating the buffer file if it doesn't exist or is not large enough.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} bufferFile - the file path for the circular buffer
|
|
27
|
+
* @param {number} circularBufferSize - the size of the circular buffer
|
|
28
|
+
* @returns {Promise<bigint>} the size of the initialized circular buffer
|
|
29
|
+
*/
|
|
34
30
|
const initializeCircularBuffer = async (bufferFile, circularBufferSize) => {
|
|
35
|
-
if (!circularBufferSize) {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
31
|
// If the file doesn't exist, or is not large enough, create it.
|
|
39
|
-
const stbuf = await
|
|
32
|
+
const stbuf = await fsp.stat(bufferFile).catch(e => {
|
|
40
33
|
if (e.code === 'ENOENT') {
|
|
41
34
|
return undefined;
|
|
42
35
|
}
|
|
@@ -57,8 +50,8 @@ const initializeCircularBuffer = async (bufferFile, circularBufferSize) => {
|
|
|
57
50
|
header.setBigUint64(I_CIRC_START, 0n);
|
|
58
51
|
header.setBigUint64(I_CIRC_END, 0n);
|
|
59
52
|
|
|
60
|
-
await
|
|
61
|
-
await
|
|
53
|
+
await fsp.mkdir(path.dirname(bufferFile), { recursive: true });
|
|
54
|
+
await fsp.writeFile(bufferFile, headerBuf);
|
|
62
55
|
|
|
63
56
|
if (stbuf && stbuf.size >= circularBufferSize) {
|
|
64
57
|
// File is big enough.
|
|
@@ -66,50 +59,20 @@ const initializeCircularBuffer = async (bufferFile, circularBufferSize) => {
|
|
|
66
59
|
}
|
|
67
60
|
|
|
68
61
|
// Increase the file size.
|
|
69
|
-
await
|
|
62
|
+
await fsp.truncate(bufferFile, circularBufferSize);
|
|
70
63
|
return arenaSize;
|
|
71
64
|
};
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
circularBufferSize = DEFAULT_CBUF_SIZE,
|
|
75
|
-
stateDir = '/tmp',
|
|
76
|
-
circularBufferFilename,
|
|
77
|
-
}) => {
|
|
78
|
-
const filename = circularBufferFilename || `${stateDir}/${DEFAULT_CBUF_FILE}`;
|
|
79
|
-
// console.log({ circularBufferFilename, filename });
|
|
66
|
+
/** @typedef {Awaited<ReturnType<typeof makeSimpleCircularBuffer>>} CircularBuffer */
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*/
|
|
90
|
-
const fileBuf = BufferFromFile(filename).Uint8Array();
|
|
91
|
-
const header = new DataView(fileBuf.buffer, 0, I_ARENA_START);
|
|
92
|
-
|
|
93
|
-
// Detect the arena size from the header, if not initialized.
|
|
94
|
-
const hdrArenaSize = header.getBigUint64(I_ARENA_SIZE);
|
|
95
|
-
const arenaSize = newArenaSize || hdrArenaSize;
|
|
96
|
-
|
|
97
|
-
const hdrMagic = header.getBigUint64(I_MAGIC);
|
|
98
|
-
SLOG_MAGIC === hdrMagic ||
|
|
99
|
-
Fail`${filename} is not a slog buffer; wanted magic ${SLOG_MAGIC}, got ${hdrMagic}`;
|
|
100
|
-
arenaSize === hdrArenaSize ||
|
|
101
|
-
Fail`${filename} arena size mismatch; wanted ${arenaSize}, got ${hdrArenaSize}`;
|
|
102
|
-
const arena = new Uint8Array(
|
|
103
|
-
fileBuf.buffer,
|
|
104
|
-
header.byteLength,
|
|
105
|
-
Number(arenaSize),
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* @param {Uint8Array} outbuf
|
|
110
|
-
* @param {number} [offset] offset relative to the current trailing edge (circStart) of the data
|
|
111
|
-
* @returns {IteratorResult<Uint8Array, void>}
|
|
112
|
-
*/
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @param {bigint} arenaSize
|
|
71
|
+
* @param {DataView} header
|
|
72
|
+
* @param {(outbuf: Uint8Array, readStart: number, firstReadLength: number) => void} readRecord
|
|
73
|
+
* @param {(record: Uint8Array, firstWriteLength: number, circEnd: bigint) => Promise<void>} writeRecord
|
|
74
|
+
*/
|
|
75
|
+
function finishCircularBuffer(arenaSize, header, readRecord, writeRecord) {
|
|
113
76
|
const readCircBuf = (outbuf, offset = 0) => {
|
|
114
77
|
offset + outbuf.byteLength <= arenaSize ||
|
|
115
78
|
Fail`Reading past end of circular buffer`;
|
|
@@ -132,19 +95,13 @@ export const makeMemoryMappedCircularBuffer = async ({
|
|
|
132
95
|
// The data is contiguous, like ---AAABBB---
|
|
133
96
|
return { done: true, value: undefined };
|
|
134
97
|
}
|
|
135
|
-
outbuf
|
|
136
|
-
if (firstReadLength < outbuf.byteLength) {
|
|
137
|
-
outbuf.set(
|
|
138
|
-
arena.subarray(0, outbuf.byteLength - firstReadLength),
|
|
139
|
-
firstReadLength,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
98
|
+
readRecord(outbuf, readStart, firstReadLength);
|
|
142
99
|
return { done: false, value: outbuf };
|
|
143
100
|
};
|
|
144
101
|
|
|
145
|
-
/** @
|
|
146
|
-
const writeCircBuf = data => {
|
|
147
|
-
if (RECORD_HEADER_SIZE + data.byteLength >
|
|
102
|
+
/** @type {(data: Uint8Array) => Promise<void>} */
|
|
103
|
+
const writeCircBuf = async data => {
|
|
104
|
+
if (RECORD_HEADER_SIZE + data.byteLength > arenaSize) {
|
|
148
105
|
// The data is too big to fit in the arena, so skip it.
|
|
149
106
|
const tooBigRecord = JSON.stringify({
|
|
150
107
|
type: 'slog-record-too-big',
|
|
@@ -153,14 +110,17 @@ export const makeMemoryMappedCircularBuffer = async ({
|
|
|
153
110
|
data = new TextEncoder().encode(tooBigRecord);
|
|
154
111
|
}
|
|
155
112
|
|
|
156
|
-
if (RECORD_HEADER_SIZE + data.byteLength >
|
|
113
|
+
if (RECORD_HEADER_SIZE + data.byteLength > arenaSize) {
|
|
157
114
|
// Silently drop, it just doesn't fit.
|
|
158
115
|
return;
|
|
159
116
|
}
|
|
160
117
|
|
|
118
|
+
// Allocate for the data and a header
|
|
161
119
|
const record = new Uint8Array(RECORD_HEADER_SIZE + data.byteLength);
|
|
120
|
+
// Set the data, after the header
|
|
162
121
|
record.set(data, RECORD_HEADER_SIZE);
|
|
163
122
|
|
|
123
|
+
// Set the size in the header
|
|
164
124
|
const lengthPrefix = new DataView(record.buffer);
|
|
165
125
|
lengthPrefix.setBigUint64(0, BigInt(data.byteLength));
|
|
166
126
|
|
|
@@ -206,31 +166,135 @@ export const makeMemoryMappedCircularBuffer = async ({
|
|
|
206
166
|
);
|
|
207
167
|
}
|
|
208
168
|
|
|
209
|
-
arena.set(record.subarray(0, firstWriteLength), Number(circEnd));
|
|
210
|
-
if (firstWriteLength < record.byteLength) {
|
|
211
|
-
// Write to the beginning of the arena.
|
|
212
|
-
arena.set(record.subarray(firstWriteLength, record.byteLength), 0);
|
|
213
|
-
}
|
|
214
169
|
header.setBigUint64(
|
|
215
170
|
I_CIRC_END,
|
|
216
171
|
(circEnd + BigInt(record.byteLength)) % arenaSize,
|
|
217
172
|
);
|
|
173
|
+
|
|
174
|
+
return writeRecord(record, firstWriteLength, circEnd);
|
|
218
175
|
};
|
|
219
176
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
177
|
+
return { readCircBuf, writeCircBuf };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {{
|
|
182
|
+
* circularBufferSize?: number,
|
|
183
|
+
* stateDir?: string,
|
|
184
|
+
* circularBufferFilename?: string
|
|
185
|
+
* }} opts
|
|
186
|
+
*/
|
|
187
|
+
export const makeSimpleCircularBuffer = async ({
|
|
188
|
+
circularBufferSize = DEFAULT_CBUF_SIZE,
|
|
189
|
+
stateDir = '/tmp',
|
|
190
|
+
circularBufferFilename,
|
|
191
|
+
}) => {
|
|
192
|
+
const filename = circularBufferFilename || `${stateDir}/${DEFAULT_CBUF_FILE}`;
|
|
193
|
+
|
|
194
|
+
const newArenaSize = await initializeCircularBuffer(
|
|
195
|
+
filename,
|
|
196
|
+
circularBufferSize,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const file = await fsp.open(filename, 'r+');
|
|
200
|
+
|
|
201
|
+
const headerBuffer = Buffer.alloc(I_ARENA_START);
|
|
202
|
+
|
|
203
|
+
await file.read({
|
|
204
|
+
buffer: headerBuffer,
|
|
205
|
+
length: I_ARENA_START,
|
|
206
|
+
position: 0,
|
|
207
|
+
});
|
|
208
|
+
const header = new DataView(headerBuffer.buffer);
|
|
209
|
+
|
|
210
|
+
// Detect the arena size from the header, if not initialized.
|
|
211
|
+
const hdrArenaSize = header.getBigUint64(I_ARENA_SIZE);
|
|
212
|
+
const arenaSize = newArenaSize || hdrArenaSize;
|
|
213
|
+
|
|
214
|
+
const hdrMagic = header.getBigUint64(I_MAGIC);
|
|
215
|
+
SLOG_MAGIC === hdrMagic ||
|
|
216
|
+
Fail`${filename} is not a slog buffer; wanted magic ${SLOG_MAGIC}, got ${hdrMagic}`;
|
|
217
|
+
arenaSize === hdrArenaSize ||
|
|
218
|
+
Fail`${filename} arena size mismatch; wanted ${arenaSize}, got ${hdrArenaSize}`;
|
|
219
|
+
|
|
220
|
+
/** @type {(outbuf: Uint8Array, readStart: number, firstReadLength: number) => void} */
|
|
221
|
+
const readRecord = (outbuf, readStart, firstReadLength) => {
|
|
222
|
+
const bytesRead = fs.readSync(file.fd, outbuf, {
|
|
223
|
+
length: firstReadLength,
|
|
224
|
+
position: Number(readStart) + I_ARENA_START,
|
|
225
|
+
});
|
|
226
|
+
assert.equal(bytesRead, firstReadLength, 'Too few bytes read');
|
|
227
|
+
|
|
228
|
+
if (bytesRead < outbuf.byteLength) {
|
|
229
|
+
fs.readSync(file.fd, outbuf, {
|
|
230
|
+
offset: firstReadLength,
|
|
231
|
+
length: outbuf.byteLength - firstReadLength,
|
|
232
|
+
position: I_ARENA_START,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Writes to the file, offset by the header size. Also updates the file header.
|
|
239
|
+
*
|
|
240
|
+
* @param {Uint8Array} record
|
|
241
|
+
* @param {number} firstWriteLength
|
|
242
|
+
* @param {bigint} circEnd
|
|
243
|
+
*/
|
|
244
|
+
const writeRecord = async (record, firstWriteLength, circEnd) => {
|
|
245
|
+
await file.write(
|
|
246
|
+
record,
|
|
247
|
+
// TS saying options bag not available
|
|
248
|
+
0,
|
|
249
|
+
firstWriteLength,
|
|
250
|
+
I_ARENA_START + Number(circEnd),
|
|
251
|
+
);
|
|
252
|
+
if (firstWriteLength < record.byteLength) {
|
|
253
|
+
// Write to the beginning of the arena.
|
|
254
|
+
await file.write(
|
|
255
|
+
record,
|
|
256
|
+
firstWriteLength,
|
|
257
|
+
record.byteLength - firstWriteLength,
|
|
258
|
+
I_ARENA_START,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Write out the updated file header.
|
|
263
|
+
// This is somewhat independent of writing the record itself, but it needs
|
|
264
|
+
// updating each time a record is written.
|
|
265
|
+
await file.write(headerBuffer, undefined, undefined, 0);
|
|
225
266
|
};
|
|
226
267
|
|
|
227
|
-
return
|
|
268
|
+
return finishCircularBuffer(arenaSize, header, readRecord, writeRecord);
|
|
228
269
|
};
|
|
229
270
|
|
|
230
|
-
|
|
231
|
-
|
|
271
|
+
/**
|
|
272
|
+
*
|
|
273
|
+
* @param {Pick<Awaited<ReturnType<typeof makeSimpleCircularBuffer>>, 'writeCircBuf'>} circBuf
|
|
274
|
+
*/
|
|
275
|
+
export const makeSlogSenderFromBuffer = ({ writeCircBuf }) => {
|
|
276
|
+
/** @type {Promise<void>} */
|
|
277
|
+
let toWrite = Promise.resolve();
|
|
278
|
+
const writeJSON = (obj, serialized = serializeSlogObj(obj)) => {
|
|
279
|
+
// Prepend a newline so that the file can be more easily manipulated.
|
|
280
|
+
const data = new TextEncoder().encode(`\n${serialized}`);
|
|
281
|
+
// console.log('have obj', obj, data);
|
|
282
|
+
toWrite = toWrite.then(() => writeCircBuf(data));
|
|
283
|
+
};
|
|
232
284
|
return Object.assign(writeJSON, {
|
|
233
|
-
forceFlush: async () => {
|
|
285
|
+
forceFlush: async () => {
|
|
286
|
+
await toWrite;
|
|
287
|
+
},
|
|
234
288
|
usesJsonObject: true,
|
|
235
289
|
});
|
|
236
290
|
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Loaded dynamically by makeSlogSender()
|
|
294
|
+
*
|
|
295
|
+
* @type {import('./index.js').MakeSlogSender}
|
|
296
|
+
*/
|
|
297
|
+
export const makeSlogSender = async opts => {
|
|
298
|
+
const { writeCircBuf } = await makeSimpleCircularBuffer(opts);
|
|
299
|
+
return makeSlogSenderFromBuffer({ writeCircBuf });
|
|
300
|
+
};
|
package/src/frcat-entrypoint.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
|
-
/*
|
|
2
|
+
/* eslint-env node */
|
|
3
3
|
// frcat - print out the contents of a flight recorder
|
|
4
4
|
// NOTE: this only works on inactive recorder files where the writer has terminated
|
|
5
5
|
|
|
6
6
|
import '@endo/init';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { makeSimpleCircularBuffer } from './flight-recorder.js';
|
|
9
9
|
|
|
10
10
|
const main = async () => {
|
|
11
11
|
const files = process.argv.slice(2);
|
|
@@ -14,8 +14,7 @@ const main = async () => {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
for await (const file of files) {
|
|
17
|
-
|
|
18
|
-
const { readCircBuf } = await makeMemoryMappedCircularBuffer({
|
|
17
|
+
const { readCircBuf } = await makeSimpleCircularBuffer({
|
|
19
18
|
circularBufferFilename: file,
|
|
20
19
|
circularBufferSize: 0,
|
|
21
20
|
});
|
|
@@ -50,13 +49,18 @@ const main = async () => {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
// If the buffer is full, wait for stdout to drain.
|
|
53
|
-
// eslint-disable-next-line no-await-in-loop, @jessie.js/no-nested-await
|
|
54
52
|
await new Promise(resolve => process.stdout.once('drain', resolve));
|
|
55
53
|
}
|
|
56
54
|
}
|
|
57
55
|
};
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
main().then(
|
|
59
|
+
() => {
|
|
60
|
+
process.exitCode = 0;
|
|
61
|
+
},
|
|
62
|
+
err => {
|
|
63
|
+
console.error('Failed with', err);
|
|
64
|
+
process.exit(process.exitCode || 1);
|
|
65
|
+
},
|
|
66
|
+
);
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,9 @@ export * from './make-slog-sender.js';
|
|
|
13
13
|
* shutdown?: () => Promise<void>;
|
|
14
14
|
* }} SlogSender
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {(opts: import('./index.js').MakeSlogSenderOptions) => SlogSender | undefined} MakeSlogSender
|
|
18
|
+
*/
|
|
16
19
|
/**
|
|
17
20
|
* @typedef {MakeSlogSenderCommonOptions & Record<string, unknown>} MakeSlogSenderOptions
|
|
18
21
|
* @typedef {object} MakeSlogSenderCommonOptions
|
|
@@ -34,9 +37,9 @@ export const tryFlushSlogSender = async (
|
|
|
34
37
|
await Promise.resolve(slogSender?.forceFlush?.()).catch(err => {
|
|
35
38
|
log?.('Failed to flush slog sender', err);
|
|
36
39
|
if (err.errors) {
|
|
37
|
-
err.errors
|
|
40
|
+
for (const error of err.errors) {
|
|
38
41
|
log?.('nested error:', error);
|
|
39
|
-
}
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
if (env.SLOGSENDER_FAIL_ON_ERROR) {
|
|
42
45
|
throw err;
|
|
@@ -58,21 +61,20 @@ export const getResourceAttributes = ({
|
|
|
58
61
|
SDK_REVISION;
|
|
59
62
|
}
|
|
60
63
|
if (!resourceAttributes[SemanticResourceAttributes.SERVICE_INSTANCE_ID]) {
|
|
61
|
-
resourceAttributes[
|
|
62
|
-
|
|
63
|
-
] = `${Math.random()}`;
|
|
64
|
+
resourceAttributes[SemanticResourceAttributes.SERVICE_INSTANCE_ID] =
|
|
65
|
+
`${Math.random()}`;
|
|
64
66
|
}
|
|
65
67
|
if (serviceName) {
|
|
66
68
|
resourceAttributes[SemanticResourceAttributes.SERVICE_NAME] = serviceName;
|
|
67
69
|
}
|
|
68
70
|
if (OTEL_RESOURCE_ATTRIBUTES) {
|
|
69
71
|
// Allow overriding resource attributes.
|
|
70
|
-
OTEL_RESOURCE_ATTRIBUTES.split(',')
|
|
72
|
+
for (const kv of OTEL_RESOURCE_ATTRIBUTES.split(',')) {
|
|
71
73
|
const match = kv.match(/^([^=]*)=(.*)$/);
|
|
72
74
|
if (match) {
|
|
73
75
|
resourceAttributes[match[1]] = match[2];
|
|
74
76
|
}
|
|
75
|
-
}
|
|
77
|
+
}
|
|
76
78
|
}
|
|
77
79
|
return resourceAttributes;
|
|
78
80
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
|
-
/*
|
|
2
|
+
/* eslint-env node */
|
|
3
3
|
import '@endo/init';
|
|
4
4
|
|
|
5
5
|
import fs from 'fs';
|
|
@@ -11,7 +11,8 @@ import { makeSlogSender } from './make-slog-sender.js';
|
|
|
11
11
|
|
|
12
12
|
const LINE_COUNT_TO_FLUSH = 10000;
|
|
13
13
|
const ELAPSED_MS_TO_FLUSH = 3000;
|
|
14
|
-
const MAX_LINE_COUNT_PER_PERIOD =
|
|
14
|
+
const MAX_LINE_COUNT_PER_PERIOD = 10000;
|
|
15
|
+
const MAX_BLOCKS_PER_PERIOD = 10;
|
|
15
16
|
const PROCESSING_PERIOD = 1000;
|
|
16
17
|
|
|
17
18
|
async function run() {
|
|
@@ -29,7 +30,7 @@ async function run() {
|
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
+
const slogFile = args[0] === '-' ? undefined : args[0];
|
|
33
34
|
const slogSender = await makeSlogSender({
|
|
34
35
|
serviceName,
|
|
35
36
|
stateDir: '.',
|
|
@@ -56,14 +57,21 @@ async function run() {
|
|
|
56
57
|
const lines = readline.createInterface({ input: slogF });
|
|
57
58
|
const slogFileName = slogFile || '*stdin*';
|
|
58
59
|
|
|
59
|
-
const progressFileName = `${slogFileName}.ingest-progress`;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
fs.
|
|
60
|
+
const progressFileName = slogFile && `${slogFileName}.ingest-progress`;
|
|
61
|
+
const progress = { virtualTimeOffset: 0, lastSlogTime: 0 };
|
|
62
|
+
if (progressFileName) {
|
|
63
|
+
if (!fs.existsSync(progressFileName)) {
|
|
64
|
+
fs.writeFileSync(progressFileName, JSON.stringify(progress));
|
|
65
|
+
} else {
|
|
66
|
+
Object.assign(
|
|
67
|
+
progress,
|
|
68
|
+
JSON.parse(fs.readFileSync(progressFileName).toString()),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
63
71
|
}
|
|
64
|
-
const progress = JSON.parse(fs.readFileSync(progressFileName).toString());
|
|
65
72
|
|
|
66
73
|
let linesProcessedThisPeriod = 0;
|
|
74
|
+
let blocksInThisPeriod = 0;
|
|
67
75
|
let startOfLastPeriod = 0;
|
|
68
76
|
|
|
69
77
|
let lastTime = Date.now();
|
|
@@ -74,11 +82,13 @@ async function run() {
|
|
|
74
82
|
if (!flush) {
|
|
75
83
|
return;
|
|
76
84
|
}
|
|
77
|
-
await slogSender.forceFlush();
|
|
78
|
-
|
|
85
|
+
await slogSender.forceFlush?.();
|
|
86
|
+
if (progressFileName) {
|
|
87
|
+
fs.writeFileSync(progressFileName, JSON.stringify(progress));
|
|
88
|
+
}
|
|
79
89
|
};
|
|
80
90
|
|
|
81
|
-
console.
|
|
91
|
+
console.warn(`parsing`, slogFileName);
|
|
82
92
|
|
|
83
93
|
let update = false;
|
|
84
94
|
const maybeUpdateStats = async now => {
|
|
@@ -106,9 +116,14 @@ async function run() {
|
|
|
106
116
|
continue;
|
|
107
117
|
}
|
|
108
118
|
|
|
119
|
+
const isAfterCommit = obj.type === 'cosmic-swingset-after-commit-stats';
|
|
120
|
+
|
|
109
121
|
// Maybe wait for the next period to process a bunch of lines.
|
|
110
122
|
let maybeWait;
|
|
111
|
-
if (
|
|
123
|
+
if (
|
|
124
|
+
linesProcessedThisPeriod >= MAX_LINE_COUNT_PER_PERIOD ||
|
|
125
|
+
blocksInThisPeriod >= MAX_BLOCKS_PER_PERIOD
|
|
126
|
+
) {
|
|
112
127
|
const delayMS = PROCESSING_PERIOD - (now - startOfLastPeriod);
|
|
113
128
|
maybeWait = new Promise(resolve => setTimeout(resolve, delayMS));
|
|
114
129
|
}
|
|
@@ -118,8 +133,8 @@ async function run() {
|
|
|
118
133
|
if (now - startOfLastPeriod >= PROCESSING_PERIOD) {
|
|
119
134
|
startOfLastPeriod = now;
|
|
120
135
|
linesProcessedThisPeriod = 0;
|
|
136
|
+
blocksInThisPeriod = 0;
|
|
121
137
|
}
|
|
122
|
-
linesProcessedThisPeriod += 1;
|
|
123
138
|
|
|
124
139
|
if (progress.virtualTimeOffset) {
|
|
125
140
|
const virtualTime = obj.time + progress.virtualTimeOffset;
|
|
@@ -133,14 +148,30 @@ async function run() {
|
|
|
133
148
|
// Use the original.
|
|
134
149
|
slogSender(obj);
|
|
135
150
|
}
|
|
151
|
+
|
|
152
|
+
linesProcessedThisPeriod += 1;
|
|
153
|
+
if (isAfterCommit) {
|
|
154
|
+
blocksInThisPeriod += 1;
|
|
155
|
+
lastTime = Date.now();
|
|
156
|
+
await stats(true);
|
|
157
|
+
}
|
|
136
158
|
}
|
|
137
159
|
|
|
138
160
|
await stats(true);
|
|
139
|
-
console.
|
|
161
|
+
console.warn(
|
|
140
162
|
`done parsing`,
|
|
141
163
|
slogFileName,
|
|
142
164
|
`(${lineCount} lines, ${byteCount} bytes)`,
|
|
143
165
|
);
|
|
144
166
|
}
|
|
145
167
|
|
|
146
|
-
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
run().then(
|
|
170
|
+
() => {
|
|
171
|
+
process.exitCode = 0;
|
|
172
|
+
},
|
|
173
|
+
err => {
|
|
174
|
+
console.error('Failed with', err);
|
|
175
|
+
process.exit(process.exitCode || 1);
|
|
176
|
+
},
|
|
177
|
+
);
|
package/src/make-slog-sender.js
CHANGED
|
@@ -9,7 +9,7 @@ export const SLOGFILE_SENDER_MODULE = '@agoric/telemetry/src/slog-file.js';
|
|
|
9
9
|
|
|
10
10
|
export const DEFAULT_SLOGSENDER_AGENT = 'self';
|
|
11
11
|
|
|
12
|
-
/** @
|
|
12
|
+
/** @import {SlogSender} from './index.js' */
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @template T
|
|
@@ -19,7 +19,7 @@ export const DEFAULT_SLOGSENDER_AGENT = 'self';
|
|
|
19
19
|
const filterTruthy = arr => /** @type {any[]} */ (arr.filter(Boolean));
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* @
|
|
22
|
+
* @type {import('./index.js').MakeSlogSender}
|
|
23
23
|
*/
|
|
24
24
|
export const makeSlogSender = async (opts = {}) => {
|
|
25
25
|
const { env = {}, stateDir: stateDirOption, ...otherOpts } = opts;
|
|
@@ -88,7 +88,7 @@ export const makeSlogSender = async (opts = {}) => {
|
|
|
88
88
|
slogSenderModules.map(async moduleIdentifier =>
|
|
89
89
|
import(moduleIdentifier)
|
|
90
90
|
.then(
|
|
91
|
-
/** @param {{makeSlogSender: (
|
|
91
|
+
/** @param {{makeSlogSender: import('./index.js').MakeSlogSender}} module */ ({
|
|
92
92
|
makeSlogSender: maker,
|
|
93
93
|
}) => {
|
|
94
94
|
if (typeof maker !== 'function') {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { NonNullish } from '@agoric/
|
|
1
|
+
import { NonNullish } from '@agoric/internal';
|
|
2
2
|
import { makeSlogSender as makeSlogSenderFromEnv } from './make-slog-sender.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('./index.js').MakeSlogSenderOptions} opts
|
|
6
|
+
*/
|
|
4
7
|
export const makeSlogSender = async opts => {
|
|
5
8
|
const { SLOGFILE: _1, SLOGSENDER: _2, ...otherEnv } = opts.env || {};
|
|
6
9
|
|