dcp-worker 3.3.18 → 3.3.19
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-task-probe +696 -0
- package/bin/dcp-worker +23 -31
- package/lib/dashboard-tui.js +4 -3
- package/lib/{webgpu-info.js → worker-info.js} +32 -24
- package/package.json +2 -2
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file dcp-task-probe
|
|
4
|
+
* Tool to discover work available on the scheduler by requesting and
|
|
5
|
+
* then returning slices.
|
|
6
|
+
*
|
|
7
|
+
* task request response payload currently (dec 2024) has
|
|
8
|
+
* - owner: scheduler's address
|
|
9
|
+
* - signature: ethereum signature object
|
|
10
|
+
* - auth:
|
|
11
|
+
* - workerId: our worker id
|
|
12
|
+
* - authSlices: job address props -> array of number
|
|
13
|
+
* - schedulerId: { address: scheduler's address }
|
|
14
|
+
* - jobCommissions: job address props
|
|
15
|
+
* - rate: number
|
|
16
|
+
* - bank account
|
|
17
|
+
* - body:
|
|
18
|
+
* - newJobs: job address props
|
|
19
|
+
* - address: job address
|
|
20
|
+
* - metrics:
|
|
21
|
+
* - sliceCPUTime
|
|
22
|
+
* - sliceGPUTime
|
|
23
|
+
* - sliceCPUDensity
|
|
24
|
+
* - sliceGPUDensity
|
|
25
|
+
* - lastSliceNumber
|
|
26
|
+
* - measuredSlices
|
|
27
|
+
* - alpha
|
|
28
|
+
* - running: true
|
|
29
|
+
* - requirements: object
|
|
30
|
+
* - public:
|
|
31
|
+
* - computeGroups array
|
|
32
|
+
* - name
|
|
33
|
+
* - description
|
|
34
|
+
* - link
|
|
35
|
+
* - uuid
|
|
36
|
+
* - codeLocation: uri
|
|
37
|
+
* - argumentsLocation: array
|
|
38
|
+
* - MROLocation: uri
|
|
39
|
+
* - requirePath: array
|
|
40
|
+
* - modulePath: array
|
|
41
|
+
* - dependencies: array of module identifiers
|
|
42
|
+
* - urlParameters:
|
|
43
|
+
* - job: job address
|
|
44
|
+
* - jobId: uuid
|
|
45
|
+
* - workerConsole: 0
|
|
46
|
+
* - worktime:
|
|
47
|
+
* - name
|
|
48
|
+
* - version
|
|
49
|
+
* - task: job address props -> array
|
|
50
|
+
* - jobAddress: address
|
|
51
|
+
* - uuid: uuid
|
|
52
|
+
* - worker: worker opaque id
|
|
53
|
+
* - sliceNumber
|
|
54
|
+
* - cotaskCount
|
|
55
|
+
* - isEstimationSlice: boolean
|
|
56
|
+
* - isLongSlice: boolean
|
|
57
|
+
* - timestamp: ms
|
|
58
|
+
* - resultStorageType: values|??
|
|
59
|
+
* - resultStorageDetails
|
|
60
|
+
* - resultStorageParams
|
|
61
|
+
* - computeGroupJobs: opaqueId props
|
|
62
|
+
* - array of jobId
|
|
63
|
+
* - computeGroups: opaque id -> array
|
|
64
|
+
* - computeGroupOrigins: {}
|
|
65
|
+
* - schedulerConfig:
|
|
66
|
+
* - shared:
|
|
67
|
+
* - frequencyMs
|
|
68
|
+
* - debuggingFactor
|
|
69
|
+
* - useLifetimeAnalysis
|
|
70
|
+
* - lifetimeFactor
|
|
71
|
+
* - slice
|
|
72
|
+
* - maxCotasking
|
|
73
|
+
* - multiplier
|
|
74
|
+
* - sigmas
|
|
75
|
+
* - factor
|
|
76
|
+
* - biasMs
|
|
77
|
+
* - targetTaskDuration: number in seconds
|
|
78
|
+
*
|
|
79
|
+
* @author Wes Garland, wes@distributive.network
|
|
80
|
+
* @date Dec 2024
|
|
81
|
+
*/
|
|
82
|
+
'use strict';
|
|
83
|
+
|
|
84
|
+
const fs = require('fs');
|
|
85
|
+
const path = require('path');
|
|
86
|
+
const debug = require('debug');
|
|
87
|
+
const getopt = require('posix-getopt');
|
|
88
|
+
const util = require('util');
|
|
89
|
+
|
|
90
|
+
function usage()
|
|
91
|
+
{
|
|
92
|
+
console.log(
|
|
93
|
+
`
|
|
94
|
+
DCP Task Probe - Copyright (c) 2024 Distributive Corp.
|
|
95
|
+
Released under the terms of the MIT License.
|
|
96
|
+
|
|
97
|
+
Usage: ${process.argv[0]} [option...]
|
|
98
|
+
Examples (bash syntax):
|
|
99
|
+
- # dcp-task-probe --set cores.cpu=13 --set sandbox.progressTimeoutMs=2000
|
|
100
|
+
- # dcp-task-probe --merge allowOrigins.fetchData=https://google.com -c
|
|
101
|
+
- # bin/dcp-task-probe --set minimumWage.CPU=1 --set minimumWage.GPU=3 -c
|
|
102
|
+
- # dcp-task-probe --merge "minimumWage=({CPU:1, GPU:3})" -c
|
|
103
|
+
- # dcp-task-probe --merge 'minimumWage={"CPU":1,"GPU":3}' -c
|
|
104
|
+
Options:
|
|
105
|
+
-h | --help show this help and exit
|
|
106
|
+
-C | --dumpConfig dump worker configuration instead of requesting a task
|
|
107
|
+
-v | --version increase verbosity (show job info)
|
|
108
|
+
-H | --hostname= set evaluator hostname (for capability probing)
|
|
109
|
+
-p | --port= set evaluator port number (for capability probing)
|
|
110
|
+
-S | --set property.name= sets a node in dcpConfig.worker to the given value
|
|
111
|
+
-M | --merge property.name= merge a given value to with a node in dcpConfig.worker
|
|
112
|
+
-a | --allowedOrigins= add url to dcpConfig.worker.allowedOrigins.any
|
|
113
|
+
-c | --cores= set the number of cpu,gpu cores
|
|
114
|
+
-u | --utilization= set the cpu,gpu utilization target
|
|
115
|
+
-m | --maxSandboxes= Maximum number of sandboxes
|
|
116
|
+
-g | --joinComputeGroup= joinKey,joinSecret or joinKey,joinHash of compute group to join
|
|
117
|
+
-j | --jobId= id of job to work
|
|
118
|
+
-r | --replay replay request from worker's last requestSnapshot
|
|
119
|
+
-l | --load load config from worker's last requestSnapshot
|
|
120
|
+
-i | --input= render the input file instead of requesting a new task
|
|
121
|
+
-o | --output= save the task to disk
|
|
122
|
+
Syntax:
|
|
123
|
+
The --merge and --set options have a variety of parsing options, to allow values in
|
|
124
|
+
dcpConfig.worker to have the correct types. The parsing method is controlled by the
|
|
125
|
+
first character of the value:
|
|
126
|
+
@ - the value is a filename; the file is read in and its contents are used in place
|
|
127
|
+
of the value specified on the command-line
|
|
128
|
+
( - the value is parsed as a JavaScript expression
|
|
129
|
+
{ - the value is parsed as a JSON object
|
|
130
|
+
# - the rest of the value is parsed as JSON
|
|
131
|
+
[ - the value is parsed as a JSON array
|
|
132
|
+
\` - the value is parsed as a JavaScript template string
|
|
133
|
+
' - the value is parsed as a string
|
|
134
|
+
" - the value is parsed as a string
|
|
135
|
+
other - the value is parsed as a string, unless it can be parsed as a number
|
|
136
|
+
Note:
|
|
137
|
+
Beyond the usual dcpConfig.worker values, this program adds dcpConfig.worker.capabilities and
|
|
138
|
+
dcpConfig.worker.worktimes. If they are not present in the loaded config, they are automtically
|
|
139
|
+
populated by querying the local evaluator. If they are both specified in the config, then it
|
|
140
|
+
is not necessary to have a working evaluator to run this program.
|
|
141
|
+
Environment:
|
|
142
|
+
DEBUG - enables debugging, eg DEBUG='dcp-task-probe*,-dcp-task-probe:evaluator'
|
|
143
|
+
DCP_CONFIG - sets location of dcp-worker-config file
|
|
144
|
+
`);
|
|
145
|
+
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function panic()
|
|
150
|
+
{
|
|
151
|
+
console.error.apply(console, arguments);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function detailedView(object)
|
|
156
|
+
{
|
|
157
|
+
return util.inspect(object, { depth: null, colors: process.env.FORCE_COLOR || true });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Determine if an string is a hash; used to differentiate between compute group join keys and hashes.
|
|
162
|
+
*/
|
|
163
|
+
function isHash(b)
|
|
164
|
+
{
|
|
165
|
+
return b && b.length === 68 && b.startsWith('eh1-');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Evaluate a JavaScript expression within a given namespace. This function creates an IIFE and injects
|
|
170
|
+
* the properties of the namespace object into the IIFE's function block scope.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} expression the JS expression to evaluatte
|
|
173
|
+
* @param {object} namespace the namespace object; each property becomes a symbol that can be used
|
|
174
|
+
* by the expression
|
|
175
|
+
*/
|
|
176
|
+
function nsEval(expression, namespace)
|
|
177
|
+
{
|
|
178
|
+
const indirectEval = eval; // eslint-disable-line no-eval
|
|
179
|
+
const symbolNames = Object.keys(namespace);
|
|
180
|
+
const symbolValues = Object.values(namespace);
|
|
181
|
+
return indirectEval(`(${symbolNames}) => ${expression}`)(symbolValues);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Parse a value within a given namespace. See the usage function for the syntax supported by this
|
|
186
|
+
* function. If the value is not a string, it is not parsed and simply returned.
|
|
187
|
+
*
|
|
188
|
+
* @param {any} value the value to parse
|
|
189
|
+
* @param {object} namespace a namespacing object used when evaluating JS expresssions; see nsEval
|
|
190
|
+
* @returns {any} value or a JS value derived by parsing value
|
|
191
|
+
*/
|
|
192
|
+
function parseValue(value, namespace)
|
|
193
|
+
{
|
|
194
|
+
if (typeof value !== 'string')
|
|
195
|
+
return;
|
|
196
|
+
|
|
197
|
+
if (value[0] === '@')
|
|
198
|
+
value = fs.readFileSync(value[0], 'utf-8');
|
|
199
|
+
switch(value[0])
|
|
200
|
+
{
|
|
201
|
+
case '#':
|
|
202
|
+
value = JSON.parse(value.slice(1));
|
|
203
|
+
break;
|
|
204
|
+
case '[':
|
|
205
|
+
case '{':
|
|
206
|
+
value = JSON.parse(value);
|
|
207
|
+
break;
|
|
208
|
+
case '`': /* fallthrough */
|
|
209
|
+
case '(':
|
|
210
|
+
value = nsEval(value, namespace);
|
|
211
|
+
break;
|
|
212
|
+
case "'":
|
|
213
|
+
if (/^'([^']*)'/.test(value))
|
|
214
|
+
value = value.match(/^'([^.]*)'/)[1];
|
|
215
|
+
break;
|
|
216
|
+
case '"':
|
|
217
|
+
if (/^"([^"]*)"/.test(value))
|
|
218
|
+
value = value.match(/^"([^"]*)"/)[1];
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
if (!isNaN(Number(value)))
|
|
222
|
+
value = Number(value);
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Traverse a graph by walking a dot path (eg a.b.c); ie. the path names each edge of the graph of the
|
|
230
|
+
* graph to traverse. The value returned is the node which is the parent of the final edge in the path,
|
|
231
|
+
* and the final edge the path. This is so that the calling code can replace the edge and is effectively
|
|
232
|
+
* a pointer to the node described by the dot path.
|
|
233
|
+
*
|
|
234
|
+
* @example:
|
|
235
|
+
* walkDotPath(dcpConfig.worker, 'minimumWage.CPU')
|
|
236
|
+
* returns [ dcpConfig.worker.minimumWage, 'CPU' ]
|
|
237
|
+
*
|
|
238
|
+
* @param {object} startNode the name of the first node in the graph
|
|
239
|
+
* @param {string} dotPath the names of the edges to traverse, separated by dots
|
|
240
|
+
* @return { Array } whose first element is the node containing parent node of the final edge and
|
|
241
|
+
* whose second element is the name of the final edge
|
|
242
|
+
*/
|
|
243
|
+
function walkDotPath(startNode, dotPath)
|
|
244
|
+
{
|
|
245
|
+
var node = startNode;
|
|
246
|
+
var seen = [ 'worker' ];
|
|
247
|
+
|
|
248
|
+
dotPath = dotPath.split('.');
|
|
249
|
+
while(dotPath.length > 1)
|
|
250
|
+
{
|
|
251
|
+
const prop = dotPath.shift();
|
|
252
|
+
if (!node[prop])
|
|
253
|
+
panic(`invalid property ${prop} in dcpConfig.${seen.join('.')}`)
|
|
254
|
+
node = node[prop];
|
|
255
|
+
seen.push(prop);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return [ node, dotPath[0] ];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Parse a string of the form a,b into { cpu: a, gpu: b } */
|
|
262
|
+
function cpuCommaGpu(str)
|
|
263
|
+
{
|
|
264
|
+
const [cpu,gpu] = str.split(/[ ,]/);
|
|
265
|
+
const obj = {};
|
|
266
|
+
if (typeof cpu !== 'undefined' && cpu !== '')
|
|
267
|
+
obj.cpu = Number(cpu);
|
|
268
|
+
if (typeof gpu !== 'undefined')
|
|
269
|
+
obj.gpu = Number(gpu);
|
|
270
|
+
return obj;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Parse and process command-line options. Most options result in changes to the running dcpConfig;
|
|
275
|
+
* other options are reflected in the returned object:
|
|
276
|
+
* - dumpConfig
|
|
277
|
+
* - workerId
|
|
278
|
+
*/
|
|
279
|
+
async function processOptions()
|
|
280
|
+
{
|
|
281
|
+
const opts = { verbose: 0 };
|
|
282
|
+
const parser = new getopt.BasicParser('h(help)C(dumpConfig)S:(set)M:(merge)a:(allowedOrigins)'
|
|
283
|
+
+ 'c:(cores)u:(utilization)m:(maxSandboxes)w:(workerId)'
|
|
284
|
+
+ 'J:(jobAddress)j:(job)g:(computeGroup)G(leaveGlobalGroup)'
|
|
285
|
+
+ 'H:(hostname)p:(port)r(replay)l(load)i(input)o(output)'
|
|
286
|
+
+ 'v(verbose)',
|
|
287
|
+
process.argv);
|
|
288
|
+
var opthnd;
|
|
289
|
+
|
|
290
|
+
while ((opthnd = parser.getopt()) !== undefined)
|
|
291
|
+
{
|
|
292
|
+
switch (opthnd.option)
|
|
293
|
+
{
|
|
294
|
+
case 'h':
|
|
295
|
+
usage();
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
default:
|
|
299
|
+
throw new Error(`defined but unspecified option: -${opthnd.option}` + (opthnd.optarg ? `=${opthnd.optarg}` : ''));
|
|
300
|
+
|
|
301
|
+
case '?':
|
|
302
|
+
process.exit(1);
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case 'C':
|
|
306
|
+
opts.dumpConfig = true;
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case 'v':
|
|
310
|
+
opts.verbose += 1;
|
|
311
|
+
break;
|
|
312
|
+
|
|
313
|
+
case 'w':
|
|
314
|
+
opts.workerId = opthnd.optarg;
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
case 'r':
|
|
318
|
+
opts.replay = true;
|
|
319
|
+
break;
|
|
320
|
+
|
|
321
|
+
case 'a':
|
|
322
|
+
dcpConfig.worker.allowedOrigins.any.push(opthnd.optarg);
|
|
323
|
+
break;
|
|
324
|
+
|
|
325
|
+
case 'c':
|
|
326
|
+
Object.assign(dcpConfig.worker.cores, cpuCommaGpu(opthnd.optarg));
|
|
327
|
+
break;
|
|
328
|
+
|
|
329
|
+
case 'u':
|
|
330
|
+
Object.assign(dcpConfig.worker.utilization, cpuCommaGpu(opthnd.optarg));
|
|
331
|
+
break;
|
|
332
|
+
|
|
333
|
+
case 'm':
|
|
334
|
+
dcpConfig.worker.maxSandboxes = Number(opthnd.optarg);
|
|
335
|
+
break;
|
|
336
|
+
|
|
337
|
+
case 'J':
|
|
338
|
+
dcpConfig.worker.jobAddresses = (dcpConfig.worker.jobAddresses || []).concat(opthnd.optarg);
|
|
339
|
+
break;
|
|
340
|
+
|
|
341
|
+
case 'j': /** @todo integrate with upcoming scheduler changes to opaqueId /wg dec 2024 */
|
|
342
|
+
dcpConfig.worker.jobs = (dcpConfig.worker.jobs || []).concat(opthnd.optarg);
|
|
343
|
+
break;
|
|
344
|
+
|
|
345
|
+
case 'G':
|
|
346
|
+
dcpConfig.worker.leavePublicGroup = true;
|
|
347
|
+
break;
|
|
348
|
+
|
|
349
|
+
case 'H':
|
|
350
|
+
dcpConfig.evaluator.location.hostname = opthnd.ortarg;
|
|
351
|
+
break;
|
|
352
|
+
|
|
353
|
+
case 'p':
|
|
354
|
+
dcpConfig.evaluator.location.port = opthnd.ortarg;
|
|
355
|
+
break;
|
|
356
|
+
|
|
357
|
+
case 'g':
|
|
358
|
+
{
|
|
359
|
+
const [ joinKey, joinSecret ] = opthnd.optarg.split(',');
|
|
360
|
+
const joinHash = joinSecret;
|
|
361
|
+
|
|
362
|
+
if (isHash(joinHash))
|
|
363
|
+
dcpConfig.worker.computeGroups.push({ joinKey, joinHash });
|
|
364
|
+
else
|
|
365
|
+
dcpConfig.worker.computeGroups.push({ joinKey, joinSecret });
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
case 'M': /* fallthrough */
|
|
370
|
+
case 'S':
|
|
371
|
+
{
|
|
372
|
+
const eqIdx = opthnd.optarg.indexOf('=');
|
|
373
|
+
const idx = eqIdx !== -1 ? eqIdx : opthnd.optarg.length;
|
|
374
|
+
const value = parseValue(opthnd.optarg.slice(idx + 1), dcpConfig.worker);
|
|
375
|
+
const [ confNode, prop ] = walkDotPath(dcpConfig.worker, opthnd.optarg.slice(0, idx));
|
|
376
|
+
|
|
377
|
+
if (opthnd.option === 's')
|
|
378
|
+
confNode[prop] = value;
|
|
379
|
+
else
|
|
380
|
+
{
|
|
381
|
+
/* merge mode: objects are merged at the property level, except for arrays, which are concatenated */
|
|
382
|
+
if (confNode === dcpConfig.worker && (prop === 'worktimes' || prop === 'capabilities'))
|
|
383
|
+
await probeEvaluator(); /* ensure we have something to merge into */
|
|
384
|
+
switch(typeof confNode[prop])
|
|
385
|
+
{
|
|
386
|
+
case 'object':
|
|
387
|
+
{
|
|
388
|
+
if (!Array.isArray(confNode[prop]))
|
|
389
|
+
Object.assign(confNode[prop], value);
|
|
390
|
+
else
|
|
391
|
+
{
|
|
392
|
+
if (Array.isArray(value))
|
|
393
|
+
Array.push.apply(confNode, value);
|
|
394
|
+
else
|
|
395
|
+
confNode[prop].push(value);
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
default:
|
|
400
|
+
confNode[prop] = value;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
case 'l':
|
|
408
|
+
{
|
|
409
|
+
const wallet = require('dcp/wallet');
|
|
410
|
+
const snapshot = readWorkerSnapshot();
|
|
411
|
+
dcpConfig.worker = snapshot.workerConfig;
|
|
412
|
+
dcpConfig.worker.paymentAddress = new wallet.Address(dcpConfig.worker.paymentAddress);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return opts;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Probe the evaluator to determine its capabilities and supported worktimes. This function is delayed
|
|
422
|
+
* as late as possible during the configuration phase so that the user has the option to specify these
|
|
423
|
+
* completely on a system with no evaluator.
|
|
424
|
+
*/
|
|
425
|
+
async function probeEvaluator()
|
|
426
|
+
{
|
|
427
|
+
if (probeEvaluator.probed)
|
|
428
|
+
return;
|
|
429
|
+
probeEvaluator.probed = true;
|
|
430
|
+
const probeTimer = setTimeout(() => panic('unable to probe evaluator'), 66610e3); /* Need to keep main from exiting */
|
|
431
|
+
|
|
432
|
+
if (!dcpConfig.worker.capabilities || !dcpConfig.worker.worktimes)
|
|
433
|
+
{
|
|
434
|
+
const { workerFactory } = require('dcp-client/lib/standaloneWorker');
|
|
435
|
+
debug('dcp-task-probe:evaluator')('Probing evaluator at', dcpConfig.evaluator.location);
|
|
436
|
+
const evaluatorInfo = await require('dcp/worker').probeEvaluator(workerFactory(dcpConfig.evaluator.location));
|
|
437
|
+
dcpConfig.worker.capabilities ||= evaluatorInfo.capabilities;
|
|
438
|
+
dcpConfig.worker.worktimes ||= evaluatorInfo.worktimes;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
clearTimeout(probeTimer);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Returns the most recent snapshot left behind by the worker. The location of the snapshot is given by
|
|
446
|
+
* dcpConfig.worker.requestSnapshot. Multiple calls to this function always return the same snapshot.
|
|
447
|
+
* This function understands that the worker might write kvin-encoded snapshots, identitfied by their
|
|
448
|
+
* extension. The snapshots are written automatically by the worker during task request when the
|
|
449
|
+
* dcpConfig.worker.requestSnapshot is set.
|
|
450
|
+
*/
|
|
451
|
+
function readWorkerSnapshot()
|
|
452
|
+
{
|
|
453
|
+
if (!dcpConfig.worker.requestSnapshot)
|
|
454
|
+
panic('no snapshot specified in dcpConfig.worker.requestSnapshot');
|
|
455
|
+
|
|
456
|
+
if (!readWorkerSnapshot.cache)
|
|
457
|
+
{
|
|
458
|
+
const serializer = dcpConfig.worker.requestSnapshot.endsWith('kvin') ? require('kvin').KVIN : JSON;
|
|
459
|
+
debug('dcp-task-probe')('loading snapshot', detailedView(dcpConfig.worker.requestSnapshot));
|
|
460
|
+
readWorkerSnapshot.cache = serializer.parse(fs.readFileSync(dcpConfig.worker.requestSnapshot));
|
|
461
|
+
}
|
|
462
|
+
return readWorkerSnapshot.cache;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Generate non-global compute group secrets, hashed with the current task distributor connection's
|
|
467
|
+
* dcpsid. Add in the public group if necessary.
|
|
468
|
+
*
|
|
469
|
+
* @todo unify with supervisor /wg dec2024
|
|
470
|
+
*/
|
|
471
|
+
function makeComputeGroupsRequest(tdConn, dummyWorker)
|
|
472
|
+
{
|
|
473
|
+
const computeGroups = structuredClone(dcpConfig.worker.computeGroups); /* supervisor code sadly mutates this /wg dec 2024 */
|
|
474
|
+
const options = Object.assign({}, dcpConfig.worker, { computeGroups });
|
|
475
|
+
const taskDistributor = { connection: tdConn };
|
|
476
|
+
|
|
477
|
+
dummyWorker.badSupervisorBackdoor.dcp4.generateWorkerComputeGroups.apply({ taskDistributor, options });
|
|
478
|
+
if (!dcpConfig.worker.leavePublicGroup && !dcpConfig.worker.leaveGlobalGroup)
|
|
479
|
+
options.computeGroups.unshift({ joinKey: 'public' });
|
|
480
|
+
return options.computeGroups;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Request a task from the task distributor
|
|
486
|
+
*
|
|
487
|
+
* @todo unify with supervisor /wg dec 2024
|
|
488
|
+
* @param {Keystore} identity identity of the requesting entity
|
|
489
|
+
* @param {Object} opts command-line options to dcp-task-probe
|
|
490
|
+
* .replay truey to use request from worker snapshot instead of a generated request
|
|
491
|
+
* .workerId workerId for request
|
|
492
|
+
*/
|
|
493
|
+
async function requestTask(identity, opts)
|
|
494
|
+
{
|
|
495
|
+
const protocol = require('dcp/protocol');
|
|
496
|
+
const worker = require('dcp/worker');
|
|
497
|
+
const tdConn = new protocol.Connection(dcpConfig.scheduler.services.taskDistributor);
|
|
498
|
+
const dummyWorker = new worker.Worker(identity, dcpConfig.worker); /* side effect: initializes dcpConfig.worker */
|
|
499
|
+
var request;
|
|
500
|
+
|
|
501
|
+
if (opts.replay)
|
|
502
|
+
request = readWorkerSnapshot().request;
|
|
503
|
+
else
|
|
504
|
+
request = {
|
|
505
|
+
supervisor: dummyWorker.supervisorVersion,
|
|
506
|
+
targetLoad: {
|
|
507
|
+
cpu: Math.min(dcpConfig.worker.maxSandboxes, dcpConfig.worker.cores.cpu * dcpConfig.worker.utilization.cpu) /* cpuCoreSpace */,
|
|
508
|
+
gpu: Math.min(dcpConfig.worker.maxSandboxes, dcpConfig.worker.cores.gpu * dcpConfig.worker.utilization.gpu) /* this.maxWorkingGPUs */,
|
|
509
|
+
longSlices: dcpConfig.worker.maxSandboxes /* Math.floor(cpuCoreSpace) */,
|
|
510
|
+
},
|
|
511
|
+
coreStats: { /** @todo why are we specifying these? /wg dec 2024 */
|
|
512
|
+
worker: dummyWorker.workerId,
|
|
513
|
+
lCores: require('os').cpus().length,
|
|
514
|
+
pCores: require('physical-cpu-count'),
|
|
515
|
+
sandbox: dcpConfig.worker.maxSandboxes,
|
|
516
|
+
},
|
|
517
|
+
jobQuanta: [ 1 ]/* this.quanta.calculateJobQuanta() */,
|
|
518
|
+
capabilities: dcpConfig.worker.capabilities,
|
|
519
|
+
paymentAddress: dcpConfig.worker.paymentAddress,
|
|
520
|
+
jobAddresses: dcpConfig.worker.jobAddresses || [],
|
|
521
|
+
workerComputeGroups: makeComputeGroupsRequest(tdConn, dummyWorker),
|
|
522
|
+
minimumWage: dcpConfig.worker.minimumWage,
|
|
523
|
+
soteriaJobs: [],
|
|
524
|
+
overdueSlices: [],
|
|
525
|
+
previouslyWorkedJobs: [], /* discrete jobs */
|
|
526
|
+
rejectedJobs: [],
|
|
527
|
+
unresolvedJobs: [],
|
|
528
|
+
fetchState: 1, /* normal fetch */
|
|
529
|
+
worktimes: dcpConfig.worker.worktimes,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
debug('dcp-task-probe:connect')('Connecting to task distributor at', tdConn.targetDescriptor.location.href);
|
|
533
|
+
await tdConn.keepalive();
|
|
534
|
+
debug('dcp-task-probe:request')('Task Request:', request);
|
|
535
|
+
|
|
536
|
+
const rtStart = Date.now();
|
|
537
|
+
const response = await tdConn.request('requestTask', request);
|
|
538
|
+
const rtElapsed = Date.now() - rtStart;
|
|
539
|
+
console.log('Task Request Time: ', fmtMs(rtElapsed));
|
|
540
|
+
|
|
541
|
+
debug('dcp-task-probe:request')('Task Response:', detailedView(response));
|
|
542
|
+
if (!response.success)
|
|
543
|
+
panic('request failed:', response.payload);
|
|
544
|
+
|
|
545
|
+
return { response, workerId: request.coreStats?.worker };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async function returnTask(taskResponsePayload)
|
|
549
|
+
{
|
|
550
|
+
const protocol = require('dcp/protocol');
|
|
551
|
+
const { body, ...authorizationMessage } = taskResponsePayload;
|
|
552
|
+
const { auth } = taskResponsePayload;
|
|
553
|
+
const returnRequest = {
|
|
554
|
+
worker: auth.workerId,
|
|
555
|
+
slices: [],
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
for (const job in auth.authSlices)
|
|
559
|
+
{
|
|
560
|
+
for (const sliceNumber of auth.authSlices[job])
|
|
561
|
+
{
|
|
562
|
+
const slices = body.task[job];
|
|
563
|
+
returnRequest.slices.push({
|
|
564
|
+
sliceNumber,
|
|
565
|
+
job,
|
|
566
|
+
authorizationMessage,
|
|
567
|
+
isEstimationSlice: slices.filter(slice => slice.sliceNumber === sliceNumber)[0].isEstimationSlice,
|
|
568
|
+
status: 'return',
|
|
569
|
+
reason: 'task-probe',
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
debug('dcp-task-probe:request')('Return Request:', detailedView(returnRequest));
|
|
575
|
+
const rsConn = new protocol.Connection(dcpConfig.scheduler.services.resultSubmitter);
|
|
576
|
+
const response = await rsConn.request('status', returnRequest);
|
|
577
|
+
|
|
578
|
+
if (!response.success)
|
|
579
|
+
panic('request failed:', response.payload);
|
|
580
|
+
|
|
581
|
+
debug('dcp-task-probe:response')('Return Response:', detailedView(response));
|
|
582
|
+
if (!response.payload?.length)
|
|
583
|
+
panic('did not return task');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function unique(e, i, self)
|
|
587
|
+
{
|
|
588
|
+
return i === self. indexOf(e);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function fmtMs(ms)
|
|
592
|
+
{
|
|
593
|
+
return (ms / 1e3).toFixed(3) + 's';
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Main program entry point */
|
|
597
|
+
async function main()
|
|
598
|
+
{
|
|
599
|
+
const wallet = require('dcp/wallet');
|
|
600
|
+
const identity = await wallet.getId();
|
|
601
|
+
const opts = await processOptions();
|
|
602
|
+
|
|
603
|
+
if (!dcpConfig.worker.paymentAddress)
|
|
604
|
+
dcpConfig.worker.paymentAddress = (await wallet.get()).address;
|
|
605
|
+
|
|
606
|
+
if (!opts.replay)
|
|
607
|
+
await probeEvaluator();
|
|
608
|
+
|
|
609
|
+
if (opts.dumpConfig)
|
|
610
|
+
{
|
|
611
|
+
/* work around the cores getter/setter that is added by the worker */
|
|
612
|
+
const dump = Object.assign({}, dcpConfig.worker);
|
|
613
|
+
dump.cores = { cpu: dcpConfig.worker.cores.cpu, gpu: dcpConfig.worker.cores.gpu };
|
|
614
|
+
console.log(dump);
|
|
615
|
+
process.exit(0);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const { workerId, response } = await requestTask(identity, opts);
|
|
619
|
+
const task = response.payload?.body?.task;
|
|
620
|
+
if (task)
|
|
621
|
+
await returnTask(response.payload);
|
|
622
|
+
|
|
623
|
+
console.log('Task Distributor: ', dcpConfig.scheduler.services.taskDistributor.location.href);
|
|
624
|
+
console.log('Result Submitter: ', dcpConfig.scheduler.services.resultSubmitter.location.href);
|
|
625
|
+
console.log('Worker Id: ', workerId);
|
|
626
|
+
console.log('DCP Address: ', String(identity.address));
|
|
627
|
+
|
|
628
|
+
if (!task)
|
|
629
|
+
{
|
|
630
|
+
console.error('no work');
|
|
631
|
+
process.exit(22);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const { newJobs, computeGroupJobs, computeGroupOrigins, schedulerConfig } = response.payload.body;
|
|
635
|
+
const cgs = {}
|
|
636
|
+
for (const cgId in computeGroupJobs)
|
|
637
|
+
cgs[cgId] = { jobCount: computeGroupJobs[cgId].length };
|
|
638
|
+
for (const jobId in newJobs)
|
|
639
|
+
{
|
|
640
|
+
for (const cg of newJobs[jobId].public.computeGroups)
|
|
641
|
+
Object.assign(cgs[cg.opaqueId], { name: cg.name, description: cg.description });
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
console.log('Total Jobs: ', Object.keys(task).length);
|
|
645
|
+
console.log('Total Slices: ', Object.keys(task).map(jobId => task[jobId].length).reduce((a, b) => a+b, 0));
|
|
646
|
+
console.log('Total CPU,GPU Time: ',
|
|
647
|
+
fmtMs(Object.keys(task).map(jobId => newJobs[jobId].metrics.sliceCPUTime).reduce((a, b) => a+b, 0)) + ',',
|
|
648
|
+
fmtMs(Object.keys(task).map(jobId => newJobs[jobId].metrics.sliceGPUTime).reduce((a, b) => a+b, 0)));
|
|
649
|
+
console.log('CPU,GPU Time/core: ',
|
|
650
|
+
fmtMs(Object.keys(task).map(jobId => newJobs[jobId].metrics.sliceCPUTime).reduce((a, b) => a+b, 0) / dcpConfig.worker.cores.cpu) + ',',
|
|
651
|
+
fmtMs(Object.keys(task).map(jobId => newJobs[jobId].metrics.sliceGPUTime).reduce((a, b) => a+b, 0) / dcpConfig.worker.cores.gpu));
|
|
652
|
+
console.log('Task Duration: ', schedulerConfig.targetTaskDuration + 's');
|
|
653
|
+
console.log('Compute Groups: ', Object.keys(computeGroupJobs).length);
|
|
654
|
+
|
|
655
|
+
if (opts.verbose)
|
|
656
|
+
{
|
|
657
|
+
for (const cgId in cgs)
|
|
658
|
+
{
|
|
659
|
+
console.log(' -', cgId, cgs[cgId].name);
|
|
660
|
+
if (computeGroupOrigins[cgId])
|
|
661
|
+
console.log(' allow origins:', computeGroupOrigins[cgId]);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (opts.verbose)
|
|
666
|
+
{
|
|
667
|
+
for (const jobId of Object.keys(task))
|
|
668
|
+
{
|
|
669
|
+
console.log(`\n-- Job ${jobId} ------------------------------`);
|
|
670
|
+
console.log('Public Info: ', newJobs[jobId].public.name, newJobs[jobId].public.description, newJobs[jobId].public.link || '');
|
|
671
|
+
console.log('Compute Groups: ', newJobs[jobId].public.computeGroups.map(cg=>cg.opaqueId).filter(unique));
|
|
672
|
+
console.log('Slices: ', Object.keys(task[jobId]).length);
|
|
673
|
+
console.log('Avg Slice Time: ',
|
|
674
|
+
`cpu=${fmtMs(newJobs[jobId].metrics.sliceCPUTime)}`,
|
|
675
|
+
`gpu=${fmtMs(newJobs[jobId].metrics.sliceGPUTime)}`);
|
|
676
|
+
console.log('Avg Slice Density: ',
|
|
677
|
+
`cpu=${(newJobs[jobId].metrics.sliceCPUDensity * 100).toFixed(1)}%`,
|
|
678
|
+
`gpu=${(newJobs[jobId].metrics.sliceGPUDensity * 100).toFixed(1)}%`);
|
|
679
|
+
console.log('Worktime: ', `${newJobs[jobId].worktime.name}@${newJobs[jobId].worktime.version}`);
|
|
680
|
+
if (newJobs[jobId].codeLocation.startsWith('data:'))
|
|
681
|
+
console.log('Work Function: ', newJobs[jobId].codeLocation.length, 'bytes, inline');
|
|
682
|
+
else
|
|
683
|
+
console.log('Work Function: ', newJobs[jobId].codeLocation);
|
|
684
|
+
|
|
685
|
+
console.log('Result Storage: ', task[jobId][0].resultStorageType, task[jobId][0].resultStorageDetails);
|
|
686
|
+
console.log('Dependencies: ', newJobs[jobId].dependencies.map(a => path.basename(a)).join(' '));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/* Initialize dcp-client to use only local resources before launching the main function */
|
|
692
|
+
require('dcp-client').init({
|
|
693
|
+
progName: 'dcp-worker',
|
|
694
|
+
parseArgv: false,
|
|
695
|
+
configName: process.env.DCP_CONFIG || '../etc/dcp-worker-config', /* => .js or .json */
|
|
696
|
+
}).then(main);
|
package/bin/dcp-worker
CHANGED
|
@@ -130,14 +130,7 @@ function parseCliArgs()
|
|
|
130
130
|
alias: 'p',
|
|
131
131
|
describe: 'Evaluator port',
|
|
132
132
|
type: 'number',
|
|
133
|
-
default: Number(dcpConfig.evaluator.
|
|
134
|
-
},
|
|
135
|
-
priorityOnly: {
|
|
136
|
-
alias: 'P',
|
|
137
|
-
hidden: true,
|
|
138
|
-
describe: 'Set the priority mode [deprecated]',
|
|
139
|
-
type: 'boolean',
|
|
140
|
-
default: false
|
|
133
|
+
default: Number(dcpConfig.evaluator.location.port),
|
|
141
134
|
},
|
|
142
135
|
'job-id': {
|
|
143
136
|
alias: 'j',
|
|
@@ -403,7 +396,6 @@ z */
|
|
|
403
396
|
if (dcpWorkerOptions.jobAddresses === false || dcpWorkerOptions.jobAddresses === undefined)
|
|
404
397
|
dcpWorkerOptions.jobAddresses = [];
|
|
405
398
|
dcpWorkerOptions.jobAddresses.push(...cliArgs.jobId);
|
|
406
|
-
dcpWorkerOptions.priorityOnly = true;
|
|
407
399
|
}
|
|
408
400
|
|
|
409
401
|
if (cliArgs.allowedOrigins)
|
|
@@ -462,10 +454,12 @@ z */
|
|
|
462
454
|
else
|
|
463
455
|
{
|
|
464
456
|
const errorCode = error.code ?? error.errorCode;
|
|
457
|
+
const location = error.stack.split('\n')[1].replace(/^\s*/,'');
|
|
458
|
+
let message = error.message;
|
|
459
|
+
|
|
465
460
|
if (errorCode)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
console.error(`Error: ${error.message}`);
|
|
461
|
+
message += ` (${error.code})`;
|
|
462
|
+
console.error(`Error: ${error.message} ${location}`);
|
|
469
463
|
}
|
|
470
464
|
});
|
|
471
465
|
require('../lib/default-ui-events').hook(nascentWorker, cliArgs);
|
|
@@ -533,29 +527,26 @@ z */
|
|
|
533
527
|
if (telnetd.hasOwnProperty('port'))
|
|
534
528
|
bannerLog(` ! telnetd listening on port ${telnetd.port}`);
|
|
535
529
|
|
|
536
|
-
|
|
537
|
-
if (Object.keys(worktimes).length > 0)
|
|
538
|
-
{
|
|
539
|
-
bannerLog(' . Worktimes Available:');
|
|
540
|
-
for (const wt of worktimes)
|
|
541
|
-
bannerLog(` -\t${wt.name}@${wt.versions.join(';')}`);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
let webgpuInfo;
|
|
530
|
+
let workerInfo;
|
|
545
531
|
console.throb('log', ' ! Waiting for dcp-evaluator to start...');
|
|
546
|
-
for (let i=0; !
|
|
532
|
+
for (let i=0; !workerInfo; i++)
|
|
547
533
|
{
|
|
548
|
-
|
|
549
|
-
if (
|
|
534
|
+
workerInfo = await require('../lib/worker-info').getEvaluatorInformation(sawOptions);
|
|
535
|
+
if (workerInfo)
|
|
550
536
|
break;
|
|
551
537
|
console.throb();
|
|
552
538
|
await a$sleep(Math.max(i + 1, 10) / 4);
|
|
553
539
|
console.throb();
|
|
554
540
|
}
|
|
555
541
|
|
|
556
|
-
if (
|
|
557
|
-
|
|
558
|
-
|
|
542
|
+
if (Object.keys(workerInfo.worktimes).length > 0)
|
|
543
|
+
{
|
|
544
|
+
bannerLog(' . Worktimes Available:');
|
|
545
|
+
for (const wt of workerInfo.worktimes)
|
|
546
|
+
bannerLog(` -\t${wt.name}@${wt.versions.join(';')}`);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (!workerInfo.webgpu.enabled)
|
|
559
550
|
{
|
|
560
551
|
bannerLog(' . WebGPU not enabled');
|
|
561
552
|
systemStateInfo.gpu = false;
|
|
@@ -563,13 +554,13 @@ z */
|
|
|
563
554
|
else
|
|
564
555
|
{
|
|
565
556
|
bannerLog(' . WebGPU available. GPU info:');
|
|
566
|
-
for (let descriptor in
|
|
567
|
-
bannerLog(` -\t${descriptor}: ${
|
|
568
|
-
systemStateInfo.gpu = Object.assign({},
|
|
557
|
+
for (let descriptor in workerInfo.webgpu.info)
|
|
558
|
+
bannerLog(` -\t${descriptor}: ${workerInfo.webgpu.info[descriptor]}`);
|
|
559
|
+
systemStateInfo.gpu = Object.assign({}, workerInfo.webgpu.info);
|
|
569
560
|
systemStateInfo.gpu.device
|
|
570
561
|
for (let descriptor in bannedGPUs)
|
|
571
562
|
{
|
|
572
|
-
if (bannedGPUs[descriptor].test(
|
|
563
|
+
if (bannedGPUs[descriptor].test(workerInfo.webgpu.info[descriptor]))
|
|
573
564
|
{
|
|
574
565
|
console.error(' * This GPU is not supported; disabling');
|
|
575
566
|
dcpWorkerOptions.cores = { cpu: dcpWorkerOptions.cores.cpu, gpu: 0 };
|
|
@@ -834,6 +825,7 @@ function handleSigDeath(signalName, signal)
|
|
|
834
825
|
if (handleSigDeath.count === 3)
|
|
835
826
|
die();
|
|
836
827
|
}
|
|
828
|
+
globalThis.die = () => handleSigDeath('QUIT', 15);
|
|
837
829
|
|
|
838
830
|
/**
|
|
839
831
|
* Returns the duration of the cleanup timeout in milliseconds. It is possible to specify zero.
|
package/lib/dashboard-tui.js
CHANGED
|
@@ -44,11 +44,12 @@ exports.init = function dashboard$$init(worker, options, bannerLogCumulative)
|
|
|
44
44
|
var screen = blessed.screen(screenConf);
|
|
45
45
|
|
|
46
46
|
worker.on('end', () => {
|
|
47
|
-
screen
|
|
48
|
-
|
|
47
|
+
if (screen?.destroy)
|
|
48
|
+
screen.destroy();
|
|
49
|
+
screen.destroy = false;
|
|
49
50
|
});
|
|
50
51
|
process.on('exit', () => {
|
|
51
|
-
if (screen)
|
|
52
|
+
if (screen?.destroy)
|
|
52
53
|
screen.destroy();
|
|
53
54
|
});
|
|
54
55
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file
|
|
3
|
-
* Check evaluator to determine if
|
|
4
|
-
*
|
|
2
|
+
* @file worker-reporting.js
|
|
3
|
+
* Check evaluator to determine if certain information including:
|
|
4
|
+
* - if webgpu is enabled, and what capabilities it has.
|
|
5
|
+
* - supported worktimes for the evaluator
|
|
6
|
+
*
|
|
5
7
|
* @author Ryan Saweczko, ryansaweczko@distributive.network
|
|
6
8
|
*
|
|
7
9
|
* @date Sept 2024
|
|
@@ -9,19 +11,20 @@
|
|
|
9
11
|
'use strict';
|
|
10
12
|
|
|
11
13
|
const kvin = require('kvin');
|
|
12
|
-
const evaluatorId = '
|
|
14
|
+
const evaluatorId = 'reportingCheck';
|
|
13
15
|
|
|
14
|
-
async function eval$$
|
|
16
|
+
async function eval$$getInfo()
|
|
15
17
|
{
|
|
18
|
+
const info = {};
|
|
16
19
|
if (typeof initWebGPU === 'function')
|
|
17
20
|
await initWebGPU();
|
|
18
21
|
|
|
19
22
|
if (!(typeof globalThis.navigator?.gpu === 'object'))
|
|
20
|
-
|
|
23
|
+
info.webgpu = { enabled: false };
|
|
21
24
|
|
|
22
25
|
try
|
|
23
26
|
{
|
|
24
|
-
const
|
|
27
|
+
const webgpuInfo = {};
|
|
25
28
|
const adapter = await navigator.gpu.requestAdapter();
|
|
26
29
|
/*
|
|
27
30
|
* Newer versions of dcp-evaluator use different methods to access the adapter info as the api changes/bugs are fixed.
|
|
@@ -35,29 +38,34 @@ async function eval$$webgpu()
|
|
|
35
38
|
if (adapter.info)
|
|
36
39
|
{
|
|
37
40
|
for (let key in adapter.info)
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
webgpuInfo[key] = adapter.info[key];
|
|
42
|
+
info.webgpu = { enabled: true, info: webgpuInfo };
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
{
|
|
46
|
+
const properties = ['vendor', 'architecture', 'device', 'description'];
|
|
47
|
+
const adapterInfo = await adapter.requestAdapterInfo();
|
|
48
|
+
for (let key of properties)
|
|
49
|
+
webgpuInfo[key] = adapterInfo[key];
|
|
50
|
+
info.webgpu = { enabled: true, info: webgpuInfo };
|
|
40
51
|
}
|
|
41
|
-
const properties = ['vendor', 'architecture', 'device', 'description'];
|
|
42
|
-
const adapterInfo = await adapter.requestAdapterInfo();
|
|
43
|
-
for (let key of properties)
|
|
44
|
-
info[key] = adapterInfo[key];
|
|
45
|
-
return { enabled: true, info: info };
|
|
46
52
|
}
|
|
47
53
|
catch (err)
|
|
48
54
|
{
|
|
49
|
-
|
|
55
|
+
info.webgpu = { enabled: false };
|
|
50
56
|
}
|
|
57
|
+
|
|
58
|
+
info.worktimes = globalThis.worktimes;
|
|
59
|
+
|
|
60
|
+
return info;
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
/**
|
|
54
|
-
* Connect to an evaluator instance and return back
|
|
55
|
-
* GPU device.
|
|
64
|
+
* Connect to an evaluator instance and return back information on the evaluator
|
|
56
65
|
* @param {object} evaluatorConfig - Object containing the hostname and port to connect to the evaluator instance on
|
|
57
|
-
* @returns {Promise} which resolves with undefined detection failed (ie couldn't connect to evaluator),
|
|
58
|
-
* or the object {enabled, info} with info on if webgpu exists, and if so what the gpu is.
|
|
66
|
+
* @returns {Promise} which resolves with undefined detection failed (ie couldn't connect to evaluator), or the information object
|
|
59
67
|
*/
|
|
60
|
-
function
|
|
68
|
+
function getEvaluatorInformation(evaluatorConfig)
|
|
61
69
|
{
|
|
62
70
|
const StandaloneWorker = require('dcp-client/lib/standaloneWorker').workerFactory(evaluatorConfig);
|
|
63
71
|
const { a$sleep } = require('dcp/utils');
|
|
@@ -65,7 +73,7 @@ function checkEvaluatorWebGPU(evaluatorConfig)
|
|
|
65
73
|
const evaluatorHandle = new StandaloneWorker({ name: 'DCP Sandbox #1', });
|
|
66
74
|
|
|
67
75
|
var noResponse, resolve;
|
|
68
|
-
const p$
|
|
76
|
+
const p$workerInfo = new Promise((res, rej) => { resolve = res; }).finally(() => {
|
|
69
77
|
evaluatorHandle.terminate();
|
|
70
78
|
noResponse.intr();
|
|
71
79
|
});
|
|
@@ -84,11 +92,11 @@ function checkEvaluatorWebGPU(evaluatorConfig)
|
|
|
84
92
|
|
|
85
93
|
const message = {
|
|
86
94
|
request: 'eval',
|
|
87
|
-
data: '(' + eval$$
|
|
95
|
+
data: '(' + eval$$getInfo.toString() + ')()',
|
|
88
96
|
msgId: evaluatorId,
|
|
89
97
|
}
|
|
90
98
|
evaluatorHandle.postMessage(kvin.marshal(message));
|
|
91
|
-
return p$
|
|
99
|
+
return p$workerInfo;
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
exports.
|
|
102
|
+
exports.getEvaluatorInformation = getEvaluatorInformation;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dcp-worker",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.19",
|
|
4
4
|
"description": "Node.js Worker for Distributive Compute Platform",
|
|
5
5
|
"main": "bin/dcp-worker",
|
|
6
6
|
"keywords": [
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"blessed": "^0.1.81",
|
|
41
41
|
"blessed-contrib": "4.11.0",
|
|
42
42
|
"chalk": "^4.1.0",
|
|
43
|
-
"dcp-client": "^4.4.
|
|
43
|
+
"dcp-client": "^4.4.24",
|
|
44
44
|
"kvin": "^1.2.7",
|
|
45
45
|
"posix-getopt": "^1.2.1",
|
|
46
46
|
"semver": "^7.3.8",
|