dcp-worker 3.3.6 → 3.3.7-1

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.
@@ -1,7 +1,7 @@
1
1
  #! /usr/bin/env node
2
2
  /**
3
3
  * @file dcp-evaluator-manager
4
- * Daemon to monitor the state of the system and enable/disable
4
+ * Daemon to monitor the state of the system and enable/disable
5
5
  * the evaluator as appropriate.
6
6
  *
7
7
  * The actual mechanics of communicating with the evaluator is
@@ -10,6 +10,9 @@
10
10
  *
11
11
  * The methods the system can be monitored include:
12
12
  * - screensaver enabled/disabled
13
+ * - terminal activity
14
+ * - load average
15
+ * - signals
13
16
  *
14
17
  * @author Wes Garland, wes@distributive.network
15
18
  * @date June 2018, Sep 2022
@@ -19,7 +22,7 @@
19
22
  const os = require('os');
20
23
  const fs = require('fs');
21
24
  const path = require('path');
22
- const net = require('net')
25
+ const net = require('net');
23
26
  const dns = require('dns');
24
27
  const debug = require('debug');
25
28
  const getopt = require('posix-getopt');
@@ -27,6 +30,7 @@ const getopt = require('posix-getopt');
27
30
  var userActivity = { /* no props true => okay to launch evaluator */
28
31
  screensaver: true,
29
32
  session: true,
33
+ signal: true,
30
34
  };
31
35
  var children;
32
36
  var seq = 0;
@@ -38,48 +42,67 @@ const daemonConfig = {
38
42
  net: new URL('tcpip://localhost:9000/'),
39
43
  proc: require.resolve('./dcp-evaluator-start'),
40
44
  argv: [ '-s', '--prefix', `${path.resolve(defaultPrefix)}/` ],
41
- limits: {},
45
+ limits: {
46
+ loadavgType: 'short',
47
+ randomLoadSamples: new Array(5).fill(0.5),
48
+ },
42
49
  session: {
43
50
  idlePoll: 3, /* s between checks*/
44
51
  idleTimeout: 30, /* s to decide the session really IS idle */
45
- }
52
+ },
53
+ signal: {
54
+ which: os.platform() === 'win32' ? 'SIGBREAK' : 'SIGPWR',
55
+ mode: 'toggle'
56
+ },
46
57
  };
47
58
 
48
59
  function usage()
49
60
  {
50
61
  console.log(
51
62
  `
52
- DCP Evaluator Monitor - Copyright (c) 2022-2023 Distributive Corp.
63
+ DCP Evaluator Monitor - Copyright (c) 2022-2024 Distributive Corp.
53
64
  Released under the terms of the MIT License.
54
65
 
55
- Usage: ${process.argv[0]} [-h | --help] [--prefix=<dir>] [--pts=<dir>] [-a]
56
- [--disable-monitor=<session|screensaver>] [-i <timeout>] [-p port]
57
- [-l | --max-load=<number>] [-r | --rate=<number>] [-L]
66
+ Usage: ${process.argv[0]} [-h | --help] [--prefix=<dir>] [--pts=<dir>] [-a]
67
+ [--disable-monitor=<session|screensaver|signal>] [-i <timeout>]
68
+ [-p port] [-l | --max-load=<number>] [-r | --rate=<number>] [-L]
69
+ [-s | --signal=[name or number][,<toggle|quick>]]
70
+ [-T | --loadavg-type=<short,medium,long,random>]
58
71
  [-- <options to dcp-evaluator-start, must be last option>]
59
72
  Where:
60
- - -i sets the idle timeout in seconds for tty sessions (${daemonConfig.session.idleTimeout}s)
61
- - -p specifies the port to listen on for dcpsaw: worker connections (${daemonConfig.net.port})
62
- - --prefix specifies the directory where the DCP evaluator was installed
63
- - --pts specifies the location of the system's pseudo terminal (${ptsDir})
64
- - --disable-monitor=session disables monitoring of ssh/telnet/etc sessions
65
- - --disable-monitor=screensaver disables monitoring of the screensaver
66
- - -a means always run the evaluator, even when the monitors say the machine is busy
67
- - --max-load specifies the point at which machine load prevents connections
68
- - --rate specifies the minimum number of seconds between connections
69
- - -L only apply the limits when the monitors say the machine is busy
73
+ -i sets the idle timeout in seconds for tty sessions (${daemonConfig.session.idleTimeout}s)
74
+ -p specifies the port to listen on for dcpsaw: worker connections (${daemonConfig.net.port})
75
+ --prefix specifies the directory where the DCP evaluator package was installed
76
+ --pts specifies the location of the system's pseudo terminal (${ptsDir})
77
+ --disable-monitor=session disables monitoring of ssh/telnet/etc sessions
78
+ --disable-monitor=screensaver disables monitoring of the screensaver
79
+ --disable-monitor=signal disables monitoring of the signal
80
+ --signal=name,mode changes which signal to monitor and its behaviour. Toggle
81
+ mode turns the monitor on/off, quick mode kills active evaluators but
82
+ more can spawn right away. Currently ${daemonConfig.signal.which},${daemonConfig.signal.mode}.
83
+ -a means always run the evaluator, even when the monitors say the machine is busy
84
+ --rate specifies the minimum number of seconds between connections
85
+ --max-load specifies the point at which machine load prevents connections
86
+ --loadavg-type determines how load is measured; random generates a pseudo-random
87
+ load between 0 and 1; currently ${daemonConfig.limits.loadavgType}.
88
+ -L only apply the limits when the monitors say the machine is busy
89
+
90
+ Environment:
91
+ DEBUG - enables debugging, eg DEBUG='dcp-evaluator-*,-dcp-evaluator-manager:network'
92
+ DCP_CONFIG - sets location of dcp-worker-config file
70
93
  `);
71
94
 
72
95
  process.exit(0);
73
96
  }
74
97
 
75
98
  /**
76
- * Establish listening socket evaluator proxy
99
+ * Establish listening socket evaluator proxy
77
100
  */
78
101
  async function listenForConnections(config)
79
102
  {
80
103
  var server = net.createServer((socket) => handleConnection(socket, config));
81
104
  var hostaddr;
82
-
105
+
83
106
  if (config.net.hostname === 'localhost')
84
107
  hostaddr = '::';
85
108
  else
@@ -116,20 +139,20 @@ function checkUserActivity()
116
139
  debug('dcp-evaluator-manager')('System is not idle; activity detected via', active.join(', '));
117
140
  else
118
141
  debug('dcp-evaluator-manager')('System is idle; checked', Object.entries(userActivity).filter(a => a[1] !== null).map(a => a[0]).join(', '));
119
-
142
+
120
143
  return active.length ? active.join() : false;
121
144
  }
122
145
 
123
146
  /**
124
- * Handle an incoming connection. If the screensaver is not active, the conneciton
125
- * is immediately rejected. Otherwise, we spin up an evaluator process and proxy its
126
- * stdio to the socket.
147
+ * Handle an incoming connection. If the screensaver is active or there is user activity,
148
+ * the connection is immediately rejected. Otherwise, we spin up an evaluator process and
149
+ * proxy its stdio to the socket.
127
150
  */
128
151
  function handleConnection (socket, config)
129
152
  {
130
153
  var child;
131
154
  var userActivityMemo = checkUserActivity();
132
-
155
+
133
156
  function cleanup()
134
157
  {
135
158
  try
@@ -161,9 +184,33 @@ function handleConnection (socket, config)
161
184
 
162
185
  if (!daemonConfig.limits.onlyWhenBusy || (daemonConfig.limits.onlyWhenBusy && userActivityMemo))
163
186
  {
164
- if (daemonConfig.limits.maxLoad < os.loadavg()[0])
187
+ let loadavg;
188
+
189
+ switch(daemonConfig.limits.loadavgType)
190
+ {
191
+ case 'short':
192
+ loadavg = os.loadavg[0];
193
+ break;
194
+ case 'medium':
195
+ case 'med':
196
+ loadavg = os.loadavg[1];
197
+ break;
198
+ case 'long':
199
+ loadavg = os.loadavg[2];
200
+ break;
201
+ case 'random': /* random number in range [0,1) that usually changes slowly and averages 0.5 */
202
+ {
203
+ const samples = daemonConfig.limits.randomLoadSamples;
204
+ samples[Math.floor(samples.length * Math.random())] = Math.random();
205
+ loadavg = Math.random() * (samples.reduce((total, sample) => total + sample, 0) / samples.length);
206
+ loadavg = Number(loadavg.toFixed(2));
207
+ break;
208
+ }
209
+ }
210
+
211
+ if (daemonConfig.limits.maxLoad < loadavg)
165
212
  {
166
- debug('dcp-evaluator-manager')(`New connection; closing due to system load (${os.loadavg().join(', ')})`);
213
+ debug('dcp-evaluator-manager')(`New connection; closing due to ${daemonConfig.limits.loadavgType} system load ${loadavg} (${os.loadavg().join(', ')})`);
167
214
  cleanup();
168
215
  return;
169
216
  }
@@ -175,12 +222,15 @@ function handleConnection (socket, config)
175
222
  return;
176
223
  }
177
224
  }
178
-
225
+
179
226
  debug('dcp-evaluator-manager')('New connection; spawning ', config.proc, config.argv);
180
227
  lastConnectionMs = Date.now();
181
-
182
- child = require('child_process').spawn(config.proc, config.argv);
183
- child.stderr.setEncoding('ascii')
228
+
229
+ const options = {
230
+ env: Object.assign({}, process.env),
231
+ };
232
+ child = require('child_process').spawn(config.proc, config.argv, options);
233
+ child.stderr.setEncoding('ascii');
184
234
  child.socket = socket;
185
235
  children.add(child);
186
236
  child.id = ++seq;
@@ -210,11 +260,11 @@ function handleConnection (socket, config)
210
260
  child.stdout.on('data', function (data) {
211
261
  debug('dcp-evaluator-manager:network')('<', child.id, bufToDisplayStr(data), 93);
212
262
  if (socket)
213
- socket.write(data)
263
+ socket.write(data);
214
264
  });
215
265
 
216
266
  child.stderr.on('data', function (data) {
217
- console.log('child ' + child.id + ' stderr: ', data);
267
+ console.error('child ' + child.id + ' stderr: ', data.replace(/\n/,''));
218
268
  });
219
269
 
220
270
  socket.on('data', function (data) {
@@ -222,17 +272,17 @@ function handleConnection (socket, config)
222
272
 
223
273
  try
224
274
  {
225
- child.stdin.write(data)
275
+ child.stdin.write(data);
226
276
  }
227
277
  catch (error)
228
278
  {
229
- console.warn('could not write to child process (', child.pid, ', index', child.id, ') stdin')
279
+ console.warn('could not write to child process (', child.pid, ', index', child.id, ') stdin');
230
280
  throw error;
231
281
  }
232
282
  });
233
283
  }
234
284
 
235
- /**
285
+ /**
236
286
  * Format detailed debug output of raw socket traffic
237
287
  */
238
288
  function bufToDisplayStr (buf, limit)
@@ -270,7 +320,7 @@ function killChildren()
270
320
  }
271
321
  }
272
322
 
273
- /**
323
+ /**
274
324
  * Monitor dbus messages for screensaver start / stop events.
275
325
  */
276
326
  async function dbusScreenSaverMonitor()
@@ -308,7 +358,7 @@ async function dbusScreenSaverMonitor()
308
358
  process.exit(3);
309
359
  }
310
360
 
311
- const ssActive = await iface.GetActive();
361
+ const ssActive = await iface.GetActive(); // eslint-disable-line new-cap
312
362
  debug('dcp-evaluator-manager')('Screen saver active:', ssActive);
313
363
  userActivity.screensaver = ssActive ? false : 'active dbus screensaver (initial)';
314
364
 
@@ -332,6 +382,59 @@ function screensaverMonitor()
332
382
  return dbusScreenSaverMonitor();
333
383
  }
334
384
 
385
+ /**
386
+ * Activate signal montior.
387
+ */
388
+ function signalMonitor()
389
+ {
390
+ var signal = daemonConfig.signal.which;
391
+
392
+ userActivity.signal = false;
393
+
394
+ switch (Number(signal))
395
+ {
396
+ case 1: signal='SIGHUP'; break;
397
+ case 2: signal='SIGINT'; break;
398
+ case 3: signal='SIGQUIT'; break;
399
+ case 10: signal='SIGUSR1'; break;
400
+ case 12: signal='SIGUSR2'; break;
401
+ case 20: signal='SIGTSTP'; break;
402
+ case 30: signal='SIGPWR'; break;
403
+ }
404
+
405
+ if (!signal.startsWith('SIG'))
406
+ signal = 'SIG' + signal;
407
+
408
+ switch(signal)
409
+ {
410
+ default:
411
+ console.error('invalid signal:', signal);
412
+ return;
413
+ case 'SIGHUP':
414
+ case 'SIGINT':
415
+ case 'SIGQUIT':
416
+ case 'SIGUSR1':
417
+ case 'SIGUSR2':
418
+ case 'SIGTSTP':
419
+ case 'SIGPWR':
420
+ break;
421
+ }
422
+
423
+ if ((daemonConfig.signal.mode !== 'toggle') && (daemonConfig.signal.mode !== 'quick'))
424
+ {
425
+ console.error('invalid signal monitor mode:', daemonConfig.signal.mode);
426
+ return;
427
+ }
428
+
429
+ debug('dcp-evaluator-manager:signal')(`Trapping signal ${signal} for ${daemonConfig.signal.mode}`);
430
+ process.on(signal, (which) => {
431
+ debug('dcp-evaluator-manager:signal')(`Received signal ${which}`);
432
+ if (daemonConfig.signal.mode === 'toggle')
433
+ userActivity.signal = !userActivity.signal;
434
+ killChildren();
435
+ });
436
+ }
437
+
335
438
  /**
336
439
  * Activate session monitor - mechanism is similar to last/fingerd, where we check the last
337
440
  * update time on all ttys to see if there is a remote user using the system or not. We poll
@@ -340,7 +443,7 @@ function screensaverMonitor()
340
443
  function sessionMonitor()
341
444
  {
342
445
  const myTTY = fs.realpathSync('/dev/stdout');
343
-
446
+
344
447
  if (userActivity.session === null) /* disabled */
345
448
  return;
346
449
 
@@ -362,7 +465,7 @@ function sessionMonitor()
362
465
  const sb = fs.statSync(fullPath);
363
466
  if (sb.atimeMs > recentMs)
364
467
  {
365
- /* Experiment suggests atime involves reads and mtime includes writes. Using mtime so that
468
+ /* Experiment suggests atime involves reads and mtime includes writes. Using mtime so that
366
469
  * tailing logs has anticipated result.
367
470
  */
368
471
  userActivity.session = fullPath;
@@ -400,9 +503,9 @@ function odRequire(moduleId)
400
503
  /* Main program entry point */
401
504
  async function main()
402
505
  {
403
- const parser = new getopt.BasicParser('h(help)P:(prefix)d:(disable-monitor)l:(max-load)r:(rate)Li:ap:', process.argv);
506
+ const parser = new getopt.BasicParser('h(help)P:(prefix)d:(disable-monitor)l:(max-load)r:(rate)Li:ap:s:(signal)T:(loadavg-type)', process.argv);
404
507
  var option;
405
-
508
+
406
509
  while ((option = parser.getopt()) !== undefined)
407
510
  {
408
511
  switch (option.option)
@@ -410,12 +513,13 @@ async function main()
410
513
  case 'h':
411
514
  usage();
412
515
  break;
413
-
516
+
414
517
  default:
415
- throw new Error('defined but unspecified option', option);
518
+ throw new Error(`defined but unspecified option: -${option.option}` + (option.optarg ? `=${option.optarg}` : ''));
416
519
 
417
520
  case '?':
418
521
  process.exit(1);
522
+ break;
419
523
 
420
524
  case 'P':
421
525
  {
@@ -452,6 +556,12 @@ async function main()
452
556
  break;
453
557
  }
454
558
 
559
+ case 'R':
560
+ {
561
+ daemonConfig.limits.randomLoad = true;
562
+ break;
563
+ }
564
+
455
565
  case 'L':
456
566
  {
457
567
  daemonConfig.limits.onlyWhenBusy = true;
@@ -475,27 +585,42 @@ async function main()
475
585
  daemonConfig.limits.rateMs = 1000 * Number(option.optarg);
476
586
  break;
477
587
  }
588
+
589
+ case 's':
590
+ {
591
+ const opts = option.optarg.split(',');
592
+ daemonConfig.signal.which = opts[0] || daemonConfig.signal.which;
593
+ daemonConfig.signal.mode = opts[1] || daemonConfig.signal.mode;
594
+ break;
595
+ }
596
+
597
+ case 'T':
598
+ {
599
+ daemonConfig.limits.loadavgType = option.optarg;
600
+ break;
601
+ }
478
602
  }
479
603
  }
480
604
 
481
605
  daemonConfig.argv = daemonConfig.argv.concat(process.argv.slice(parser.optind())); /* All options after -- pass to dcp-evaluator-start */
482
-
606
+
483
607
  process.on('uncaughtException', function (error) {
484
- console.error('\n---', (new Date()).toLocaleString(), '-------------------------------------------------')
485
- console.error('uncaught exception:', error.stack)
486
- console.error('\n')
608
+ console.error('\n---', (new Date()).toLocaleString(), '-------------------------------------------------');
609
+ console.error('uncaught exception:', error.stack);
610
+ console.error('\n');
487
611
  });
488
612
 
489
613
  process.on('unhandledRejection', function (error) {
490
- console.error('\n---', (new Date()).toLocaleString(), '-------------------------------------------------')
491
- console.error('unhandled rejection:', error.stack)
492
- console.error('\n')
493
- })
614
+ console.error('\n---', (new Date()).toLocaleString(), '-------------------------------------------------');
615
+ console.error('unhandled rejection:', error.stack);
616
+ console.error('\n');
617
+ });
494
618
 
495
619
  children = new (require('dcp/utils').Inventory)();
496
620
 
497
621
  await screensaverMonitor();
498
622
  sessionMonitor();
623
+ signalMonitor();
499
624
 
500
625
  listenForConnections(daemonConfig);
501
626
  }
@@ -504,7 +629,7 @@ async function main()
504
629
  require('dcp-client').init({
505
630
  progName: 'dcp-worker',
506
631
  parseArgv: false,
507
- configName: process.env.DCP_CONFIG || '../etc/dcp-worker-config',
632
+ configName: process.env.DCP_CONFIG || '../etc/dcp-worker-config', /* => .js or .json */
508
633
  dcpConfig: {
509
634
  scheduler: { configLocation: false },
510
635
  bundle: { location: false },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dcp-worker",
3
- "version": "3.3.6",
3
+ "version": "3.3.7-1",
4
4
  "description": "JavaScript portion of DCP Workers for Node.js",
5
5
  "main": "bin/dcp-worker",
6
6
  "keywords": [
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git@github.com:Distributed-Compute-Labs/dcp-worker.git"
15
+ "url": "git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-worker.git"
16
16
  },
17
17
  "license": "MIT",
18
18
  "author": "Kings Distributed Systems",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "blessed": "^0.1.81",
41
- "blessed-contrib": "^4.11.0",
41
+ "blessed-contrib": "4.11.0",
42
42
  "chalk": "^4.1.0",
43
- "dcp-client": "4.4.6",
43
+ "dcp-client": "4.4.7-0",
44
44
  "kvin": "^1.2.7",
45
45
  "posix-getopt": "^1.2.1",
46
46
  "semver": "^7.3.8",
@@ -51,9 +51,7 @@
51
51
  "telnet-console": "^1.0.4"
52
52
  },
53
53
  "devDependencies": {
54
- "@distributive/eslint-config": "2.1.1",
55
- "@distributive/eslint-plugin": "1.0.2",
56
- "@kingsds/eslint-config": "^1.0.1",
54
+ "@distributive/eslint-config": "2.1.4",
57
55
  "@trunkio/launcher": "1.2.7",
58
56
  "eslint": "8.56.0"
59
57
  },
@@ -70,7 +68,11 @@
70
68
  "npm": ">=7"
71
69
  },
72
70
  "overrides": {
71
+ "blessed-contrib": {
72
+ "xml2js": "^0.6.0"
73
+ },
73
74
  "dbus-next": {
75
+ "xml2js": "^0.6.0",
74
76
  "usocket": {
75
77
  "node-gyp": "10.0.1"
76
78
  }