dcp-worker 4.3.2 → 4.3.4
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/collect-debug-info +192 -0
- package/bin/dcp-evaluator-manager +82 -22
- package/bin/dcp-evaluator-start +93 -40
- package/bin/dcp-worker +1 -1
- package/lib/utils.js +83 -3
- package/lib/worker-consoles/dashboard-console.js +1 -1
- package/lib/worker-info.js +1 -1
- package/package.json +10 -10
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file collect-debug-info
|
|
4
|
+
* Collect information about the host environment and send it to Distributive or and arbitrary
|
|
5
|
+
* e-mail address for analysis. If no command-line options are specified, logs are sent to a
|
|
6
|
+
* TLS syslog target controlled by Distributive. Emails are sent via DCP (https) through the
|
|
7
|
+
* Distributive scheduler.
|
|
8
|
+
*
|
|
9
|
+
* Usage: collect-debug-info [-f] [mailbox@domain]
|
|
10
|
+
*
|
|
11
|
+
* @author Wes Garland, wes@distributive.network
|
|
12
|
+
* @date Nov 2025
|
|
13
|
+
*/
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const process = require('process');
|
|
16
|
+
const readline = require('readline');
|
|
17
|
+
|
|
18
|
+
const { execFileSync } = require('child_process');
|
|
19
|
+
|
|
20
|
+
const argv = process.argv.slice(2);
|
|
21
|
+
var force = false;
|
|
22
|
+
var email;
|
|
23
|
+
var syslogUrl = 'tls://s1565808.eu-nbg-2-vec.betterstackdata.com/?logtail@11993+source_token=kcpVMsbZf9DEvur4MCDd4Fkj';
|
|
24
|
+
|
|
25
|
+
process.stdout.write(`
|
|
26
|
+
*** This program collects information from your computer, and sends it to
|
|
27
|
+
*** Distributive and our analytics partners. This includes your local DCP
|
|
28
|
+
*** configuration, which might include sensitive information such as Compute
|
|
29
|
+
*** Group credentials, or API keys.
|
|
30
|
+
|
|
31
|
+
`);
|
|
32
|
+
|
|
33
|
+
while (argv[0])
|
|
34
|
+
{
|
|
35
|
+
if (argv[0] === '-f' || argv[0] === '--force')
|
|
36
|
+
{
|
|
37
|
+
argv.shift();
|
|
38
|
+
force = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (argv[0] === '-s')
|
|
43
|
+
{
|
|
44
|
+
syslogUrl = argv[0];
|
|
45
|
+
argv.shift();
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (argv[0].startsWith('--syslog='))
|
|
50
|
+
{
|
|
51
|
+
syslogUrl = argv[0].slice(0);
|
|
52
|
+
argv.shift();
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (force)
|
|
60
|
+
{
|
|
61
|
+
go();
|
|
62
|
+
email = argv[0] || 'wes+worker-debug-info@distributive.network';
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
{
|
|
66
|
+
email = argv[0];
|
|
67
|
+
|
|
68
|
+
const rl = readline.createInterface({
|
|
69
|
+
input: process.stdin,
|
|
70
|
+
output: process.stdout
|
|
71
|
+
});
|
|
72
|
+
rl.question(`Proceed? (y/n) `, answer => {
|
|
73
|
+
rl.close();
|
|
74
|
+
switch(answer.toLowerCase())
|
|
75
|
+
{
|
|
76
|
+
case 'y':
|
|
77
|
+
case 'yes':
|
|
78
|
+
console.log();
|
|
79
|
+
go();
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
console.error(`Abort (${answer})`);
|
|
83
|
+
process.exit(2);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function syslogReport(report)
|
|
90
|
+
{
|
|
91
|
+
const syslog = require('../lib/worker-loggers/syslog');
|
|
92
|
+
console.log(' - logging to', syslogUrl);
|
|
93
|
+
syslog.open({ syslogUrl: new URL(syslogUrl) });
|
|
94
|
+
syslog.at('notice', report);
|
|
95
|
+
await syslog.close();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async function emailReport(report)
|
|
100
|
+
{
|
|
101
|
+
const protocol = require('dcp/protocol');
|
|
102
|
+
const worker = require('dcp/worker');
|
|
103
|
+
|
|
104
|
+
const conn = new protocol.Connection(dcpConfig.scheduler.services.mailgw);
|
|
105
|
+
const message = {
|
|
106
|
+
from: `${os.userInfo().username}-${worker.DistributiveWorker.obtainWorkerId()}-worker-${os.hostname()}@distributive.network`,
|
|
107
|
+
fromName: 'collect-debug-info',
|
|
108
|
+
to: email,
|
|
109
|
+
toName: 'DCP Debug Report Receiver',
|
|
110
|
+
subject: `collect-debug-info ${os.userInfo().username}@${os.hostname()}`,
|
|
111
|
+
contentType: 'text/html',
|
|
112
|
+
body: `<html><pre>${report}</pre></html>`
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const response = await conn.request('sendmail', message);
|
|
116
|
+
conn.close();
|
|
117
|
+
console.log(' - sent report to', message.to);
|
|
118
|
+
if (!response.success)
|
|
119
|
+
console.error(`error ${response.payload?.code} sending email:`, response.payload?.message);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function cmd(prog, args)
|
|
123
|
+
{
|
|
124
|
+
const argv = args.slice();
|
|
125
|
+
console.log(' -', prog, argv.join(' '));
|
|
126
|
+
argv.unshift(prog);
|
|
127
|
+
argv.unshift('/c');
|
|
128
|
+
const s = execFileSync('cmd', argv, { encoding: 'utf8' });
|
|
129
|
+
return `------ [ ${prog} ] ---------------------------------------------------------------\n${s}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function sh(prog, args)
|
|
133
|
+
{
|
|
134
|
+
const argv = args.slice();
|
|
135
|
+
console.log(' -', prog, argv.join(' '));
|
|
136
|
+
const s = execFileSync(prog, argv, { encoding: 'utf8', maxBuffer: 1e9 });
|
|
137
|
+
return `------ [ ${prog} ] ---------------------------------------------------------------\n${s}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function go()
|
|
141
|
+
{
|
|
142
|
+
const gigs = Math.pow(2, 30);
|
|
143
|
+
await require('dcp-client').init({
|
|
144
|
+
programName: 'dcp-worker',
|
|
145
|
+
dcpConfig: {
|
|
146
|
+
bundle: { location: false },
|
|
147
|
+
worker: { logging: { syslog: {} } },
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
console.log(' - host information');
|
|
152
|
+
var report = `------ [ ${new Date()} ] ---------------------------------------------------------------
|
|
153
|
+
os: ${os.version()}
|
|
154
|
+
platform: node ${process.version} ${os.platform()} ${os.release()} ${os.machine()} ${os.endianness()}
|
|
155
|
+
uptime: ${Math.floor(os.uptime() / 86400)} days ${Number(((os.uptime() / 86400) % 1) * 86400).toFixed(1)} seconds, load average: ${os.loadavg().join(', ')}
|
|
156
|
+
homedir: ${os.homedir()}
|
|
157
|
+
avail //ism: ${os.availableParallelism()}
|
|
158
|
+
memory: ${os.freemem()/gigs}G / ${os.totalmem()/gigs}G (${Number(os.freemem() / os.totalmem()).toFixed(1)}% free)
|
|
159
|
+
userInfo: ${JSON.stringify(os.userInfo(), null, 2)}
|
|
160
|
+
cpus: ${JSON.stringify(os.cpus, null, 2)}
|
|
161
|
+
network: ${JSON.stringify(os.networkInterfaces, null, 2)}
|
|
162
|
+
more versions: ${JSON.stringify(process.versions, null, 2)}
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
console.log(' - dcp information');
|
|
166
|
+
report +=
|
|
167
|
+
`
|
|
168
|
+
dcpConfig: ${JSON.stringify(dcpConfig, null, 2)}
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
if (os.platform() === 'win32')
|
|
172
|
+
{
|
|
173
|
+
report += await cmd('dir');
|
|
174
|
+
report += await cmd('tasklist');
|
|
175
|
+
report += await cmd('powershell', [ '"eventlog -LogName Applications -newest 100 | FormatTable -wrap"' ]);
|
|
176
|
+
}
|
|
177
|
+
else
|
|
178
|
+
{
|
|
179
|
+
report += await sh('ls', [ '-l' ]);
|
|
180
|
+
report += await sh('ps', [ '-ef' ]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (os.platform() === 'linux')
|
|
184
|
+
{
|
|
185
|
+
report += await sh('journalctl', [ '-n', '100', '-u', 'dcp-worker', '-u', 'dcp-evaluator', '-u', 'dcp' ]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (email)
|
|
189
|
+
await emailReport(report);
|
|
190
|
+
else if (syslogUrl)
|
|
191
|
+
syslogReport(report);
|
|
192
|
+
}
|
|
@@ -25,8 +25,11 @@ const path = require('path');
|
|
|
25
25
|
const net = require('net');
|
|
26
26
|
const dns = require('dns');
|
|
27
27
|
const debug = require('debug');
|
|
28
|
-
const getopt = require('posix-getopt');
|
|
29
28
|
const pidfile = require('../lib/pidfile');
|
|
29
|
+
const loggers = require('../lib/loggers');
|
|
30
|
+
const utils = require('../lib/utils');
|
|
31
|
+
|
|
32
|
+
const { logLevels } = require('../lib/consts');
|
|
30
33
|
|
|
31
34
|
var userActivity = { /* no props true => okay to launch evaluator */
|
|
32
35
|
screensaver: true,
|
|
@@ -59,6 +62,15 @@ const daemonConfig = {
|
|
|
59
62
|
},
|
|
60
63
|
};
|
|
61
64
|
|
|
65
|
+
async function processExit(code)
|
|
66
|
+
{
|
|
67
|
+
if (typeof code === 'undefined')
|
|
68
|
+
code = process.exitCode;
|
|
69
|
+
console.debug('Exit, code', code);
|
|
70
|
+
await loggers.unhook();
|
|
71
|
+
process.exit(code);
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
function usage()
|
|
63
75
|
{
|
|
64
76
|
console.log(
|
|
@@ -123,7 +135,7 @@ async function listenForConnections(config)
|
|
|
123
135
|
server.on('error', (error) => {
|
|
124
136
|
delete error.stack;
|
|
125
137
|
console.error(error);
|
|
126
|
-
|
|
138
|
+
processExit(2);
|
|
127
139
|
});
|
|
128
140
|
|
|
129
141
|
server.listen({ host: hostaddr, port: config.net.port }, function daemonReady() {
|
|
@@ -410,7 +422,7 @@ async function dbusScreenSaverMonitor()
|
|
|
410
422
|
{
|
|
411
423
|
const prettyName = chosen.split('.')[1];
|
|
412
424
|
console.error(`${prettyName} screensaver doesn't support activeness query, screensaver is not started, or user ${os.userInfo().username} doesn't have access to dbus\n ${error}`);
|
|
413
|
-
|
|
425
|
+
processExit(3);
|
|
414
426
|
}
|
|
415
427
|
}
|
|
416
428
|
|
|
@@ -538,15 +550,20 @@ function odRequire(moduleId)
|
|
|
538
550
|
|
|
539
551
|
console.error(`Screensaver operating mode requires optional dependency ${moduleId}.`);
|
|
540
552
|
console.error(`Please \`npm i ${moduleId}\` and try again.`);
|
|
541
|
-
|
|
553
|
+
processExit(2);
|
|
542
554
|
}
|
|
543
555
|
|
|
544
556
|
/* Main program entry point */
|
|
545
557
|
async function main()
|
|
546
558
|
{
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
559
|
+
const loggingOptions = {
|
|
560
|
+
minLevel: { all: undefined }, /* minimum logging levels for console and per logger */
|
|
561
|
+
loggers: [], /* names of loggers (places logs go besides console) */
|
|
562
|
+
syslogUrl: new URL(dcpConfig.worker.logging?.syslog?.url || 'udp://localhost:514/local7'),
|
|
563
|
+
logfile: path.resolve(path.dirname(require.main.filename),
|
|
564
|
+
(dcpConfig.worker.logging?.['evaluator-start-logfile']
|
|
565
|
+
|| '../log/dcp-evaluator-start.log')),
|
|
566
|
+
};
|
|
550
567
|
|
|
551
568
|
if (dcpConfig.evaluator.listen)
|
|
552
569
|
{
|
|
@@ -554,32 +571,36 @@ async function main()
|
|
|
554
571
|
daemonConfig.net.port = dcpConfig.evaluator.listen.port;
|
|
555
572
|
}
|
|
556
573
|
|
|
557
|
-
|
|
574
|
+
for (let { option, optarg, argvSegment } of
|
|
575
|
+
utils.getoptParse(process.argv.slice(2), ''
|
|
576
|
+
+ 'h(help)P:(prefix)H:p:d:(disable-monitor)l:(max-load)r:(rate)'
|
|
577
|
+
+ 'Li:as:(signal)T:(loadavg-type)f:(pidfile)n(no-pidfile)'
|
|
578
|
+
+ '*:(logfile)*(overwrite-logfile)*:(syslog)*:(min-level)'))
|
|
558
579
|
{
|
|
559
|
-
switch (option
|
|
580
|
+
switch (option)
|
|
560
581
|
{
|
|
561
582
|
case 'h':
|
|
562
583
|
usage();
|
|
563
584
|
break;
|
|
564
585
|
|
|
565
586
|
default:
|
|
566
|
-
throw new Error(`defined but unspecified option: -${option
|
|
587
|
+
throw new Error(`defined but unspecified option: -${option}` + (optarg ? `=${optarg}` : ''));
|
|
567
588
|
|
|
568
589
|
case '?':
|
|
569
|
-
|
|
590
|
+
processExit(1);
|
|
570
591
|
break;
|
|
571
592
|
|
|
572
593
|
case 'P':
|
|
573
594
|
{
|
|
574
595
|
const re = new RegExp('^(--prefix=).*$');
|
|
575
596
|
for (let i=0; i < daemonConfig.argv.length; i++)
|
|
576
|
-
daemonConfig.argv[i] = daemonConfig.argv[i].replace(re, '$1' + path.resolve(
|
|
597
|
+
daemonConfig.argv[i] = daemonConfig.argv[i].replace(re, '$1' + path.resolve(optarg + '/'));
|
|
577
598
|
break;
|
|
578
599
|
}
|
|
579
600
|
|
|
580
601
|
case 'd':
|
|
581
602
|
{
|
|
582
|
-
let what =
|
|
603
|
+
let what = optarg;
|
|
583
604
|
|
|
584
605
|
if (what[0] === '=')
|
|
585
606
|
what = what.slice(1);
|
|
@@ -590,7 +611,7 @@ async function main()
|
|
|
590
611
|
|
|
591
612
|
case 'i':
|
|
592
613
|
{
|
|
593
|
-
daemonConfig.session.idleTimeout = Number(
|
|
614
|
+
daemonConfig.session.idleTimeout = Number(optarg).toFixed(1);
|
|
594
615
|
break;
|
|
595
616
|
}
|
|
596
617
|
|
|
@@ -619,31 +640,31 @@ async function main()
|
|
|
619
640
|
|
|
620
641
|
case 'p':
|
|
621
642
|
{
|
|
622
|
-
daemonConfig.net.port = Number(
|
|
643
|
+
daemonConfig.net.port = Number(optarg);
|
|
623
644
|
break;
|
|
624
645
|
}
|
|
625
646
|
|
|
626
647
|
case 'H':
|
|
627
648
|
{
|
|
628
|
-
daemonConfig.net.hostname =
|
|
649
|
+
daemonConfig.net.hostname = optarg;
|
|
629
650
|
break;
|
|
630
651
|
}
|
|
631
652
|
|
|
632
653
|
case 'l':
|
|
633
654
|
{
|
|
634
|
-
daemonConfig.limits.maxLoad = Number(
|
|
655
|
+
daemonConfig.limits.maxLoad = Number(optarg);
|
|
635
656
|
break;
|
|
636
657
|
}
|
|
637
658
|
|
|
638
659
|
case 'r':
|
|
639
660
|
{
|
|
640
|
-
daemonConfig.limits.rateMs = 1000 * Number(
|
|
661
|
+
daemonConfig.limits.rateMs = 1000 * Number(optarg);
|
|
641
662
|
break;
|
|
642
663
|
}
|
|
643
664
|
|
|
644
665
|
case 's':
|
|
645
666
|
{
|
|
646
|
-
const opts =
|
|
667
|
+
const opts = optarg.split(',');
|
|
647
668
|
daemonConfig.signal.which = opts[0] || daemonConfig.signal.which;
|
|
648
669
|
daemonConfig.signal.mode = opts[1] || daemonConfig.signal.mode;
|
|
649
670
|
break;
|
|
@@ -651,13 +672,13 @@ async function main()
|
|
|
651
672
|
|
|
652
673
|
case 'T':
|
|
653
674
|
{
|
|
654
|
-
daemonConfig.limits.loadavgType =
|
|
675
|
+
daemonConfig.limits.loadavgType = optarg;
|
|
655
676
|
break;
|
|
656
677
|
}
|
|
657
678
|
|
|
658
679
|
case 'f':
|
|
659
680
|
{
|
|
660
|
-
daemonConfig.pidfile =
|
|
681
|
+
daemonConfig.pidfile = optarg;
|
|
661
682
|
break;
|
|
662
683
|
}
|
|
663
684
|
|
|
@@ -666,10 +687,43 @@ async function main()
|
|
|
666
687
|
daemonConfig.pidfile = false;
|
|
667
688
|
break;
|
|
668
689
|
}
|
|
690
|
+
case 'syslog':
|
|
691
|
+
loggingOptions.loggers.push('syslog');
|
|
692
|
+
loggingOptions.syslogUrl = utils.makeSyslogUrl(loggingOptions.syslogUrl, optarg);
|
|
693
|
+
break;
|
|
694
|
+
case 'logfile':
|
|
695
|
+
loggingOptions.logfile = optarg;
|
|
696
|
+
loggingOptions.loggers.push('logfile');
|
|
697
|
+
break;
|
|
698
|
+
case 'overwrite-logfile':
|
|
699
|
+
loggingOptions.overwriteLogfile = true;
|
|
700
|
+
break;
|
|
701
|
+
case 'min-level':
|
|
702
|
+
{
|
|
703
|
+
if (optarg.indexOf(',') === -1)
|
|
704
|
+
optarg=`all,${optarg}`;
|
|
705
|
+
const [ which, level ] = optarg.split(',');
|
|
706
|
+
if (which !== 'all' && which !== 'console' && !loggers.exists(which))
|
|
707
|
+
console.error(`--min-level: ${which} is neither 'console' nor the name of a logger`);
|
|
708
|
+
if (!logLevels.lookup(level))
|
|
709
|
+
console.error(`--min-level: ${level} is not a valid log level (${Object.keys(logLevels).join(', ')})`);
|
|
710
|
+
loggingOptions.minLevel[which] = level;
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
case '--':
|
|
714
|
+
daemonConfig.argv = daemonConfig.argv.concat(argvSegment); /* All options after -- pass to dcp-evaluator-start */
|
|
715
|
+
break;
|
|
669
716
|
}
|
|
670
717
|
}
|
|
671
718
|
|
|
672
|
-
|
|
719
|
+
if (dcpConfig.worker.logging?.syslog) loggingOptions.loggers.push('syslog');
|
|
720
|
+
if (dcpConfig.worker.logging?.logfile) loggingOptions.loggers.push('logfile');
|
|
721
|
+
if (dcpConfig.worker.eventLog) loggingOptions.loggers.push('event-log');
|
|
722
|
+
loggingOptions.loggers = utils.uniq(loggingOptions.loggers);
|
|
723
|
+
|
|
724
|
+
const newConsole = require('../lib/worker-consoles').setConsoleType('stdio');
|
|
725
|
+
loggers.hook(newConsole, loggingOptions);
|
|
726
|
+
|
|
673
727
|
if (daemonConfig.pidfile)
|
|
674
728
|
pidfile.write(daemonConfig.pidfile);
|
|
675
729
|
|
|
@@ -696,12 +750,18 @@ async function main()
|
|
|
696
750
|
|
|
697
751
|
/* Initialize dcp-client to use only local resources before launching the main function */
|
|
698
752
|
try {
|
|
753
|
+
process.on('SIGHUP', () => processExit( 1 - 128));
|
|
754
|
+
process.on('SIGINT', () => processExit( 2 - 128));
|
|
755
|
+
process.on('SIGTERM', () => processExit(15 - 128));
|
|
756
|
+
process.on('EXIT', processExit);
|
|
757
|
+
|
|
699
758
|
require('dcp-client').init({
|
|
700
759
|
programName: 'dcp-worker',
|
|
701
760
|
parseArgv: false,
|
|
702
761
|
dcpConfig: {
|
|
703
762
|
scheduler: { configLocation: false },
|
|
704
763
|
bundle: { location: false },
|
|
764
|
+
worker: { logging: { syslog: {} } },
|
|
705
765
|
}
|
|
706
766
|
}).then(main);
|
|
707
767
|
}
|
package/bin/dcp-evaluator-start
CHANGED
|
@@ -13,28 +13,43 @@
|
|
|
13
13
|
*/
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
+
const loggers = require('../lib/loggers');
|
|
17
|
+
const utils = require('../lib/utils');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const { logLevels } = require('../lib/consts');
|
|
21
|
+
|
|
22
|
+
async function processExit(code)
|
|
23
|
+
{
|
|
24
|
+
if (typeof code === 'undefined')
|
|
25
|
+
code = process.exitCode;
|
|
26
|
+
console.debug('Exit, code', code);
|
|
27
|
+
await loggers.unhook();
|
|
28
|
+
process.exit(code);
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
function panic(...argv)
|
|
17
32
|
{
|
|
18
33
|
console.error(...argv);
|
|
19
|
-
|
|
34
|
+
loggers.unhook();
|
|
35
|
+
processExit(1);
|
|
20
36
|
}
|
|
21
37
|
|
|
22
38
|
function main()
|
|
23
39
|
{
|
|
40
|
+
const dcpConfig = require('dcp/dcp-config');
|
|
41
|
+
const loggingOptions = {
|
|
42
|
+
minLevel: { all: undefined }, /* minimum logging levels for console and per logger */
|
|
43
|
+
loggers: [], /* names of loggers (places logs go besides console) */
|
|
44
|
+
syslogUrl: new URL(dcpConfig.worker.logging?.syslog?.url || 'udp://localhost:514/local7'),
|
|
45
|
+
logfile: path.resolve(path.dirname(require.main.filename),
|
|
46
|
+
(dcpConfig.worker.logging?.['evaluator-start-logfile']
|
|
47
|
+
|| '../log/dcp-evaluator-start.log')),
|
|
48
|
+
};
|
|
24
49
|
const { clearLine, cursorTo } = require('readline');
|
|
25
|
-
const { BasicParser } = require('posix-getopt');
|
|
26
50
|
const { expandPath } = require('dcp/utils');
|
|
27
|
-
const path = require('path');
|
|
28
51
|
const pidfile = require('../lib/pidfile')
|
|
29
|
-
const dcpConfig = require('dcp/dcp-config');
|
|
30
52
|
const dcpClientDir = path.dirname(require.resolve('dcp-client'));
|
|
31
|
-
const parser = new BasicParser(
|
|
32
|
-
'h(help)P:(prefix)p:(port)s(stdio)e:(evaluator)r:(rEvaluator)\u1000:(sandbox-libexec-dir)' +
|
|
33
|
-
'\u1001:(sandbox-definitions)t:(sandbox-type)D(dump)T(dump-types)F(dump-files)J(json)v(verbose)' +
|
|
34
|
-
'f:(pidfile)d:(logLevel)',
|
|
35
|
-
process.argv
|
|
36
|
-
);
|
|
37
|
-
|
|
38
53
|
const options = {
|
|
39
54
|
prefix: path.resolve(path.dirname(process.argv[1]), '..'),
|
|
40
55
|
port: Number(dcpConfig.evaluator.listen.port),
|
|
@@ -42,7 +57,7 @@ function main()
|
|
|
42
57
|
sandboxDefinitions: path.resolve(dcpClientDir, 'generated', 'sandbox-definitions.json'),
|
|
43
58
|
sandboxType: 'native',
|
|
44
59
|
evaluator: path.join('bin', 'dcp-evaluator'), /* resolved against prefix later */
|
|
45
|
-
verbose:
|
|
60
|
+
verbose: 0,
|
|
46
61
|
};
|
|
47
62
|
|
|
48
63
|
function help() {
|
|
@@ -75,66 +90,72 @@ Options:
|
|
|
75
90
|
-v, --verbose Generate verbose output
|
|
76
91
|
-f, --pidfile= create a .pid file; default is
|
|
77
92
|
${pidfile.getDefaultPidFileName(dcpConfig.worker.pidfile)}
|
|
78
|
-
-d, --log-level= Select minimum log level for evaluator output
|
|
79
|
-
(debug, info, notice, warn, error, crit, alert, emerg)
|
|
93
|
+
-d, --log-level= Select minimum log level for evaluator's output
|
|
94
|
+
(debug, info, notice, warn, error, crit, alert, emerg).
|
|
95
|
+
Affects only the evaluator itself, not this script.
|
|
96
|
+
--logfile=filename Change filename, implies --logger=logfile
|
|
97
|
+
--overwrite-logfile Overwrite previous log files instead of appending
|
|
98
|
+
--syslog=url Change syslog url, implies --logger=syslog. Syslog
|
|
99
|
+
facility (eg. local7) is the pathname, structured
|
|
100
|
+
data is encoded in search params; id+name=value.
|
|
101
|
+
--syslog=facility Change syslog facility, implies --logger=syslog.
|
|
102
|
+
--min-level=[logger,]level Set the lowest level of log sent to a given logger
|
|
103
|
+
--min-level=[console,]level Set the lowest level of log sent to the console
|
|
80
104
|
`);
|
|
81
105
|
}
|
|
82
106
|
|
|
83
|
-
for (let
|
|
107
|
+
for (let { option, optarg, argvSegment } of
|
|
108
|
+
utils.getoptParse(process.argv.slice(2), ''
|
|
109
|
+
+ 'h(help)P:(prefix)p:(port)s(stdio)e:(evaluator)r:(rEvaluator)'
|
|
110
|
+
+ '*:(sandbox-libexec-dir)*:(sandbox-definitions)t:(sandbox-type)'
|
|
111
|
+
+ 'D(dump)T(dump-types)F(dump-files)J(json)v(verbose)f:(pidfile)d:(logLevel)'
|
|
112
|
+
+ '*:(logfile)*(overwrite-logfile)*:(syslog)*:(min-level)'))
|
|
84
113
|
{
|
|
85
|
-
let option;
|
|
86
|
-
|
|
87
|
-
/* To fake long-only-opts, we use short opts >= \u1000), and present only the long opt below */
|
|
88
|
-
if (opthnd.option < '\u1000')
|
|
89
|
-
option = opthnd.option;
|
|
90
|
-
else
|
|
91
|
-
option = (Object.entries(parser.gop_aliases).filter(longShort => longShort[1] === opthnd.option)[0] || ['?'])[0];
|
|
92
|
-
|
|
93
114
|
switch (option)
|
|
94
115
|
{
|
|
95
116
|
case '?':
|
|
96
|
-
|
|
117
|
+
processExit(1);
|
|
97
118
|
break;
|
|
98
119
|
case 'h':
|
|
99
120
|
help();
|
|
100
|
-
|
|
121
|
+
processExit(0);
|
|
101
122
|
break;
|
|
102
123
|
case 'P':
|
|
103
|
-
options.prefix =
|
|
124
|
+
options.prefix = optarg;
|
|
104
125
|
break;
|
|
105
126
|
case 'p':
|
|
106
|
-
options.port =
|
|
127
|
+
options.port = optarg;
|
|
107
128
|
break;
|
|
108
129
|
case 's':
|
|
109
130
|
options.stdio = true;
|
|
110
131
|
options.port = false;
|
|
111
132
|
break;
|
|
112
133
|
case 'e':
|
|
113
|
-
options.evaluator = expandPath(
|
|
134
|
+
options.evaluator = expandPath(optarg);
|
|
114
135
|
break;
|
|
115
136
|
case 'm':
|
|
116
|
-
options.evaluator = require.resolve(
|
|
137
|
+
options.evaluator = require.resolve(optarg);
|
|
117
138
|
break;
|
|
118
139
|
case 't':
|
|
119
|
-
options.sandboxType =
|
|
140
|
+
options.sandboxType = optarg;
|
|
120
141
|
break;
|
|
121
142
|
case 'l':
|
|
122
|
-
options.sandboxLibexecDir = expandPath(
|
|
143
|
+
options.sandboxLibexecDir = expandPath(optarg);
|
|
123
144
|
break;
|
|
124
145
|
case 'J':
|
|
125
146
|
options.json = true;
|
|
126
147
|
break;
|
|
127
148
|
case 'f':
|
|
128
|
-
pidfile.write(expandPath(
|
|
149
|
+
pidfile.write(expandPath(optarg));
|
|
129
150
|
break;
|
|
130
151
|
case 'sandbox-definitions':
|
|
131
|
-
options.sandboxDefinitions = expandPath(
|
|
152
|
+
options.sandboxDefinitions = expandPath(optarg);
|
|
132
153
|
break;
|
|
133
154
|
case 'T':
|
|
134
155
|
{
|
|
135
156
|
const dump = Object.keys(require(options.sandboxDefinitions));
|
|
136
157
|
console.log(options.json ? JSON.stringify(dump) : dump);
|
|
137
|
-
|
|
158
|
+
processExit(0);
|
|
138
159
|
break;
|
|
139
160
|
}
|
|
140
161
|
case 'D':
|
|
@@ -146,13 +167,44 @@ Options:
|
|
|
146
167
|
case 'v':
|
|
147
168
|
options.verbose++;
|
|
148
169
|
break;
|
|
170
|
+
case 'syslog':
|
|
171
|
+
loggingOptions.loggers.push('syslog');
|
|
172
|
+
loggingOptions.syslogUrl = utils.makeSyslogUrl(loggingOptions.syslogUrl, optarg);
|
|
173
|
+
break;
|
|
174
|
+
case 'logfile':
|
|
175
|
+
loggingOptions.logfile = optarg;
|
|
176
|
+
loggingOptions.loggers.push('logfile');
|
|
177
|
+
break;
|
|
178
|
+
case 'overwrite-logfile':
|
|
179
|
+
loggingOptions.overwriteLogfile = true;
|
|
180
|
+
break;
|
|
181
|
+
case 'min-level':
|
|
182
|
+
{
|
|
183
|
+
if (optarg.indexOf(',') === -1)
|
|
184
|
+
optarg=`all,${optarg}`;
|
|
185
|
+
const [ which, level ] = optarg.split(',');
|
|
186
|
+
if (which !== 'all' && which !== 'console' && !loggers.exists(which))
|
|
187
|
+
console.error(`--min-level: ${which} is neither 'console' nor the name of a logger`);
|
|
188
|
+
if (!logLevels.lookup(level))
|
|
189
|
+
console.error(`--min-level: ${level} is not a valid log level (${Object.keys(logLevels).join(', ')})`);
|
|
190
|
+
loggingOptions.minLevel[which] = level;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case '--':
|
|
194
|
+
options.evaluatorOptions = argvSegment; /* after --, pass to evaluator as-is */
|
|
195
|
+
break;
|
|
149
196
|
default:
|
|
150
197
|
throw new Error(`unhandled option '${option}'`);
|
|
151
198
|
}
|
|
152
199
|
}
|
|
153
200
|
|
|
154
|
-
|
|
201
|
+
if (dcpConfig.worker.logging?.syslog) loggingOptions.loggers.push('syslog');
|
|
202
|
+
if (dcpConfig.worker.logging?.logfile) loggingOptions.loggers.push('logfile');
|
|
203
|
+
if (dcpConfig.worker.eventLog) loggingOptions.loggers.push('event-log');
|
|
204
|
+
loggingOptions.loggers = utils.uniq(loggingOptions.loggers);
|
|
155
205
|
|
|
206
|
+
const newConsole = require('../lib/worker-consoles').setConsoleType('stdio');
|
|
207
|
+
loggers.hook(newConsole, loggingOptions);
|
|
156
208
|
const sandboxSetupDefs = require(options.sandboxDefinitions)[options.sandboxType];
|
|
157
209
|
if (!sandboxSetupDefs)
|
|
158
210
|
panic(`Invalid sandbox type: ${options.sandboxType}`);
|
|
@@ -208,7 +260,7 @@ Options:
|
|
|
208
260
|
}
|
|
209
261
|
|
|
210
262
|
/* Pass options after -- as evaluator-specific options */
|
|
211
|
-
if (options.evaluatorOptions
|
|
263
|
+
if (options.evaluatorOptions?.length > 0)
|
|
212
264
|
args = args.concat(options.evaluatorOptions);
|
|
213
265
|
|
|
214
266
|
/* Definitions in the JSON file are either in the sandboxLibexecDir, or pathed
|
|
@@ -229,7 +281,7 @@ Options:
|
|
|
229
281
|
console.log('Evaluator command:', [path.resolve(options.prefix, options.evaluator)].concat(files).concat(args).join(' '));
|
|
230
282
|
console.log('Options:', options);
|
|
231
283
|
}
|
|
232
|
-
|
|
284
|
+
processExit(0);
|
|
233
285
|
}
|
|
234
286
|
|
|
235
287
|
if (options.dumpFiles)
|
|
@@ -239,7 +291,7 @@ Options:
|
|
|
239
291
|
if (options.json)
|
|
240
292
|
dump = JSON.stringify(dump);
|
|
241
293
|
console.log(dump);
|
|
242
|
-
|
|
294
|
+
processExit(0);
|
|
243
295
|
}
|
|
244
296
|
|
|
245
297
|
if (!options.stdio && options.sandboxType === 'node') {
|
|
@@ -295,7 +347,7 @@ Options:
|
|
|
295
347
|
console.log('Evaluator server process exited, status', code);
|
|
296
348
|
}
|
|
297
349
|
|
|
298
|
-
|
|
350
|
+
processExit(code);
|
|
299
351
|
});
|
|
300
352
|
|
|
301
353
|
process.on('SIGINT', () => process.kill(child.pid, 'SIGINT'));
|
|
@@ -308,5 +360,6 @@ require('dcp-client').init({
|
|
|
308
360
|
dcpConfig: {
|
|
309
361
|
scheduler: { configLocation: false },
|
|
310
362
|
bundle: { location: false },
|
|
363
|
+
worker: { logging: { syslog: {} } },
|
|
311
364
|
}
|
|
312
|
-
}).then(main);
|
|
365
|
+
}).then(main).finally(loggers.unhook);
|
package/bin/dcp-worker
CHANGED
|
@@ -602,7 +602,7 @@ async function main()
|
|
|
602
602
|
{
|
|
603
603
|
console.info(' . Worktimes Available:');
|
|
604
604
|
for (const wt of workerInfo.worktimes)
|
|
605
|
-
console.info(` - ${wt.name}@${wt.
|
|
605
|
+
console.info(` - ${wt.name}@${wt.version}`);
|
|
606
606
|
}
|
|
607
607
|
|
|
608
608
|
if (!workerInfo.webgpu.enabled)
|
package/lib/utils.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.shortLoc = shortLoc;
|
|
|
16
16
|
exports.qty = qty;
|
|
17
17
|
exports.uniq = uniq;
|
|
18
18
|
exports.makeSyslogUrl = makeSyslogUrl;
|
|
19
|
+
exports.getoptParse = getoptParse;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Figure out #slices fetched from the different forms of the 'fetch' event.
|
|
@@ -118,9 +119,10 @@ function uniq(array)
|
|
|
118
119
|
*
|
|
119
120
|
* existingUrl newArg result
|
|
120
121
|
* ------------------------------- -------------------------------- ----------------------------------
|
|
121
|
-
* udp://localhost:
|
|
122
|
-
* udp://localhost:
|
|
123
|
-
* udp://localhost:
|
|
122
|
+
* udp://localhost:514/local0 tls://provider.com:123/local1 tls://provider.com:123/local1
|
|
123
|
+
* udp://localhost:514/local0 local1 udp://localhost:514/local1
|
|
124
|
+
* udp://localhost:514/local0 tls://provider.com:123/ tls://provider.com:123/local0
|
|
125
|
+
* udp://localhost:514/local0 tls://provider.com/ tls://provider.com:6514/local0
|
|
124
126
|
*
|
|
125
127
|
* @param {URL} existingUrl
|
|
126
128
|
* @param {string|URL} newArg
|
|
@@ -145,5 +147,83 @@ function makeSyslogUrl(existingUrl, newArg)
|
|
|
145
147
|
if (!newUrl.pathname)
|
|
146
148
|
newUrl.pathname = 'local7';
|
|
147
149
|
|
|
150
|
+
if (!newUrl.port)
|
|
151
|
+
{
|
|
152
|
+
switch(newUrl.protocol)
|
|
153
|
+
{
|
|
154
|
+
case 'udp:':
|
|
155
|
+
case 'tcp:':
|
|
156
|
+
newUrl.port = '514';
|
|
157
|
+
break;
|
|
158
|
+
case 'tls:':
|
|
159
|
+
newUrl.port = '6514';
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
148
164
|
return newUrl;
|
|
149
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A posix getopt parser with a nice UX and API.
|
|
169
|
+
* - accepts optionsDefn string like posix-getopt module, but understands that the character * means
|
|
170
|
+
* long option only.
|
|
171
|
+
* - is a generator that yields for each option
|
|
172
|
+
* - option: character or long option when character is *.
|
|
173
|
+
* Magic option -- means that argvSegment is remainder of argv.
|
|
174
|
+
* - optarg: argument to option
|
|
175
|
+
* - optind: position of option character in argv
|
|
176
|
+
* - argvSegment: elements of argv used to make up option and optarg
|
|
177
|
+
*
|
|
178
|
+
* @param {Array} argv array of options to parse
|
|
179
|
+
* @param {string} optionsDefn
|
|
180
|
+
*/
|
|
181
|
+
function *getoptParse(argv, optionsDefn)
|
|
182
|
+
{
|
|
183
|
+
const { BasicParser } = require('posix-getopt');
|
|
184
|
+
var longOptCount = 0;
|
|
185
|
+
|
|
186
|
+
optionsDefn = optionsDefn /* patchup long opt * to > \u1000 chars */
|
|
187
|
+
.match(/[A-Za-z*]:?(\([^)]*\))?/g)
|
|
188
|
+
.map(s => s[0] === '*' ? String.fromCharCode(++longOptCount + 0x1000) + s.slice(1) : s).join('');
|
|
189
|
+
const optionChars = optionsDefn.match(/[A-Za-z\u1000-\u1100/](:?\([^)]*\))?/g).map(s=>s[0]);
|
|
190
|
+
const uniqueOptionChars = uniq(optionChars);
|
|
191
|
+
if (optionChars.length !== uniqueOptionChars.length)
|
|
192
|
+
{
|
|
193
|
+
uniqueOptionChars.forEach(ch => {
|
|
194
|
+
if (optionChars.lastIndexOf(ch) !== optionChars.indexOf(ch))
|
|
195
|
+
throw new Error(`getopt - option character '${ch}' used more than once`);
|
|
196
|
+
});
|
|
197
|
+
throw new Error('unreachable code');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
argv = process.argv.slice(0,2).concat(argv);
|
|
201
|
+
const parser = new BasicParser(optionsDefn, argv);
|
|
202
|
+
const psw = process.stderr.write;
|
|
203
|
+
process.stderr.write = function patchupGetoptErrorOutput(s, ...args) {
|
|
204
|
+
/* posix-getopt library has useless chr for long opts when stderr.write('option requires an argument -- ' + chr + '\n'); */
|
|
205
|
+
if (s.startsWith('option requires an argument -- '))
|
|
206
|
+
s = `option ${argv[parser.optind() - 1]} requires an argument\n`;
|
|
207
|
+
psw.call(process.stderr, s, ...args);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
for (let opthnd, lastOpt=2; (opthnd = parser.getopt()); lastOpt = parser.optind())
|
|
211
|
+
{
|
|
212
|
+
let option;
|
|
213
|
+
|
|
214
|
+
/* To fake long-only-opts, we use short opts >= \u1000), and present only the long opt below */
|
|
215
|
+
if (opthnd.option < '\u1000')
|
|
216
|
+
option = opthnd.option;
|
|
217
|
+
else
|
|
218
|
+
option = (Object.entries(parser.gop_aliases).filter(longShort => longShort[1] === opthnd.option)[0] || ['?'])[0];
|
|
219
|
+
|
|
220
|
+
yield { option, optarg: opthnd.optarg, optind: parser.optind() - 2, argvSegment: argv.slice(lastOpt, parser.optind()) };
|
|
221
|
+
if (opthnd.error)
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (argv.length !== parser.optind())
|
|
226
|
+
yield { option: '--', optind: parser.optind(), argvSegment: argv.slice(parser.optind()) };
|
|
227
|
+
|
|
228
|
+
process.stderr.write = psw; // eslint-disable-line require-atomic-updates
|
|
229
|
+
}
|
|
@@ -165,7 +165,7 @@ function setWorker(worker)
|
|
|
165
165
|
const identity = require('dcp/identity');
|
|
166
166
|
|
|
167
167
|
console.log(' . Starting dashboard');
|
|
168
|
-
setInterval(() => screen.render(), 2000).unref(); /* ensure we didn't forget to render an important update */
|
|
168
|
+
setInterval(() => screen && screen.render(), 2000).unref(); /* ensure we didn't forget to render an important update */
|
|
169
169
|
updateWorkerInfo();
|
|
170
170
|
|
|
171
171
|
setInterval(updateWorkerInfo, 1000).unref();
|
package/lib/worker-info.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dcp-worker",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.4",
|
|
4
4
|
"description": "Node.js Worker for Distributive Compute Platform",
|
|
5
5
|
"main": "bin/dcp-worker",
|
|
6
6
|
"keywords": [
|
|
@@ -38,23 +38,23 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"blessed": "0.1.81",
|
|
40
40
|
"blessed-contrib": "4.11.0",
|
|
41
|
-
"chalk": "
|
|
42
|
-
"dcp-client": "^5.
|
|
41
|
+
"chalk": "4.1.2",
|
|
42
|
+
"dcp-client": "^5.4.0",
|
|
43
43
|
"kvin": "^9.0.0",
|
|
44
|
-
"posix-getopt": "
|
|
45
|
-
"semver": "
|
|
44
|
+
"posix-getopt": "1.2.1",
|
|
45
|
+
"semver": "7.7.3",
|
|
46
46
|
"syslog-client": "1.1.1",
|
|
47
47
|
"syslog-client-tls": "github:wesgarland/node-syslog-client#0d734f33767bc6a8275552d3daa51712cc2d306d",
|
|
48
48
|
"websocket": "1.0.34"
|
|
49
49
|
},
|
|
50
50
|
"optionalDependencies": {
|
|
51
|
-
"dbus-next": "
|
|
51
|
+
"dbus-next": "0.9.2",
|
|
52
52
|
"telnet-console": "^1.0.6"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@distributive/eslint-config": "2.1.4",
|
|
56
56
|
"@trunkio/launcher": "1.2.7",
|
|
57
|
-
"eslint": "
|
|
57
|
+
"eslint": "9.39.1",
|
|
58
58
|
"peter": "2.4.7"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
},
|
|
68
68
|
"engines": {
|
|
69
69
|
"node": ">=18",
|
|
70
|
-
"npm": ">=
|
|
70
|
+
"npm": ">=8"
|
|
71
71
|
},
|
|
72
72
|
"overrides": {
|
|
73
73
|
"blessed-contrib": {
|
|
74
|
-
"xml2js": "
|
|
74
|
+
"xml2js": "0.6.2"
|
|
75
75
|
},
|
|
76
76
|
"dbus-next": {
|
|
77
|
-
"xml2js": "
|
|
77
|
+
"xml2js": "0.6.2",
|
|
78
78
|
"usocket": {
|
|
79
79
|
"node-gyp": "10.0.1"
|
|
80
80
|
}
|