@nosana/node 1.1.68-rc → 1.1.69-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/node/api/routes/wss/log.d.ts +4 -1
- package/dist/src/NodeManager/node/api/routes/wss/log.js +21 -6
- package/dist/src/NodeManager/node/task/TaskManager.d.ts +4 -3
- package/dist/src/NodeManager/node/task/loggers/logManager.d.ts +3 -3
- package/dist/src/NodeManager/node/task/loggers/logManager.js +57 -13
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -6,9 +6,12 @@ import WebSocket from 'ws';
|
|
|
6
6
|
export declare function wssLogRoute(ws: WebSocket, _: string, { jobAddress }: {
|
|
7
7
|
jobAddress: string;
|
|
8
8
|
}): Promise<void>;
|
|
9
|
-
export declare function wssTaskManagerLogRoute(ws: WebSocket, _: string, { jobAddress, group, opId, type, }: {
|
|
9
|
+
export declare function wssTaskManagerLogRoute(ws: WebSocket, _: string, { jobAddress, group, opId, type, since, limit, }: {
|
|
10
10
|
jobAddress: string;
|
|
11
11
|
group?: string;
|
|
12
12
|
opId?: string;
|
|
13
13
|
type?: string;
|
|
14
|
+
/** Only logs written after this epoch-millisecond mark. */
|
|
15
|
+
since?: number;
|
|
16
|
+
limit?: number;
|
|
14
17
|
}): Promise<void>;
|
|
@@ -3,6 +3,12 @@ import { sleep } from '@nosana/sdk';
|
|
|
3
3
|
import { logStreaming } from '../../../../monitoring/streaming/LogStreamer.js';
|
|
4
4
|
import { getSDK } from '../../../../sdk/index.js';
|
|
5
5
|
import { TaskManagerRegistry } from '../../../task/TaskManagerRegistry.js';
|
|
6
|
+
/**
|
|
7
|
+
* Lines of history a client gets when it doesn't ask for a number. A service
|
|
8
|
+
* that has run for hours has more log than a viewer can use, and every line
|
|
9
|
+
* costs a frame, so the recent end is sent and the rest is left to `since`.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_REPLAY_LIMIT = 5000;
|
|
6
12
|
/**
|
|
7
13
|
* this is for log streaming, this is going to be used by the basic job poster
|
|
8
14
|
* just to show that clients logs, both from the node and the container
|
|
@@ -12,7 +18,7 @@ export async function wssLogRoute(ws, _, { jobAddress }) {
|
|
|
12
18
|
const walletAddress = sdk.solana.wallet.toString();
|
|
13
19
|
logStreaming(walletAddress).subscribe(ws, jobAddress);
|
|
14
20
|
}
|
|
15
|
-
export async function wssTaskManagerLogRoute(ws, _, { jobAddress, group, opId, type, }) {
|
|
21
|
+
export async function wssTaskManagerLogRoute(ws, _, { jobAddress, group, opId, type, since, limit, }) {
|
|
16
22
|
const registry = TaskManagerRegistry.getInstance();
|
|
17
23
|
const deadline = Date.now() + 30000;
|
|
18
24
|
let task = registry.get(jobAddress);
|
|
@@ -30,12 +36,21 @@ export async function wssTaskManagerLogRoute(ws, _, { jobAddress, group, opId, t
|
|
|
30
36
|
(!opId || log.opId === opId) &&
|
|
31
37
|
(!type || log.type === type));
|
|
32
38
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
const cap = typeof limit === 'number' && isFinite(limit) && limit > 0
|
|
40
|
+
? Math.floor(limit)
|
|
41
|
+
: DEFAULT_REPLAY_LIMIT;
|
|
42
|
+
// Historical logs are replayed before the socket returns to the event loop,
|
|
43
|
+
// so a live line can never land among them.
|
|
44
|
+
let logs = opId
|
|
45
|
+
? task.getLogsByOp(opId, cap)
|
|
36
46
|
: group
|
|
37
|
-
? task.getLogsByGroup(group)
|
|
38
|
-
: task.getAllLogs();
|
|
47
|
+
? task.getLogsByGroup(group, cap)
|
|
48
|
+
: task.getAllLogs(cap);
|
|
49
|
+
// `cap` already took the newest lines, so this can only trim that window
|
|
50
|
+
// further — never reach past it for more.
|
|
51
|
+
if (typeof since === 'number' && isFinite(since)) {
|
|
52
|
+
logs = logs.filter((log) => log.timestamp > since);
|
|
53
|
+
}
|
|
39
54
|
for (const log of logs) {
|
|
40
55
|
try {
|
|
41
56
|
ws.send(JSON.stringify({ path: 'flog', data: JSON.stringify(log) }));
|
|
@@ -223,9 +223,10 @@ export default class TaskManager {
|
|
|
223
223
|
getCurrentGroupStatus: () => Record<string, string | null>;
|
|
224
224
|
getGroupStatus: (group: string) => Record<string, string | null>;
|
|
225
225
|
addlog: (log: TaskLog) => void;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
/** `limit` keeps the newest logs; the read costs the size of that window. */
|
|
227
|
+
getLogsByOp: (opid: string, limit?: number) => TaskLog[];
|
|
228
|
+
getLogsByGroup: (group: string, limit?: number) => TaskLog[];
|
|
229
|
+
getAllLogs: (limit?: number) => TaskLog[];
|
|
229
230
|
subscribe: (ws: WebSocket, matcher: (log: TaskLog) => boolean) => void;
|
|
230
231
|
unsubscribe: (ws: WebSocket) => void;
|
|
231
232
|
addStat: (stat: TaskStat) => void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import TaskManager, { TaskLog } from '../TaskManager.js';
|
|
2
2
|
import WebSocket from 'ws';
|
|
3
3
|
export declare function addLog(this: TaskManager, log: TaskLog): void;
|
|
4
|
-
export declare function getLogsByOp(this: TaskManager, opId: string): TaskLog[];
|
|
5
|
-
export declare function getLogsByGroup(this: TaskManager, group: string): TaskLog[];
|
|
6
|
-
export declare function getAllLogs(this: TaskManager): TaskLog[];
|
|
4
|
+
export declare function getLogsByOp(this: TaskManager, opId: string, limit?: number): TaskLog[];
|
|
5
|
+
export declare function getLogsByGroup(this: TaskManager, group: string, limit?: number): TaskLog[];
|
|
6
|
+
export declare function getAllLogs(this: TaskManager, limit?: number): TaskLog[];
|
|
7
7
|
export declare function subscribe(this: TaskManager, ws: WebSocket, matcher: (log: TaskLog) => boolean): void;
|
|
8
8
|
export declare function unsubscribe(this: TaskManager, ws: WebSocket): void;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Per-operation replay buffer. A long-running service logs for as long as it
|
|
3
|
+
* runs, so this is a memory bound, not a history: the full log of a finished
|
|
4
|
+
* job is read back from its results, not from here.
|
|
5
|
+
*/
|
|
6
|
+
const MAX_LOGS_PER_OP = 20000;
|
|
7
|
+
/**
|
|
8
|
+
* Dropping one line per push once at the cap moves the whole buffer each time,
|
|
9
|
+
* so room is made in blocks instead.
|
|
10
|
+
*/
|
|
11
|
+
const TRIM_BLOCK = 2000;
|
|
2
12
|
// Ensure strictly increasing timestamps per TaskManager to avoid mis-ordered logs
|
|
3
13
|
const lastTimestampByTask = new WeakMap();
|
|
4
14
|
export function addLog(log) {
|
|
@@ -16,8 +26,9 @@ export function addLog(log) {
|
|
|
16
26
|
}
|
|
17
27
|
const buffer = this.opLogBuffers.get(log.opId);
|
|
18
28
|
buffer.push(log);
|
|
19
|
-
if (buffer.length > MAX_LOGS_PER_OP)
|
|
20
|
-
buffer.
|
|
29
|
+
if (buffer.length > MAX_LOGS_PER_OP + TRIM_BLOCK) {
|
|
30
|
+
buffer.splice(0, buffer.length - MAX_LOGS_PER_OP);
|
|
31
|
+
}
|
|
21
32
|
for (const ws of this.subscribers) {
|
|
22
33
|
const matcher = this.logMatchers.get(ws);
|
|
23
34
|
if (matcher && matcher(log)) {
|
|
@@ -28,18 +39,51 @@ export function addLog(log) {
|
|
|
28
39
|
}
|
|
29
40
|
}
|
|
30
41
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Each buffer holds one operation's logs in the order they were written, so a
|
|
44
|
+
* read spanning operations has to interleave the buffers rather than join them
|
|
45
|
+
* end to end. Walking back from the newest of each costs the length of the
|
|
46
|
+
* answer rather than the length of the history, and `addLog` giving every log
|
|
47
|
+
* in a TaskManager a unique, increasing timestamp is what orders them.
|
|
48
|
+
*/
|
|
49
|
+
function mergeRecent(buffers, limit, match) {
|
|
50
|
+
// One cursor per buffer, each starting at that buffer's newest log.
|
|
51
|
+
const cursors = buffers.map((buffer) => ({ buffer, at: buffer.length - 1 }));
|
|
52
|
+
const merged = [];
|
|
53
|
+
while (merged.length < limit) {
|
|
54
|
+
let from;
|
|
55
|
+
let newest;
|
|
56
|
+
for (const cursor of cursors) {
|
|
57
|
+
const candidate = cursor.at < 0 ? undefined : cursor.buffer[cursor.at];
|
|
58
|
+
if (!candidate)
|
|
59
|
+
continue;
|
|
60
|
+
if (!newest || candidate.timestamp > newest.timestamp) {
|
|
61
|
+
newest = candidate;
|
|
62
|
+
from = cursor;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!from || !newest)
|
|
66
|
+
break;
|
|
67
|
+
from.at--;
|
|
68
|
+
if (!match || match(newest))
|
|
69
|
+
merged.push(newest);
|
|
38
70
|
}
|
|
39
|
-
return
|
|
71
|
+
return merged.reverse();
|
|
72
|
+
}
|
|
73
|
+
export function getLogsByOp(opId, limit = Infinity) {
|
|
74
|
+
const buffer = this.opLogBuffers.get(opId) || [];
|
|
75
|
+
return buffer.length > limit ? buffer.slice(-limit) : buffer;
|
|
76
|
+
}
|
|
77
|
+
export function getLogsByGroup(group, limit = Infinity) {
|
|
78
|
+
return mergeRecent(Array.from(this.opLogBuffers.values()), limit, (log) => log.group === group);
|
|
40
79
|
}
|
|
41
|
-
export function getAllLogs() {
|
|
42
|
-
|
|
80
|
+
export function getAllLogs(limit = Infinity) {
|
|
81
|
+
const buffers = Array.from(this.opLogBuffers.values());
|
|
82
|
+
// Nothing to interleave, so the buffer already is the answer.
|
|
83
|
+
const only = buffers.length === 1 ? buffers[0] : undefined;
|
|
84
|
+
if (only)
|
|
85
|
+
return only.length > limit ? only.slice(-limit) : only;
|
|
86
|
+
return mergeRecent(buffers, limit);
|
|
43
87
|
}
|
|
44
88
|
export function subscribe(ws, matcher) {
|
|
45
89
|
this.subscribers.add(ws);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosana/node",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.69-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.69-rc",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@coral-xyz/anchor": "^0.28.1-beta.1",
|