@nosana/node 1.1.56-rc → 1.1.57-rc
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/dist/package.json +1 -1
- package/dist/src/NodeManager/index.d.ts +1 -0
- package/dist/src/NodeManager/index.js +7 -0
- package/dist/src/NodeManager/monitoring/reportPodmanDiagnostics.d.ts +12 -0
- package/dist/src/NodeManager/monitoring/reportPodmanDiagnostics.js +111 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -12,6 +12,7 @@ import { LogMonitoringRegistry } from './monitoring/LogMonitoringRegistry.js';
|
|
|
12
12
|
import { checkWSLStatus } from './utils/wslCheck.js';
|
|
13
13
|
import { IN_JOB_LOOP_ENV } from '../exitCodes.js';
|
|
14
14
|
import { reportError } from './monitoring/reportError.js';
|
|
15
|
+
import { reportPodmanDiagnostics } from './monitoring/reportPodmanDiagnostics.js';
|
|
15
16
|
export default class NodeManager {
|
|
16
17
|
constructor(options) {
|
|
17
18
|
this.exiting = false;
|
|
@@ -22,6 +23,7 @@ export default class NodeManager {
|
|
|
22
23
|
* replaced by an update.
|
|
23
24
|
*/
|
|
24
25
|
this.inJobLoop = process.env[IN_JOB_LOOP_ENV] === 'true';
|
|
26
|
+
this.configLocation = options.config;
|
|
25
27
|
this.node = createLoggingProxy(new BasicNode(options));
|
|
26
28
|
/**
|
|
27
29
|
* the node class makes the api but we pass the api to the NodeManager class
|
|
@@ -58,6 +60,11 @@ export default class NodeManager {
|
|
|
58
60
|
* this prints the logs to the console.
|
|
59
61
|
*/
|
|
60
62
|
consoleLogging();
|
|
63
|
+
/**
|
|
64
|
+
* collect the diagnostics the podman container leaves in the shared
|
|
65
|
+
* directory, which has no other route off the node
|
|
66
|
+
*/
|
|
67
|
+
reportPodmanDiagnostics(this.configLocation);
|
|
61
68
|
/**
|
|
62
69
|
* start
|
|
63
70
|
*
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forward the events the podman container leaves in the directory the two
|
|
3
|
+
* share. That container has no route to the host manager of its own, so its
|
|
4
|
+
* event log is the only sign of whether the CDI watcher ever fired.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here may throw: this is diagnostics, and the node's uncaught handler
|
|
7
|
+
* would turn a failure to read a log file into the node stopping. Watching can
|
|
8
|
+
* fail outright (a directory that is not there, a host out of inotify watches)
|
|
9
|
+
* and a watch already established still reports errors of its own if the
|
|
10
|
+
* directory it covers is removed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function reportPodmanDiagnostics(configLocation: string): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { reportError } from './reportError.js';
|
|
5
|
+
const CDI_EVENTS = 'cdi-events.log';
|
|
6
|
+
// Enough of the first event to tell one log from another.
|
|
7
|
+
const HEAD_BYTES = 64;
|
|
8
|
+
// A log this far ahead of us is skipped rather than read into memory at once.
|
|
9
|
+
const MAX_CATCH_UP = 1024 * 1024;
|
|
10
|
+
/**
|
|
11
|
+
* Forward the events the podman container leaves in the directory the two
|
|
12
|
+
* share. That container has no route to the host manager of its own, so its
|
|
13
|
+
* event log is the only sign of whether the CDI watcher ever fired.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here may throw: this is diagnostics, and the node's uncaught handler
|
|
16
|
+
* would turn a failure to read a log file into the node stopping. Watching can
|
|
17
|
+
* fail outright (a directory that is not there, a host out of inotify watches)
|
|
18
|
+
* and a watch already established still reports errors of its own if the
|
|
19
|
+
* directory it covers is removed.
|
|
20
|
+
*/
|
|
21
|
+
export function reportPodmanDiagnostics(configLocation) {
|
|
22
|
+
if (!configLocation)
|
|
23
|
+
return;
|
|
24
|
+
const log = path.join(configLocation.replace(/^~/, os.homedir()), CDI_EVENTS);
|
|
25
|
+
let head = '';
|
|
26
|
+
let drained = 0;
|
|
27
|
+
let partial = '';
|
|
28
|
+
// Only the bytes appended since the last read: the log is never rotated, so
|
|
29
|
+
// re-reading it whole would cost more with every event.
|
|
30
|
+
//
|
|
31
|
+
// Replacement is detected by re-reading the start of the file, which is the
|
|
32
|
+
// only signal that survives it: the log is replaced when the podman container
|
|
33
|
+
// is recreated, and neither size nor inode moves — these events are formulaic
|
|
34
|
+
// enough that the new first line is often exactly as long as the old, and a
|
|
35
|
+
// path recreated at once takes the inode back. The descriptor is opened per
|
|
36
|
+
// read for the same reason: a held one keeps reporting the size of the
|
|
37
|
+
// replaced file and never sees another event.
|
|
38
|
+
const drain = () => {
|
|
39
|
+
let fd;
|
|
40
|
+
try {
|
|
41
|
+
fd = fs.openSync(log, 'r');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return; // not written yet, or gone with the container that wrote it
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const { size } = fs.fstatSync(fd);
|
|
48
|
+
const start = Buffer.alloc(Math.min(size, HEAD_BYTES));
|
|
49
|
+
fs.readSync(fd, start, 0, start.length, 0);
|
|
50
|
+
// Only the prefix the two have in common: while the log is still shorter
|
|
51
|
+
// than HEAD_BYTES an append lengthens what is read here, which is growth
|
|
52
|
+
// rather than a different file.
|
|
53
|
+
const first = start.toString('utf8');
|
|
54
|
+
const shared = Math.min(first.length, head.length);
|
|
55
|
+
if (first.slice(0, shared) !== head.slice(0, shared) || size < drained) {
|
|
56
|
+
drained = 0;
|
|
57
|
+
partial = '';
|
|
58
|
+
}
|
|
59
|
+
head = first;
|
|
60
|
+
if (size === drained)
|
|
61
|
+
return;
|
|
62
|
+
// Reading a log that has run far ahead would hold all of it in memory at
|
|
63
|
+
// once; the older events are given up instead.
|
|
64
|
+
if (size - drained > MAX_CATCH_UP) {
|
|
65
|
+
drained = size;
|
|
66
|
+
partial = '';
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const appended = Buffer.alloc(size - drained);
|
|
70
|
+
fs.readSync(fd, appended, 0, appended.length, drained);
|
|
71
|
+
drained = size;
|
|
72
|
+
// A write can be seen part way through a line; hold the tail until the
|
|
73
|
+
// newline that ends it arrives.
|
|
74
|
+
const lines = (partial + appended.toString('utf8')).split('\n');
|
|
75
|
+
partial = lines.pop() ?? '';
|
|
76
|
+
lines
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.forEach((event) => void reportError({
|
|
79
|
+
error_type: 'cdiEvent',
|
|
80
|
+
error_name: 'Event',
|
|
81
|
+
error_message: event,
|
|
82
|
+
error_stack: '',
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
fs.closeSync(fd);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const drainQuietly = () => {
|
|
90
|
+
try {
|
|
91
|
+
drain();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Reported nothing this time; the next event tries again.
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
drainQuietly();
|
|
98
|
+
try {
|
|
99
|
+
// The directory, not the file: the file does not exist until the first
|
|
100
|
+
// fault, and a watch on a path that is replaced follows neither file.
|
|
101
|
+
const watcher = fs.watch(path.dirname(log), (_, file) => {
|
|
102
|
+
if (!file || file === CDI_EVENTS)
|
|
103
|
+
drainQuietly();
|
|
104
|
+
});
|
|
105
|
+
watcher.on('error', () => watcher.close());
|
|
106
|
+
watcher.unref();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// No watch, so no events are forwarded; the node runs regardless.
|
|
110
|
+
}
|
|
111
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosana/node",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.57-rc",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@nosana/node",
|
|
9
|
-
"version": "1.1.
|
|
9
|
+
"version": "1.1.57-rc",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@coral-xyz/anchor": "^0.28.1-beta.1",
|