dcp-worker 3.2.11 → 3.2.12
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/bin/dcp-worker +381 -80
- package/etc/dcp-config.js +8 -0
- package/lib/remote-console.js +107 -0
- package/lib/startWorkerLogger.js +46 -17
- package/lib/worker-loggers/common-types.js +2 -0
- package/lib/worker-loggers/console.js +42 -15
- package/lib/worker-loggers/dashboard.js +15 -2
- package/lib/worker-loggers/event-log.js +88 -0
- package/lib/worker-loggers/logfile.js +125 -0
- package/lib/worker-loggers/syslog.js +91 -0
- package/package.json +3 -2
- package/lib/bindToTestHarness.js +0 -55
package/bin/dcp-worker
CHANGED
|
@@ -12,15 +12,30 @@ const process = require('process');
|
|
|
12
12
|
const os = require('os');
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// Set to true to try to understand unhandled rejection.
|
|
16
|
+
const ANALYZE_UNHANDELD_REJECTION = false;
|
|
17
|
+
|
|
18
|
+
const TOTAL_CPU_VCORES = os.cpus().length;
|
|
19
|
+
const DEFAULT_CORES = TOTAL_CPU_VCORES - 1;
|
|
16
20
|
var worker, dcpConfig;
|
|
17
21
|
|
|
22
|
+
const EXIT_CLEAN = 0; // normal exit, no error
|
|
23
|
+
const EXIT_SIGQUIT = 2; // received SIGQUIT
|
|
24
|
+
const EXIT_ERROR_STOPPING = 3; // failed to stop worker, no other error
|
|
25
|
+
const EXIT_TIMED_OUT = 4; // failed to exit after requesting graceful exit
|
|
26
|
+
const EXIT_UNHANDLED = 5; // unhandled rejection
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
process.on('unhandledRejection', (error) => {
|
|
30
|
+
console.error('Unhandled rejection:', error);
|
|
31
|
+
});
|
|
32
|
+
|
|
18
33
|
async function main () {
|
|
19
|
-
|
|
34
|
+
if (!ANALYZE_UNHANDELD_REJECTION)
|
|
35
|
+
process.on('unhandledRejection', unhandledRejectionHandler);
|
|
20
36
|
|
|
21
37
|
await require('dcp-client').init(require(process.env.DCP_CONFIG || '../etc/dcp-config'));
|
|
22
38
|
dcpConfig = require('dcp/dcp-config');
|
|
23
|
-
require('../lib/check-scheduler-version').check();
|
|
24
39
|
|
|
25
40
|
const cliArgs = require('dcp/cli')
|
|
26
41
|
.base('Standalone NodeJS DCP Worker')
|
|
@@ -38,15 +53,17 @@ async function main () {
|
|
|
38
53
|
verbose: {
|
|
39
54
|
alias: 'v',
|
|
40
55
|
describe: 'Enable verbose output',
|
|
41
|
-
type: '
|
|
56
|
+
type: 'count',
|
|
42
57
|
default: false,
|
|
58
|
+
group: 'Output options',
|
|
43
59
|
},
|
|
44
60
|
outputMode: {
|
|
45
|
-
alias: 'o',
|
|
61
|
+
alias: ['o', 'output'],
|
|
46
62
|
describe: 'Set the output mode',
|
|
47
63
|
type: 'string',
|
|
48
64
|
default: 'detect',
|
|
49
|
-
choices: ['detect', 'console', 'dashboard'],
|
|
65
|
+
choices: ['detect', 'console', 'dashboard', 'event-log', 'syslog', 'logfile'],
|
|
66
|
+
group: 'Output options',
|
|
50
67
|
},
|
|
51
68
|
hostname: {
|
|
52
69
|
alias: 'H',
|
|
@@ -71,130 +88,220 @@ async function main () {
|
|
|
71
88
|
describe: 'Restrict worker to a specific job (use N times for N jobs)',
|
|
72
89
|
type: 'array',
|
|
73
90
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
hidden: true,
|
|
77
|
-
default: false
|
|
78
|
-
},
|
|
79
|
-
'join': {
|
|
91
|
+
|
|
92
|
+
join: {
|
|
80
93
|
alias: 'g',
|
|
81
|
-
describe: 'Join compute group',
|
|
94
|
+
describe: 'Join compute group; the format is "joinKey,joinSecret" or "joinKey,eh1-joinHash"',
|
|
82
95
|
type: 'array'
|
|
83
96
|
},
|
|
84
|
-
|
|
97
|
+
joinKeystore: {
|
|
85
98
|
hidden: true,
|
|
86
99
|
/* future */
|
|
87
100
|
},
|
|
88
|
-
|
|
101
|
+
|
|
102
|
+
leavePublicGroup: {
|
|
103
|
+
type: 'boolean',
|
|
104
|
+
describe: 'Do not fetch slices from public compute group',
|
|
105
|
+
default: false,
|
|
106
|
+
},
|
|
107
|
+
publicGroupFallback: {
|
|
108
|
+
describe: 'If set, worker will prefer private groups but fall back on the public group if no preferred work is available',
|
|
109
|
+
type: 'boolean',
|
|
110
|
+
default: false,
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
identityKey: {
|
|
114
|
+
describe: 'Identity key, in hex format',
|
|
115
|
+
type: 'string',
|
|
116
|
+
group: 'Identity options',
|
|
117
|
+
},
|
|
118
|
+
identityKeystore: {
|
|
119
|
+
describe: 'Identity keystore, in json format',
|
|
120
|
+
type: 'string',
|
|
121
|
+
group: 'Identity options',
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
reportInterval: {
|
|
125
|
+
describe: 'If set, output a status summary every [interval] seconds in console output mode',
|
|
126
|
+
type: 'number',
|
|
127
|
+
group: 'Output options',
|
|
128
|
+
},
|
|
129
|
+
eventDebug: {
|
|
130
|
+
hide: true,
|
|
131
|
+
describe: 'If set, dump all sandbox and worker events',
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
logfile: {
|
|
135
|
+
describe: 'Path to log file (if --output=file)',
|
|
136
|
+
type: 'string',
|
|
137
|
+
group: 'Log File output options',
|
|
138
|
+
},
|
|
139
|
+
syslogAddress: {
|
|
140
|
+
describe: 'Address of rsyslog server (if --output=syslog)',
|
|
141
|
+
type: 'string',
|
|
142
|
+
group: 'Syslog output options',
|
|
143
|
+
},
|
|
144
|
+
syslogTransport: {
|
|
145
|
+
describe: 'Transport to connect to rsyslog daemon (if --output=syslog)',
|
|
146
|
+
type: 'string',
|
|
147
|
+
choices: ['udp','tcp'],
|
|
148
|
+
group: 'Syslog output options',
|
|
149
|
+
},
|
|
150
|
+
syslogPort: {
|
|
151
|
+
describe: 'UDP/TCP port of rsyslog server',
|
|
152
|
+
type: 'number',
|
|
153
|
+
group: 'Syslog output options',
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
allowedOrigins: {
|
|
89
157
|
alias: 'a',
|
|
90
|
-
describe: '
|
|
158
|
+
describe: 'modify the \'any\' allow origins of dcpConfig',
|
|
91
159
|
type: 'array'
|
|
92
160
|
},
|
|
161
|
+
|
|
162
|
+
replPort: {
|
|
163
|
+
describe: 'If set, open a REPL on specified TCP port',
|
|
164
|
+
type: 'number',
|
|
165
|
+
default: undefined,
|
|
166
|
+
},
|
|
167
|
+
watchdogInterval: {
|
|
168
|
+
alias: 'W',
|
|
169
|
+
describe: 'Number of milliseconds between watchdog cycles',
|
|
170
|
+
type: 'number',
|
|
171
|
+
hidden: true,
|
|
172
|
+
},
|
|
173
|
+
dumpConfig: {
|
|
174
|
+
describe: 'If set, dump the configuration and exit',
|
|
175
|
+
type: 'boolean',
|
|
176
|
+
hidden: true,
|
|
177
|
+
}
|
|
93
178
|
})
|
|
94
179
|
.strict()
|
|
95
180
|
.wrap(process.stdout.columns || 80)
|
|
96
181
|
.argv;
|
|
97
182
|
|
|
98
|
-
|
|
183
|
+
if (cliArgs.dumpConfig)
|
|
184
|
+
{
|
|
185
|
+
console.debug(JSON.stringify(require('dcp/dcp-config'), null, 2));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return startWorking(cliArgs);
|
|
99
190
|
}
|
|
100
191
|
|
|
101
192
|
// Preserve console.error, the dashboard replaces it with a custom logger
|
|
102
193
|
const logError = console.error;
|
|
103
194
|
main()
|
|
104
|
-
.then(
|
|
195
|
+
.then(exitcode => process.exit(exitcode || 0))
|
|
105
196
|
.catch(e => {
|
|
106
197
|
logError("Script failed:");
|
|
107
198
|
logError(e);
|
|
108
199
|
process.exit(1);
|
|
109
200
|
});
|
|
110
201
|
|
|
202
|
+
// Imperfect, but handles CG { joinKey, joinHash }.
|
|
203
|
+
function isHash(b) {
|
|
204
|
+
return b && b.length === 68 && b.startsWith('eh1-');
|
|
205
|
+
}
|
|
206
|
+
|
|
111
207
|
async function startWorking(cliArgs) {
|
|
208
|
+
//console.log('cliArgs', cliArgs);
|
|
112
209
|
const wallet = require('dcp/wallet');
|
|
113
210
|
const DCPWorker = require('dcp/worker').Worker;
|
|
114
211
|
const { startWorkerLogger } = require('../lib/startWorkerLogger');
|
|
115
|
-
const
|
|
116
|
-
/** @type {string[]} */
|
|
117
|
-
var dcpWorkerOptions;
|
|
118
|
-
var paymentAddress;
|
|
119
|
-
var sawOptions = {
|
|
212
|
+
const sawOptions = {
|
|
120
213
|
hostname: cliArgs.hostname,
|
|
121
214
|
port: cliArgs.port
|
|
122
215
|
};
|
|
123
216
|
|
|
124
|
-
|
|
217
|
+
let paymentAddress;
|
|
218
|
+
if (cliArgs.paymentAddress)
|
|
125
219
|
paymentAddress = new wallet.Address(cliArgs.paymentAddress);
|
|
126
|
-
|
|
220
|
+
else
|
|
127
221
|
paymentAddress = (await wallet.get()).address;
|
|
128
|
-
}
|
|
129
222
|
|
|
130
|
-
|
|
223
|
+
|
|
224
|
+
// Different ways to get the identity:
|
|
225
|
+
let identityKeystore = false;
|
|
226
|
+
|
|
227
|
+
if (cliArgs.identityKey)
|
|
228
|
+
identityKeystore = await new wallet.IdKeystore(cliArgs.identityKey, '');
|
|
229
|
+
else if (cliArgs.identityKeystore)
|
|
230
|
+
identityKeystore = await new wallet.IdKeystore(JSON.parse(cliArgs.identityKeystore), '');
|
|
231
|
+
else
|
|
232
|
+
identityKeystore = await wallet.getId();
|
|
233
|
+
|
|
234
|
+
// Set the provided identity as the wallet's default
|
|
235
|
+
await wallet.addId(identityKeystore);
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if (typeof dcpConfig.worker.unhandledRejectionCleanupTimeout !== 'undefined')
|
|
239
|
+
unhandledRejectionHandler.timeout = dcpConfig.worker.unhandledRejectionCleanupTimeout;
|
|
240
|
+
|
|
241
|
+
// Leave the public compute group, if desired
|
|
242
|
+
if (cliArgs.leavePublicGroup || cliArgs.publicGroupFallback)
|
|
243
|
+
dcpConfig.worker.leavePublicGroup = true;
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
// The exitGuard will hold an "exit" method, and a Promise to await for
|
|
247
|
+
// the exit code passed to exitGuard.exit()
|
|
248
|
+
let exitcode = EXIT_CLEAN;
|
|
249
|
+
const exitGuard = {
|
|
250
|
+
promise: Promise.resolve(0), // will be overwritten when worker starts
|
|
251
|
+
exit(code) { process.exit(code||exitcode||0) }, // will be overwritten when worker starts
|
|
252
|
+
};
|
|
253
|
+
process.on('SIGQUIT', () => {
|
|
254
|
+
exitcode = EXIT_SIGQUIT;
|
|
255
|
+
cliArgs.verbose >= 1 && console.info(`240: Caught SIGQUIT; exiting worker with exitcode ${exitcode}`);
|
|
256
|
+
exitGuard.exit(exitcode);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
/** @type {string[]} */
|
|
261
|
+
const dcpWorkerOptions = {
|
|
131
262
|
paymentAddress,
|
|
263
|
+
identity: identityKeystore,
|
|
132
264
|
maxWorkingSandboxes: cliArgs.cores,
|
|
133
|
-
|
|
265
|
+
cores: { cpu: TOTAL_CPU_VCORES, gpu: undefined }, /** XXXpfr @todo: Figure out how many gpus. */
|
|
266
|
+
targetLoad: { cpu: 1.0, gpu: 1.0 }, /** Use 100%: XXXpfr @todo Allow command-line override. */
|
|
134
267
|
sandboxOptions: {
|
|
135
268
|
SandboxConstructor: require('dcp-client/lib/standaloneWorker').workerFactory(sawOptions)
|
|
136
269
|
},
|
|
137
270
|
computeGroups: [], /* public group is implied */
|
|
138
|
-
leavePublicGroup: cliArgs.leavePublicGroup
|
|
271
|
+
leavePublicGroup: cliArgs.leavePublicGroup || dcpConfig.worker.leavePublicGroup,
|
|
139
272
|
};
|
|
140
273
|
|
|
141
274
|
/* cliArgs.join is the list of compute groups to join */
|
|
142
275
|
if (cliArgs.join && cliArgs.join.length)
|
|
143
276
|
{
|
|
144
277
|
dcpWorkerOptions.computeGroups = cliArgs.join
|
|
145
|
-
.map
|
|
146
|
-
|
|
147
|
-
|
|
278
|
+
.map((el) => {
|
|
279
|
+
/* Map cliArgs.join to give us [{ joinKey, joinSecret/joinHash }...] */
|
|
280
|
+
const [a, b] = el.split(',');
|
|
281
|
+
return isHash(b) ? { joinKey: a, joinHash: b } : { joinKey: a, joinSecret: b };
|
|
282
|
+
})
|
|
283
|
+
.filter((el) => el.joinKey); /* Filter out entries with no joinKey */
|
|
284
|
+
//console.log(dcpWorkerOptions.computeGroups);
|
|
148
285
|
}
|
|
149
286
|
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (cliArgs.jobId) {
|
|
287
|
+
if (cliArgs.jobId)
|
|
288
|
+
{
|
|
154
289
|
dcpWorkerOptions.jobAddresses = cliArgs.jobId;
|
|
290
|
+
dcpWorkerOptions.priorityOnly = true;
|
|
155
291
|
}
|
|
156
|
-
if (cliArgs.allowedOrigins)
|
|
157
|
-
|
|
158
|
-
|
|
292
|
+
if (cliArgs.allowedOrigins)
|
|
293
|
+
dcpConfig.worker.allowOrigins.any = cliArgs.allowedOrigins;
|
|
294
|
+
if (cliArgs.watchdogInterval)
|
|
295
|
+
dcpWorkerOptions.watchdogInterval = cliArgs.watchdogInterval;
|
|
159
296
|
|
|
160
|
-
worker = new DCPWorker(dcpWorkerOptions);
|
|
161
|
-
|
|
162
|
-
if (process.env.TEST_HARNESS) {
|
|
163
|
-
const { bindToTestHarness } = require('../lib/bindToTestHarness');
|
|
164
|
-
bindToTestHarness(worker);
|
|
165
|
-
}
|
|
166
297
|
|
|
167
|
-
|
|
168
|
-
setImmediate(() => {
|
|
169
|
-
console.log(` * Starting DCP Worker`);
|
|
170
|
-
console.log(` . Configured for scheduler ${dcpConfig.scheduler.location}`);
|
|
171
|
-
console.log(` . Bank is ${dcpConfig.bank.location}`);
|
|
172
|
-
console.log(` . Earned funds will be deposited in account ${paymentAddress}`);
|
|
173
|
-
console.log(` . Identity is ${identityKeystore.address}`);
|
|
174
|
-
|
|
175
|
-
function qty(amount, singular, plural) /* XXX i18n */
|
|
176
|
-
{
|
|
177
|
-
if (Array.isArray(amount))
|
|
178
|
-
amount = amount.length;
|
|
179
|
-
if (!plural)
|
|
180
|
-
plural = singular + 's';
|
|
181
|
-
if (!amount)
|
|
182
|
-
return plural;
|
|
183
|
-
if (amount == 1)
|
|
184
|
-
return singular;
|
|
185
|
-
return plural;
|
|
186
|
-
}
|
|
298
|
+
worker = new DCPWorker(dcpWorkerOptions);
|
|
187
299
|
|
|
188
|
-
if (dcpWorkerOptions.jobAddresses)
|
|
189
|
-
console.log(` * Processing only ${qty(dcpWorkerOptions.jobAddresses, 'job')}`, dcpWorkerOptions.jobAddresses.join(', '));
|
|
190
|
-
if (dcpWorkerOptions.computeGroups.length)
|
|
191
|
-
console.log(` * Joining compute ${qty(dcpWorkerOptions.computeGroups, 'group')}`, dcpWorkerOptions.computeGroups.map(el => el.joinKey).join(', '));
|
|
192
|
-
if (dcpWorkerOptions.leavePublicGroup)
|
|
193
|
-
console.log(' * Leaving the public compute group');
|
|
194
|
-
console.log(' . ready');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
300
|
/**
|
|
301
|
+
* NOTE: In Supervisor2 this function is a NOOP.
|
|
302
|
+
* When (and if) we stop using Supevisor1, delete this reference to setDefaultIdentityKeystore
|
|
303
|
+
* and delete the corresponding fucntion from Supervisor2.
|
|
304
|
+
*
|
|
198
305
|
* startWorkerLogger needs to be called before the worker is started so that
|
|
199
306
|
* it can attach event listeners before the events fire, else UI events for
|
|
200
307
|
* things such as progress will never get attached.
|
|
@@ -207,15 +314,208 @@ async function startWorking(cliArgs) {
|
|
|
207
314
|
* implementation of the worker that should be addressed in Supervisor 2
|
|
208
315
|
*/
|
|
209
316
|
await worker.supervisor.setDefaultIdentityKeystore();
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
if (cliArgs.eventDebug)
|
|
320
|
+
{
|
|
321
|
+
worker.debug = true;
|
|
322
|
+
worker.supervisor.debug = true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
// if the worker stops internally (eg. schedmsg stop), then exit without
|
|
327
|
+
// changing the saved exitcode
|
|
328
|
+
worker.on('stop', () => {
|
|
329
|
+
exitGuard.exit();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
|
|
210
333
|
startWorkerLogger(worker, {
|
|
334
|
+
exitGuard,
|
|
211
335
|
verbose: cliArgs.verbose,
|
|
212
336
|
outputMode: cliArgs.outputMode,
|
|
337
|
+
|
|
338
|
+
logfile: cliArgs.logfile,
|
|
339
|
+
|
|
340
|
+
syslogAddress: cliArgs.syslogAddress,
|
|
341
|
+
syslogTransport: cliArgs.syslogTransport,
|
|
342
|
+
syslogPort: cliArgs.syslogPort,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
require('../lib/remote-console').init(cliArgs.replPort, {
|
|
346
|
+
help: {
|
|
347
|
+
report: 'Print a worker status & slice report',
|
|
348
|
+
kill: 'Kill the worker',
|
|
349
|
+
},
|
|
350
|
+
commands: {
|
|
351
|
+
report: printReport,
|
|
352
|
+
kill: exitcode => exitGuard.exit(exitcode),
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
require('../lib/remote-console').setMainEval(function mainEval() { return eval(arguments[0]) });
|
|
356
|
+
|
|
357
|
+
console.log(` * Starting DCP Worker`);
|
|
358
|
+
console.log(` . Configured for scheduler ${dcpConfig.scheduler.location}`);
|
|
359
|
+
console.log(` . Bank is ${dcpConfig.bank.location}`);
|
|
360
|
+
console.log(` . Earned funds will be deposited in account ${paymentAddress}`);
|
|
361
|
+
console.log(` . Identity is ${identityKeystore.address}`);
|
|
362
|
+
|
|
363
|
+
function qty(amount, singular, plural) /* XXX i18n */
|
|
364
|
+
{
|
|
365
|
+
if (Array.isArray(amount))
|
|
366
|
+
amount = amount.length;
|
|
367
|
+
if (!plural)
|
|
368
|
+
plural = singular + 's';
|
|
369
|
+
if (!amount)
|
|
370
|
+
return plural;
|
|
371
|
+
if (amount == 1)
|
|
372
|
+
return singular;
|
|
373
|
+
return plural;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (dcpWorkerOptions.jobAddresses)
|
|
377
|
+
console.log(` * Processing only ${qty(dcpWorkerOptions.jobAddresses, 'job')}`, dcpWorkerOptions.jobAddresses.join(', '));
|
|
378
|
+
if (dcpWorkerOptions.computeGroups.length)
|
|
379
|
+
console.log(` * Joining compute ${qty(dcpWorkerOptions.computeGroups, 'group')}`, dcpWorkerOptions.computeGroups.map(el => el.joinKey).join(', '));
|
|
380
|
+
if (dcpWorkerOptions.publicGroupFallback)
|
|
381
|
+
console.log(' * Falling back on public group when preferred groups have no work');
|
|
382
|
+
else if (dcpWorkerOptions.leavePublicGroup)
|
|
383
|
+
console.log(' * Leaving the public compute group');
|
|
384
|
+
if (cliArgs.verbose)
|
|
385
|
+
console.log(` + Verbosity level: ${cliArgs.verbose}`);
|
|
386
|
+
if (cliArgs.eventDebug)
|
|
387
|
+
console.log(' + Event debug on');
|
|
388
|
+
console.log(' . output mode: ' + cliArgs.output);
|
|
389
|
+
|
|
390
|
+
require('../lib/check-scheduler-version').check();
|
|
391
|
+
|
|
392
|
+
console.log(' . ready');
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
/** print the slice report via console.log */
|
|
396
|
+
function printReport()
|
|
397
|
+
{
|
|
398
|
+
console.log(sliceReport());
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** retrieve a slice report screen */
|
|
402
|
+
function sliceReport()
|
|
403
|
+
{
|
|
404
|
+
const sup = worker.supervisor;
|
|
405
|
+
let report = '';
|
|
406
|
+
|
|
407
|
+
report += ('='.repeat(78)) + '\n';
|
|
408
|
+
|
|
409
|
+
const sbStates = {
|
|
410
|
+
WORKING: 0,
|
|
411
|
+
ASSIGNED: 0,
|
|
412
|
+
READY: 0,
|
|
413
|
+
TERMINATED: 0,
|
|
414
|
+
};
|
|
415
|
+
const stateNames = {
|
|
416
|
+
WORKING: 'Working',
|
|
417
|
+
ASSIGNED: 'Assigned',
|
|
418
|
+
READY: 'Ready',
|
|
419
|
+
TERMINATED: 'Terminated',
|
|
420
|
+
};
|
|
421
|
+
sup.sandboxes.forEach(sb => {
|
|
422
|
+
const { state } = sb;
|
|
423
|
+
if (!sbStates[state])
|
|
424
|
+
sbStates[state] = 0;
|
|
425
|
+
sbStates[state]++;
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
report += (Date()) + '\n';
|
|
429
|
+
report += ('Sandboxes:') + '\n';
|
|
430
|
+
Object.keys(sbStates).forEach(state => {
|
|
431
|
+
const stateName = stateNames[state] || state;
|
|
432
|
+
report += (` ${(stateName + ':').padEnd(12)} ${sbStates[state]}`) + '\n';
|
|
433
|
+
})
|
|
434
|
+
report += (` * ALL: ${sup.sandboxes.length}`) + '\n';
|
|
435
|
+
|
|
436
|
+
report += ('Progress:') + '\n';
|
|
437
|
+
sup.workingSandboxes.forEach(sb => {
|
|
438
|
+
const jobName = sb.job && sb.job.public && sb.job.public.name || `idek (${sb.jobAddress})`;
|
|
439
|
+
let el = Date.now() - sb.sliceStartTime;
|
|
440
|
+
const t = el < 1000000
|
|
441
|
+
? toInterval(el)
|
|
442
|
+
: 'new';
|
|
443
|
+
|
|
444
|
+
el = sb.progressReports && sb.progressReports.last
|
|
445
|
+
? Date.now() - (sb.sliceStartTime + sb.progressReports.last.timestamp)
|
|
446
|
+
: 0;
|
|
447
|
+
const pct = (typeof sb.progress) === 'number'
|
|
448
|
+
? `${Number(sb.progress).toFixed(0).padStart(2)}%`
|
|
449
|
+
: 'ind';
|
|
450
|
+
const stale = (el < 2000) ? '' : `(stale: ${toInterval(el)})`;
|
|
451
|
+
|
|
452
|
+
report += (` ${String(sb.id).padStart(4)}: ${sb.jobAddress} ${jobName.padEnd(34)} `+ `${t} ${pct} ${stale}`.padStart(13)) + '\n';
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
report += ('Slices:') + '\n';
|
|
456
|
+
report += (` working: ${sup.allocatedSlices.length}`) + '\n';
|
|
457
|
+
report += (` queued: ${sup.queuedSlices.length}`) + '\n';
|
|
458
|
+
|
|
459
|
+
report += ('='.repeat(78)) + '\n';
|
|
460
|
+
|
|
461
|
+
return report;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Convert a timespan in ms to a human-readable interval in minutes and seconds
|
|
466
|
+
*
|
|
467
|
+
* @param {number} el Milliseconds to convert
|
|
468
|
+
* @return {string} Timespan formatted as `m:ss`
|
|
469
|
+
*/
|
|
470
|
+
function toInterval(el)
|
|
471
|
+
{
|
|
472
|
+
const m = Math.floor((el / 1000) / 60).toString(10);
|
|
473
|
+
const s = Math.floor((el / 1000) % 60).toString(10).padStart(2, '0');
|
|
474
|
+
return `${m}:${s}`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (parseFloat(cliArgs.reportInterval))
|
|
478
|
+
{
|
|
479
|
+
if (cliArgs.outputMode !== 'dashboard')
|
|
480
|
+
setInterval(printReport, parseFloat(cliArgs.reportInterval) * 1000);
|
|
481
|
+
else
|
|
482
|
+
console.log('Ignoring --reportInterval in dashboard output mode');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
// Set the exit guard - this method can be called by signal and exception
|
|
487
|
+
// handlers
|
|
488
|
+
exitGuard.promise = new Promise(resolve => {
|
|
489
|
+
exitGuard.exit = resolve;
|
|
213
490
|
});
|
|
214
491
|
|
|
492
|
+
|
|
215
493
|
await worker.start();
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
494
|
+
|
|
495
|
+
exitcode = await exitGuard.promise;
|
|
496
|
+
|
|
497
|
+
cliArgs.verbose >= 1 && console.log(`418: exit guard called with ${exitcode}`);
|
|
498
|
+
|
|
499
|
+
const exitTimeAllowed = 30; // seconds to allow for worker to stop gracefully
|
|
500
|
+
const forceExitTimeout = setTimeout(() => {
|
|
501
|
+
console.error(`396: Worker failed to exit within ${exitTimeAllowed} seconds; terminating forcibly.`);
|
|
502
|
+
process.exit(exitcode || EXIT_TIMED_OUT)
|
|
503
|
+
}, exitTimeAllowed * 1000);
|
|
504
|
+
|
|
505
|
+
await worker.stop(true)
|
|
506
|
+
.catch(error => {
|
|
507
|
+
if (error.message.includes('Already stopped'))
|
|
508
|
+
return;
|
|
509
|
+
console.error('255: Unexpected error stopping worker:',
|
|
510
|
+
error.code
|
|
511
|
+
? `${error.code}: ${error.message}`
|
|
512
|
+
: error.message);
|
|
513
|
+
exitcode = exitcode || EXIT_ERROR_STOPPING;
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
clearTimeout(forceExitTimeout);
|
|
517
|
+
|
|
518
|
+
return exitcode;
|
|
219
519
|
}
|
|
220
520
|
|
|
221
521
|
/**
|
|
@@ -234,7 +534,7 @@ async function unhandledRejectionHandler (error) {
|
|
|
234
534
|
|
|
235
535
|
try {
|
|
236
536
|
let log = dcpConfig && dcpConfig.worker && dcpConfig.worker.unhandledRejectionLog;
|
|
237
|
-
log = process.env.DCP_WORKER_UNHANDLED_REJECTION_LOG;
|
|
537
|
+
if (!log) log = process.env.DCP_WORKER_UNHANDLED_REJECTION_LOG;
|
|
238
538
|
if (log) {
|
|
239
539
|
fs.appendFileSync(process.env.DCP_WORKER_UNHANDLED_REJECTION_LOG,
|
|
240
540
|
`${Date.now()}: ${error.message}\n${error.stack}\n\n`);
|
|
@@ -247,6 +547,7 @@ async function unhandledRejectionHandler (error) {
|
|
|
247
547
|
if (screen) {
|
|
248
548
|
screen.log(error.message + '\n' + error.stack);
|
|
249
549
|
screen.destroy();
|
|
550
|
+
logError(error.message + '\n' + error.stack);
|
|
250
551
|
} else {
|
|
251
552
|
console.error('Unhandled rejection - preparing to exit:', error.message);
|
|
252
553
|
}
|
|
@@ -260,7 +561,7 @@ async function unhandledRejectionHandler (error) {
|
|
|
260
561
|
} catch(e) {
|
|
261
562
|
console.error(error);
|
|
262
563
|
}
|
|
263
|
-
process.exit(exitCode ||
|
|
564
|
+
process.exit(exitCode || EXIT_UNHANDLED);
|
|
264
565
|
}
|
|
265
566
|
setTimeout(bail, 1000 * unhandledRejectionHandler.timeout);
|
|
266
567
|
|
|
@@ -270,6 +571,6 @@ async function unhandledRejectionHandler (error) {
|
|
|
270
571
|
console.log('Error during worker.stop:', e);
|
|
271
572
|
}
|
|
272
573
|
|
|
273
|
-
setImmediate(() => bail(
|
|
574
|
+
setImmediate(() => bail(EXIT_UNHANDLED));
|
|
274
575
|
};
|
|
275
576
|
unhandledRejectionHandler.timeout = 5;
|
package/etc/dcp-config.js
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file remote-console.js
|
|
3
|
+
* DCP Service Worker support for a remote console, accessible via telnet.
|
|
4
|
+
*
|
|
5
|
+
* * SECURITY NOTICE *
|
|
6
|
+
*
|
|
7
|
+
* This feature is turned off by default and should not be turned on except for
|
|
8
|
+
* troubleshooting, as any attacker on the network will have the same OS-level
|
|
9
|
+
* priviledges as the DCP Service Worker.
|
|
10
|
+
*
|
|
11
|
+
* To enable this feature, create a file in ../etc name enabled-debug-console. That
|
|
12
|
+
* file should contain a number which identities the port number this service will
|
|
13
|
+
* listen on.
|
|
14
|
+
*
|
|
15
|
+
* The port file could also contain the string "false", which is just an explicit
|
|
16
|
+
* way of turning this feature off.
|
|
17
|
+
*
|
|
18
|
+
* A history file will also be created in the etc directory, assuming the service
|
|
19
|
+
* worker has permission to write there.
|
|
20
|
+
*
|
|
21
|
+
* @author Wes Garland, wes@kingsds.network
|
|
22
|
+
* @date Dec 2021
|
|
23
|
+
*/
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
var dcpConfig;
|
|
29
|
+
var mainEval;
|
|
30
|
+
var ci;
|
|
31
|
+
|
|
32
|
+
function daemonEval()
|
|
33
|
+
{
|
|
34
|
+
try {
|
|
35
|
+
if (typeof dcpConfig === 'undefined')
|
|
36
|
+
dcpConfig = require('dcp/dcp-config');
|
|
37
|
+
}
|
|
38
|
+
catch(e){};
|
|
39
|
+
|
|
40
|
+
if (mainEval)
|
|
41
|
+
return mainEval(arguments[0]);
|
|
42
|
+
return eval(arguments[0]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function callbackTelnet(port, client, registry) {
|
|
46
|
+
console.log(' ! telnetd - listening on port', port);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Function to let this module know about eval within the context of the main function in
|
|
51
|
+
* the service worker.
|
|
52
|
+
*/
|
|
53
|
+
exports.setMainEval = function removeConsole$$setMainEval()
|
|
54
|
+
{
|
|
55
|
+
mainEval = arguments[0];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.init = function remoteConsole$$init(port, ...commands)
|
|
59
|
+
{
|
|
60
|
+
try
|
|
61
|
+
{
|
|
62
|
+
let historyFilename;
|
|
63
|
+
|
|
64
|
+
if (!port)
|
|
65
|
+
{
|
|
66
|
+
/* invoked before main */
|
|
67
|
+
const edcFilename = path.resolve(__dirname, '..', 'etc', 'enable-debug-console');
|
|
68
|
+
if (!fs.existsSync(edcFilename))
|
|
69
|
+
return;
|
|
70
|
+
|
|
71
|
+
port = JSON.parse(fs.readFileSync(edcFilename, 'ascii').trim());
|
|
72
|
+
if (!port)
|
|
73
|
+
return;
|
|
74
|
+
historyFilename = edcFilename + '.history';
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
{
|
|
78
|
+
historyFilename = path.resolve(__dirname, '..', 'etc', `telnetd-repl-port-${port}.history`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (port)
|
|
82
|
+
{
|
|
83
|
+
console.warn(`*** Enabling telnet daemon on port ${port} (security risk) ***`);
|
|
84
|
+
ci = require('telnet-console').start({
|
|
85
|
+
port,
|
|
86
|
+
callbackTelnet,
|
|
87
|
+
eval: daemonEval,
|
|
88
|
+
histfile: historyFilename,
|
|
89
|
+
}, ...commands);
|
|
90
|
+
exports.init = () => {
|
|
91
|
+
throw new Error('remote console has already been initialized');
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch(e)
|
|
96
|
+
{
|
|
97
|
+
console.warn(' ! Failed to enable telnet daemon:', e.message);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
exports.reintercept = function remoteConsole$$reintercept()
|
|
102
|
+
{
|
|
103
|
+
if (typeof ci === 'undefined')
|
|
104
|
+
return; /* no interception => no reinterception */
|
|
105
|
+
|
|
106
|
+
ci.reintercept();
|
|
107
|
+
}
|
package/lib/startWorkerLogger.js
CHANGED
|
@@ -22,14 +22,36 @@ function getLogger({ outputMode='detect' }) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return require('./worker-loggers/dashboard');
|
|
30
|
-
default:
|
|
31
|
-
throw new Error(`Unknown outputMode "${outputMode}"`);
|
|
25
|
+
try
|
|
26
|
+
{
|
|
27
|
+
const om = require('path').basename(outputMode);
|
|
28
|
+
return require('./worker-loggers/' + om);
|
|
32
29
|
}
|
|
30
|
+
catch (error)
|
|
31
|
+
{
|
|
32
|
+
console.error(`032: Failed to load worker logger "${outputMode}":`, error);
|
|
33
|
+
throw new Error(`Unknown outputMode "${outputMode}"`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const workerEvents = {
|
|
38
|
+
fetchStart: 'onFetchingSlices',
|
|
39
|
+
fetchEnd: 'onFetchedSlices',
|
|
40
|
+
fetchError: 'onFetchSlicesFailed',
|
|
41
|
+
submit: 'onSubmit',
|
|
42
|
+
submitError: 'onSubmitError',
|
|
43
|
+
payment: 'onPayment',
|
|
44
|
+
error: 'onError',
|
|
45
|
+
warning: 'onWarning',
|
|
46
|
+
}
|
|
47
|
+
const supervisorEvents = {
|
|
48
|
+
submittingResult: 'onSubmitStart',
|
|
49
|
+
}
|
|
50
|
+
const sandboxEvents = {
|
|
51
|
+
start: 'sandbox$onSliceStart',
|
|
52
|
+
sliceProgress: 'sandbox$onSliceProgress',
|
|
53
|
+
sliceFinish: 'sandbox$onSliceFinish',
|
|
54
|
+
terminated: 'sandbox$onWorkerStop',
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
Object.assign(exports, {
|
|
@@ -40,28 +62,35 @@ Object.assign(exports, {
|
|
|
40
62
|
*
|
|
41
63
|
* @param {Worker} worker
|
|
42
64
|
* @param {object} options
|
|
43
|
-
* @param {
|
|
65
|
+
* @param {number} options.verbose
|
|
44
66
|
* @param {boolean} options.outputMode - which logger to use (default='detect')
|
|
45
67
|
*/
|
|
46
68
|
startWorkerLogger(worker, options={}) {
|
|
47
69
|
const logger = getLogger(options);
|
|
48
70
|
logger.init(worker, options);
|
|
49
71
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
for (const [ev, handler] of Object.entries(workerEvents))
|
|
73
|
+
{
|
|
74
|
+
if (typeof logger[handler] === 'function')
|
|
75
|
+
worker.on(ev, logger[handler].bind(logger));
|
|
76
|
+
}
|
|
77
|
+
for (const [ev, handler] of Object.entries(supervisorEvents))
|
|
78
|
+
{
|
|
79
|
+
if (typeof logger[handler] === 'function')
|
|
80
|
+
worker.supervisor.on(ev, logger[handler].bind(logger));
|
|
81
|
+
}
|
|
82
|
+
|
|
53
83
|
worker.on('sandbox', (sandbox) => {
|
|
54
84
|
/**
|
|
55
85
|
* logger.onSandboxStart can return a data object that will be provided to
|
|
56
86
|
* the other sandbox event handlers
|
|
57
87
|
*/
|
|
58
88
|
const data = logger.onSandboxReady(sandbox) || {};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
for (const [ev, handler] of Object.entries(sandboxEvents))
|
|
90
|
+
{
|
|
91
|
+
if (typeof logger[handler] === 'function')
|
|
92
|
+
sandbox.on(ev, logger[handler].bind(logger, sandbox, data));
|
|
93
|
+
}
|
|
63
94
|
});
|
|
64
|
-
|
|
65
|
-
worker.on('payment', logger.onPayment.bind(logger));
|
|
66
95
|
}
|
|
67
96
|
});
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* @property {function} onFetchingSlices
|
|
6
6
|
* @property {function} onFetchedSlices
|
|
7
7
|
* @property {function} onFetchSlicesFailed
|
|
8
|
+
* @property {function} onError
|
|
9
|
+
* @property {function} onWarning
|
|
8
10
|
* @property {SandboxCallback} sandbox$onSliceStart
|
|
9
11
|
* @property {SandboxCallback} sandbox$onSliceProgress
|
|
10
12
|
* @property {SandboxCallback} sandbox$onSliceFinish
|
|
@@ -14,24 +14,23 @@ const consoleLogger = {
|
|
|
14
14
|
init(worker, options) {
|
|
15
15
|
this.worker = worker;
|
|
16
16
|
this.supervisor = worker.supervisor;
|
|
17
|
-
this.options = options;
|
|
17
|
+
this.options = Object.assign({}, options);
|
|
18
18
|
},
|
|
19
19
|
|
|
20
20
|
onSandboxReady(sandbox) {
|
|
21
|
-
const
|
|
22
|
-
const shortId = (zeroes + sandbox.id.toString(16).toUpperCase()).slice(-zeroes.length);
|
|
21
|
+
const shortId = sandbox.id.toString(10).padStart(3);
|
|
23
22
|
|
|
24
23
|
const sandboxData = {
|
|
25
24
|
shortId,
|
|
26
25
|
};
|
|
27
26
|
|
|
28
|
-
console.log(`
|
|
27
|
+
console.log(` * Sandbox ${sandboxData.shortId}: Initialized`);
|
|
29
28
|
|
|
30
29
|
return sandboxData;
|
|
31
30
|
},
|
|
32
31
|
|
|
33
32
|
sandbox$onSliceStart(sandbox, sandboxData, slice) {
|
|
34
|
-
console.log(`
|
|
33
|
+
console.log(` * Sandbox ${sandboxData.shortId}: Slice Started: ${sandbox.jobAddress} ${sandbox.public.name}`);
|
|
35
34
|
},
|
|
36
35
|
|
|
37
36
|
sandbox$onSliceProgress(sandbox, sandboxData, ev) {
|
|
@@ -39,38 +38,66 @@ const consoleLogger = {
|
|
|
39
38
|
},
|
|
40
39
|
|
|
41
40
|
sandbox$onSliceFinish(sandbox, sandboxData, ev) {
|
|
42
|
-
console.log(`
|
|
41
|
+
console.log(` * Sandbox ${sandboxData.shortId}: Slice Completed: ${sandbox.jobAddress} ${sandbox.public.name}`);
|
|
43
42
|
},
|
|
44
43
|
|
|
45
44
|
sandbox$onWorkerStop(sandbox, sandboxData, _event) {
|
|
46
|
-
const job = sandbox.
|
|
47
|
-
console.log(
|
|
48
|
-
` ~ [Sandbox 0x${sandboxData.shortId}] Terminated - Job: ${job}`,
|
|
49
|
-
);
|
|
45
|
+
const job = sandbox.public ? `${sandbox.jobAddress} ${sandbox.public.name}` : sandbox.job;
|
|
46
|
+
console.log(` * Sandbox ${sandboxData.shortId}: Terminated: ${job}`);
|
|
50
47
|
},
|
|
51
48
|
|
|
52
49
|
onPayment({ payment }) {
|
|
53
50
|
try {
|
|
54
51
|
payment = parseFloat(payment);
|
|
55
52
|
} catch (e) {
|
|
56
|
-
console.error("Failed to parse payment
|
|
53
|
+
console.error(" ! Failed to parse payment:", payment);
|
|
57
54
|
return;
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
console.log(`DCC Credit: ${payment}`);
|
|
57
|
+
if (payment > 0) console.log(` * DCC Credit: ${payment.toFixed(3)}`);
|
|
61
58
|
},
|
|
62
59
|
|
|
63
60
|
onFetchingSlices() {
|
|
64
|
-
this.options.verbose && console.log("Fetching slices...");
|
|
61
|
+
this.options.verbose && console.log(" * Fetching slices...");
|
|
65
62
|
},
|
|
66
63
|
|
|
67
64
|
onFetchedSlices(ev) {
|
|
68
|
-
this.options.verbose && console.log("
|
|
65
|
+
this.options.verbose && console.log(" * Fetched", ev, 'slices');
|
|
69
66
|
},
|
|
70
67
|
|
|
71
68
|
onFetchSlicesFailed(ev) {
|
|
72
|
-
|
|
69
|
+
console.log(" ! Failed to fetch slices:", ev);
|
|
73
70
|
},
|
|
71
|
+
|
|
72
|
+
onSubmitStart() {
|
|
73
|
+
this.options.verbose >= 2 && console.log(" * Submitting results...");
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
onSubmit() {
|
|
77
|
+
this.options.verbose >= 2 && console.log(" * Submitted");
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
onSubmitError(ev) {
|
|
81
|
+
console.log(" ! Failed to submit results:", ev);
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
onError(ev) {
|
|
85
|
+
this.options.verbose && console.error(" ! Worker error:", ev);
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
onWarning(ev) {
|
|
89
|
+
this.options.verbose >= 2 && console.warn(" ! Worker warning:", ev);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
onError(ev)
|
|
93
|
+
{
|
|
94
|
+
this.options.verbose && console.error("Worker Error:", ev);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
onWarning(ev)
|
|
98
|
+
{
|
|
99
|
+
this.options.verbose && console.warn("Worker Warning:", ev);
|
|
100
|
+
}
|
|
74
101
|
};
|
|
75
102
|
|
|
76
103
|
Object.assign(exports, consoleLogger);
|
|
@@ -25,8 +25,9 @@ const SLICE_FETCH_STATUS = {
|
|
|
25
25
|
|
|
26
26
|
/** @type {WorkerLogger} */
|
|
27
27
|
const dashboardLogger = {
|
|
28
|
-
init(worker) {
|
|
28
|
+
init(worker, options) {
|
|
29
29
|
this.worker = worker;
|
|
30
|
+
this.options = options;
|
|
30
31
|
this.supervisor = worker.supervisor;
|
|
31
32
|
this.totalDCCs = 0;
|
|
32
33
|
this.sliceFetchStatus = SLICE_FETCH_STATUS.IDLE;
|
|
@@ -72,7 +73,11 @@ const dashboardLogger = {
|
|
|
72
73
|
setInterval(() => this.screen.render(), 1000);
|
|
73
74
|
|
|
74
75
|
this.screen.key(['escape', 'C-c'], () => {
|
|
75
|
-
|
|
76
|
+
console.log('076: Exit requested...');
|
|
77
|
+
if (typeof options.exitGuard?.exit === 'function')
|
|
78
|
+
options.exitGuard.exit(0);
|
|
79
|
+
else
|
|
80
|
+
process.exit(0);
|
|
76
81
|
});
|
|
77
82
|
},
|
|
78
83
|
|
|
@@ -168,6 +173,14 @@ const dashboardLogger = {
|
|
|
168
173
|
this.sliceFetchStatus = SLICE_FETCH_STATUS.NO_WORK;
|
|
169
174
|
this.updateWorkerInfo();
|
|
170
175
|
},
|
|
176
|
+
|
|
177
|
+
onError(error) {
|
|
178
|
+
this.options.verbose && console.error('Unexpected error from worker:', error);
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
onWarning(error) {
|
|
182
|
+
this.options.verbose >= 2 && console.warn('Unexpected warning from worker:', error);
|
|
183
|
+
},
|
|
171
184
|
};
|
|
172
185
|
|
|
173
186
|
Object.assign(exports, dashboardLogger);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file worker-loggers/event-log.js
|
|
3
|
+
* @author Eddie Roosenmaallen <eddie@kingsds.network>
|
|
4
|
+
* @date August 2022
|
|
5
|
+
*
|
|
6
|
+
* This logger module emits events to the Windows event-log, writing all
|
|
7
|
+
* console logs to it. Most worker events are passed through to be handled
|
|
8
|
+
* by the console logger.
|
|
9
|
+
*
|
|
10
|
+
* @TODO: This could likely be improved by handling worker events directly
|
|
11
|
+
* and emitting events to the event log more deliberately than just
|
|
12
|
+
* redirecting the base console output. ~ER20220831
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
require('./common-types');
|
|
16
|
+
const consoleLogger = require('./console');
|
|
17
|
+
|
|
18
|
+
const { EventLog } = require('node-eventlog');
|
|
19
|
+
|
|
20
|
+
// Copy the original global console object's properties onto a backup
|
|
21
|
+
const _console = Object.assign({}, console);
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const eventlogLogger = {
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the eventlog worker logger
|
|
27
|
+
*
|
|
28
|
+
* @param {Worker} worker DCP Worker object to log
|
|
29
|
+
* @param {object} options Options for logger behaviour (passed
|
|
30
|
+
* through to consoleLogger)
|
|
31
|
+
*/
|
|
32
|
+
init(worker, options) {
|
|
33
|
+
consoleLogger.init(worker, options);
|
|
34
|
+
|
|
35
|
+
this._processName = require('path').basename(process.mainModule.filename || process.argv0);
|
|
36
|
+
const source = options.source || this._processName || 'dcp-worker';
|
|
37
|
+
// _console.log(`036: creating new EventLog(${source}) client...`);
|
|
38
|
+
this._eventLog = new EventLog(source);
|
|
39
|
+
|
|
40
|
+
['debug','error','info','log','warn'].forEach(level => {
|
|
41
|
+
console[level] = (...args) => this._log(level, ...args);
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
_log(level, ...items) {
|
|
46
|
+
const strBuilder = [`${this._processName}[${process.pid}]:`];
|
|
47
|
+
|
|
48
|
+
items.forEach(i => {
|
|
49
|
+
try {
|
|
50
|
+
switch (typeof i) {
|
|
51
|
+
case 'object':
|
|
52
|
+
strBuilder.push(JSON.stringify(i));
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
strBuilder.push(String(i));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
if (e instanceof TypeError) {
|
|
60
|
+
strBuilder.push('[encoding error: ' + e.message + ']');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Use the string log-level to look up the severity number:
|
|
66
|
+
let severity = {
|
|
67
|
+
error: 'error',
|
|
68
|
+
warn: 'warn',
|
|
69
|
+
log: 'info',
|
|
70
|
+
info: 'info',
|
|
71
|
+
debug: 'info',
|
|
72
|
+
}[level];
|
|
73
|
+
|
|
74
|
+
// _console.debug(`074: about to actually log a line:`, strBuilder, severity);
|
|
75
|
+
return this._eventLog.log(strBuilder.join(' '), severity).catch(error => {
|
|
76
|
+
if (error)
|
|
77
|
+
_console.error('255: Unexpected error writing to event log:', error);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// necessary to keep `this` pointing at the correct thing when we call
|
|
83
|
+
for (const [prop, value] of Object.entries(consoleLogger))
|
|
84
|
+
{
|
|
85
|
+
if (typeof value === 'function')
|
|
86
|
+
exports[prop] = value.bind(consoleLogger);
|
|
87
|
+
}
|
|
88
|
+
Object.assign(exports, eventlogLogger);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file worker-loggers/logfile.js
|
|
3
|
+
* @author Eddie Roosenmaallen <eddie@kingsds.network>
|
|
4
|
+
* @date August 2022
|
|
5
|
+
*
|
|
6
|
+
* This logger module maintains a log file, writing all console logs to it.
|
|
7
|
+
* Most worker events are passed through to the console logger.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
require('./common-types');
|
|
11
|
+
const consoleLogger = require('./console');
|
|
12
|
+
|
|
13
|
+
// Copy the original global console object's properties onto a backup
|
|
14
|
+
const _console = Object.assign({}, console);
|
|
15
|
+
|
|
16
|
+
const logfileLogger = {
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the logfile worker logger
|
|
19
|
+
*
|
|
20
|
+
* @param {Worker} worker DCP Worker object to log
|
|
21
|
+
* @param {object} options Options for logger behaviour
|
|
22
|
+
* @param {string} options.filepath Path to worker file; default: ./dcp-worker.log
|
|
23
|
+
* @param {boolean} options.truncate If true, logfile will be cleared at worker startup
|
|
24
|
+
*/
|
|
25
|
+
init(worker, options) {
|
|
26
|
+
consoleLogger.init(worker, options);
|
|
27
|
+
|
|
28
|
+
this._filepath = options.filepath || './dcp-worker.log';
|
|
29
|
+
this.options = options;
|
|
30
|
+
|
|
31
|
+
options.verbose >= 3 && console.debug('050: constructing LogfileConsole', this._filepath, options);
|
|
32
|
+
|
|
33
|
+
this._getLogFile();
|
|
34
|
+
|
|
35
|
+
// on SIGHUP, close the output stream and open a new one
|
|
36
|
+
process.on('SIGHUP', () => {
|
|
37
|
+
this._getLogFile(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
['debug','error','info','log','warn'].forEach(level => {
|
|
41
|
+
console[level] = (...args) => this._log(level, ...args);
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Return a handle to the WritableStream for this logger, creating one if
|
|
47
|
+
* necessary.
|
|
48
|
+
*
|
|
49
|
+
* @param {boolean} forceNew If truthy, close any existing stream and open
|
|
50
|
+
* a new one
|
|
51
|
+
* @return {fs.WriteStream}
|
|
52
|
+
*/
|
|
53
|
+
_getLogFile(forceNew = false) {
|
|
54
|
+
const fs = require('fs');
|
|
55
|
+
|
|
56
|
+
if (this._file)
|
|
57
|
+
{
|
|
58
|
+
if (!forceNew)
|
|
59
|
+
return this._file;
|
|
60
|
+
|
|
61
|
+
try
|
|
62
|
+
{
|
|
63
|
+
this._file.end();
|
|
64
|
+
}
|
|
65
|
+
catch (err)
|
|
66
|
+
{
|
|
67
|
+
console.error('061: failed to close old log file:', err);
|
|
68
|
+
}
|
|
69
|
+
this._file = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const options = {
|
|
73
|
+
flags: this.options.truncate ? 'w' : 'a', // NYI: cli --truncate
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const file = this._file = fs.createWriteStream(this._filepath, options);
|
|
77
|
+
|
|
78
|
+
// On error, close & recreate the log file
|
|
79
|
+
file.on('error', err => {
|
|
80
|
+
_console.error('082: console-patch::LogFileConsole write error:', err);
|
|
81
|
+
|
|
82
|
+
this._getLogFile(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return file;
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/** Write a log line to the output file. Items will be converted to strings as
|
|
89
|
+
* possible and concatenated after the log-level and timestamp, then written to the
|
|
90
|
+
* current outputStream.
|
|
91
|
+
* @param {string} level Log level.
|
|
92
|
+
* @param {any} ...items Items to log.
|
|
93
|
+
*/
|
|
94
|
+
_log(level = 'none', ...items) {
|
|
95
|
+
const strBuilder = [
|
|
96
|
+
(new Date()).toISOString(),
|
|
97
|
+
level,
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
items.forEach(i => {
|
|
101
|
+
try {
|
|
102
|
+
switch (typeof i) {
|
|
103
|
+
case 'object':
|
|
104
|
+
strBuilder.push(JSON.stringify(i));
|
|
105
|
+
default:
|
|
106
|
+
strBuilder.push(String(i));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
if (e instanceof TypeError) {
|
|
111
|
+
strBuilder.push('[encoding error: ' + e.message + ']');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
this._file.write(strBuilder.join(' ') + '\n');
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
_console.error('131: Unexpected error writing to log file:', error);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
Object.assign(exports, consoleLogger, logfileLogger);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file worker-loggers/syslog.js
|
|
3
|
+
* @author Eddie Roosenmaallen <eddie@kingsds.network>
|
|
4
|
+
* @date August 2022
|
|
5
|
+
*
|
|
6
|
+
* This logger module emits log lines to a remote syslogd, writing all
|
|
7
|
+
* console logs to it. Most worker events are passed through to be handled
|
|
8
|
+
* by the console logger.
|
|
9
|
+
*
|
|
10
|
+
* @TODO: This could likely be improved by handling worker events directly
|
|
11
|
+
* and emitting events to the event log more deliberately than just
|
|
12
|
+
* redirecting the base console output. ~ER20220831
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
require('./common-types');
|
|
16
|
+
const consoleLogger = require('./console');
|
|
17
|
+
|
|
18
|
+
const syslog = require('syslog-client');
|
|
19
|
+
|
|
20
|
+
// Copy the original global console object's properties onto a backup
|
|
21
|
+
const _console = Object.assign({}, console);
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const eventlogLogger = {
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the syslog worker logger
|
|
27
|
+
*
|
|
28
|
+
* @param {Worker} worker DCP Worker object to log
|
|
29
|
+
* @param {object} options Options for logger behaviour (passed
|
|
30
|
+
* through to consoleLogger)
|
|
31
|
+
*/
|
|
32
|
+
init(worker, options) {
|
|
33
|
+
consoleLogger.init(worker, options);
|
|
34
|
+
|
|
35
|
+
this.options = Object.assign({}, options);
|
|
36
|
+
const syslogOptions = {
|
|
37
|
+
transport: options.syslogTransport || 'udp', // tcp, udp, unix, tls
|
|
38
|
+
port: options.syslogPort || 514,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this._syslog = syslog.createClient(options.syslogAddress || '127.0.0.1', options);
|
|
42
|
+
this._processName = require('path').basename(process.mainModule.filename || process.argv0);
|
|
43
|
+
|
|
44
|
+
['debug','error','info','log','warn'].forEach(level => {
|
|
45
|
+
console[level] = (...args) => this._log(level, ...args);
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
_log(level, ...items) {
|
|
50
|
+
const strBuilder = [`${this._processName}[${process.pid}]:`];
|
|
51
|
+
|
|
52
|
+
items.forEach(i => {
|
|
53
|
+
try {
|
|
54
|
+
switch (typeof i) {
|
|
55
|
+
case 'object':
|
|
56
|
+
strBuilder.push(JSON.stringify(i));
|
|
57
|
+
default:
|
|
58
|
+
strBuilder.push(String(i));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
if (e instanceof TypeError) {
|
|
63
|
+
strBuilder.push('[encoding error: ' + e.message + ']');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Use the string log-level to look up the severity number:
|
|
69
|
+
let severity = {
|
|
70
|
+
error: syslog.Severity.Error,
|
|
71
|
+
warn: syslog.Severity.Warning,
|
|
72
|
+
log: syslog.Severity.Notice,
|
|
73
|
+
info: syslog.Severity.Informational,
|
|
74
|
+
debug: syslog.Severity.Debug,
|
|
75
|
+
}[level];
|
|
76
|
+
|
|
77
|
+
this._syslog.log(strBuilder.join(' '), {
|
|
78
|
+
severity,
|
|
79
|
+
}, error => {
|
|
80
|
+
if (error)
|
|
81
|
+
_console.error('168: Unexpected error writing to syslog:', error);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
for (const [prop, value] of Object.entries(consoleLogger))
|
|
87
|
+
{
|
|
88
|
+
if (typeof value === 'function')
|
|
89
|
+
exports[prop] = value.bind(consoleLogger);
|
|
90
|
+
}
|
|
91
|
+
Object.assign(exports, eventlogLogger);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dcp-worker",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.12",
|
|
4
4
|
"description": "JavaScript portion of DCP Workers for Node.js",
|
|
5
5
|
"main": "bin/dcp-worker",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"chalk": "^4.1.1",
|
|
39
39
|
"dcp-client": "^4.2.11",
|
|
40
40
|
"kvin": "^1.2.7",
|
|
41
|
-
"semver": "^7.3.5"
|
|
41
|
+
"semver": "^7.3.5",
|
|
42
|
+
"telnet-console": "^1.0.4"
|
|
42
43
|
},
|
|
43
44
|
"optionalDependencies": {},
|
|
44
45
|
"devDependencies": {
|
package/lib/bindToTestHarness.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file bindToTestHarness.js
|
|
3
|
-
* @author Ryan Rossiter, ryan@kingsds.network
|
|
4
|
-
* @date May 2020
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
function setObjProps(obj, source) {
|
|
8
|
-
for (let p in source) {
|
|
9
|
-
if (typeof source[p] === 'object') setObjProps(obj[p], source[p]);
|
|
10
|
-
else obj[p] = source[p];
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
exports.bindToTestHarness = function (worker) {
|
|
15
|
-
worker.on('start', () => {
|
|
16
|
-
process.send({
|
|
17
|
-
request: 'Process Started'
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
worker.on('fetch', () => {
|
|
22
|
-
process.send({
|
|
23
|
-
request: 'fetchedSlices'
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
worker.on('sandbox', (sandbox) => {
|
|
28
|
-
sandbox.on('sliceStart', () => {
|
|
29
|
-
process.send({
|
|
30
|
-
request: 'sliceStart'
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
worker.supervisor.once('capabilitiesCalculated', (originalCaps) => {
|
|
36
|
-
let reportedCaps = originalCaps;
|
|
37
|
-
|
|
38
|
-
Object.defineProperty(worker.supervisor, 'capabilities', {
|
|
39
|
-
get: () => reportedCaps,
|
|
40
|
-
set: v => reportedCaps = v,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
process.on('message', ({ request, data }) => {
|
|
44
|
-
const [namespace, method] = request.split('::');
|
|
45
|
-
if (namespace !== 'dcpWorker') return
|
|
46
|
-
if (method === 'setCaps') {
|
|
47
|
-
// Deep copy the original caps
|
|
48
|
-
reportedCaps = JSON.parse(JSON.stringify(originalCaps));
|
|
49
|
-
setObjProps(reportedCaps, data);
|
|
50
|
-
}
|
|
51
|
-
else if (method === 'resetCaps') reportedCaps = originalCaps;
|
|
52
|
-
// else if (method === 'reportCaps') console.error(reportedCaps);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|