@scrypted/server 0.123.46 → 0.123.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cluster/cluster-labels.d.ts +1 -0
- package/dist/cluster/cluster-labels.js +4 -0
- package/dist/cluster/cluster-labels.js.map +1 -1
- package/dist/cluster/cluster-setup.d.ts +2 -0
- package/dist/cluster/cluster-setup.js +11 -3
- package/dist/cluster/cluster-setup.js.map +1 -1
- package/dist/cluster/connect-rpc-object.d.ts +18 -0
- package/dist/cluster/cpu-timer.d.ts +6 -0
- package/dist/cluster/cpu-timer.js +44 -0
- package/dist/cluster/cpu-timer.js.map +1 -0
- package/dist/plugin/plugin-api.d.ts +1 -0
- package/dist/plugin/plugin-api.js.map +1 -1
- package/dist/plugin/plugin-host.js +2 -0
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +2 -1
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +1 -0
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-cluster-main.d.ts +7 -3
- package/dist/scrypted-cluster-main.js +36 -13
- package/dist/scrypted-cluster-main.js.map +1 -1
- package/dist/services/cluster-fork.d.ts +2 -1
- package/dist/services/cluster-fork.js +3 -1
- package/dist/services/cluster-fork.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/python/cluster_setup.py +2 -2
- package/python/plugin_remote.py +5 -5
- package/src/cluster/cluster-labels.ts +4 -0
- package/src/cluster/cluster-setup.ts +13 -5
- package/src/cluster/connect-rpc-object.ts +18 -0
- package/src/cluster/cpu-timer.ts +48 -0
- package/src/plugin/plugin-api.ts +1 -0
- package/src/plugin/plugin-host.ts +2 -0
- package/src/plugin/plugin-remote-worker.ts +2 -1
- package/src/runtime.ts +1 -0
- package/src/scrypted-cluster-main.ts +43 -17
- package/src/services/cluster-fork.ts +7 -4
@@ -1,9 +1,27 @@
|
|
1
1
|
export interface ClusterObject {
|
2
|
+
/**
|
3
|
+
* Id of the cluster.
|
4
|
+
*/
|
2
5
|
id: string;
|
6
|
+
/**
|
7
|
+
* Address of the process that created this object.
|
8
|
+
*/
|
3
9
|
address: string;
|
10
|
+
/**
|
11
|
+
* Port of the process that created this object.
|
12
|
+
*/
|
4
13
|
port: number;
|
14
|
+
/**
|
15
|
+
* Id of the object within the source peer.
|
16
|
+
*/
|
5
17
|
proxyId: string;
|
18
|
+
/**
|
19
|
+
* Id of the source peer.
|
20
|
+
*/
|
6
21
|
sourceKey: string;
|
22
|
+
/**
|
23
|
+
* Hash of the object.
|
24
|
+
*/
|
7
25
|
sha256: string;
|
8
26
|
}
|
9
27
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { CpuInfo, cpus } from 'os';
|
2
|
+
|
3
|
+
function getIdleTotal(cpu: CpuInfo) {
|
4
|
+
const t = cpu.times;
|
5
|
+
const total = t.user + t.nice + t.sys + t.idle + t.irq;
|
6
|
+
const idle = t.idle;
|
7
|
+
return {
|
8
|
+
idle,
|
9
|
+
total,
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
export class CpuTimer {
|
14
|
+
previousSample: ReturnType<typeof cpus>;
|
15
|
+
maxSpeed = 0;
|
16
|
+
|
17
|
+
sample(): number {
|
18
|
+
const sample = cpus();
|
19
|
+
const previousSample = this.previousSample;
|
20
|
+
this.previousSample = sample;
|
21
|
+
|
22
|
+
// can cpu count change at runtime, who knows
|
23
|
+
if (!previousSample || previousSample.length !== sample.length)
|
24
|
+
return 0;
|
25
|
+
|
26
|
+
// cpu may be throttled in low power mode, so observe total speed to scale
|
27
|
+
let totalSpeed = 0;
|
28
|
+
|
29
|
+
const times = sample.map((v, i) => {
|
30
|
+
totalSpeed += v.speed;
|
31
|
+
const c = getIdleTotal(v);
|
32
|
+
const p = getIdleTotal(previousSample[i]);
|
33
|
+
const total = c.total - p.total;
|
34
|
+
const idle = c.idle - p.idle;
|
35
|
+
return 1 - idle / total;
|
36
|
+
});
|
37
|
+
|
38
|
+
this.maxSpeed = Math.max(this.maxSpeed, totalSpeed);
|
39
|
+
|
40
|
+
// will return a value between 0 and 1, where 1 is full cpu speed
|
41
|
+
// the cpu usage is scaled by the clock speed
|
42
|
+
// so if the cpu is running at 1ghz out of 3ghz, the cpu usage is scaled by 1/3
|
43
|
+
const clockScale = totalSpeed / this.maxSpeed;
|
44
|
+
|
45
|
+
const total = times.reduce((p, c) => p + c, 0);
|
46
|
+
return total / sample.length * clockScale;
|
47
|
+
}
|
48
|
+
}
|
package/src/plugin/plugin-api.ts
CHANGED
@@ -248,6 +248,7 @@ export class PluginHost {
|
|
248
248
|
const loadZipOptions: PluginRemoteLoadZipOptions = {
|
249
249
|
clusterId: this.scrypted.clusterId,
|
250
250
|
clusterSecret: this.scrypted.clusterSecret,
|
251
|
+
clusterWorkerId: await this.clusterWorkerId,
|
251
252
|
// debug flag can be used to affect path resolution for sourcemaps etc.
|
252
253
|
debug: !!pluginDebug,
|
253
254
|
zipHash: this.zipHash,
|
@@ -390,6 +391,7 @@ export class PluginHost {
|
|
390
391
|
await clusterSetup.initializeCluster({
|
391
392
|
clusterId: this.scrypted.clusterId,
|
392
393
|
clusterSecret: this.scrypted.clusterSecret,
|
394
|
+
clusterWorkerId: this.scrypted.serverClusterWorkerId,
|
393
395
|
});
|
394
396
|
return this.scrypted.clusterFork;
|
395
397
|
})(),
|
@@ -115,7 +115,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
115
115
|
await initializeCluster(zipOptions);
|
116
116
|
|
117
117
|
scrypted.connectRPCObject = connectRPCObject;
|
118
|
-
scrypted.clusterManager = new ClusterManagerImpl(api);
|
118
|
+
scrypted.clusterManager = new ClusterManagerImpl(api, zipOptions.clusterWorkerId);
|
119
119
|
|
120
120
|
if (worker_threads.isMainThread) {
|
121
121
|
const fsDir = path.join(unzippedPath, 'fs')
|
@@ -332,6 +332,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
332
332
|
}
|
333
333
|
|
334
334
|
const forkOptions = Object.assign({}, zipOptions);
|
335
|
+
forkOptions.clusterWorkerId = await clusterWorkerId || forkOptions.clusterWorkerId;
|
335
336
|
forkOptions.fork = true;
|
336
337
|
forkOptions.main = options?.filename;
|
337
338
|
const forkZipAPI = new PluginZipAPI(() => zipAPI.getZip());
|
package/src/runtime.ts
CHANGED
@@ -65,6 +65,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
65
65
|
clusterId = crypto.randomBytes(3).toString('hex');
|
66
66
|
clusterSecret = process.env.SCRYPTED_CLUSTER_SECRET || crypto.randomBytes(16).toString('hex');
|
67
67
|
clusterWorkers = new Map<string, RunningClusterWorker>();
|
68
|
+
serverClusterWorkerId: string;
|
68
69
|
plugins: { [id: string]: PluginHost } = {};
|
69
70
|
pluginDevices: { [id: string]: PluginDevice } = {};
|
70
71
|
devices: { [id: string]: DeviceProxyPair } = {};
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import type { ClusterManager,
|
1
|
+
import type { ClusterManager, ForkOptions } from '@scrypted/types';
|
2
2
|
import crypto from 'crypto';
|
3
3
|
import { once } from 'events';
|
4
4
|
import net from 'net';
|
@@ -8,9 +8,10 @@ import type { Readable } from 'stream';
|
|
8
8
|
import tls from 'tls';
|
9
9
|
import type { createSelfSignedCertificate } from './cert';
|
10
10
|
import { computeClusterObjectHash } from './cluster/cluster-hash';
|
11
|
-
import { getClusterLabels } from './cluster/cluster-labels';
|
11
|
+
import { getClusterLabels, getClusterWorkerWeight } from './cluster/cluster-labels';
|
12
12
|
import { getScryptedClusterMode, InitializeCluster, setupCluster } from './cluster/cluster-setup';
|
13
13
|
import type { ClusterObject } from './cluster/connect-rpc-object';
|
14
|
+
import { CpuTimer } from './cluster/cpu-timer';
|
14
15
|
import type { PluginAPI } from './plugin/plugin-api';
|
15
16
|
import { getPluginVolume, getScryptedVolume } from './plugin/plugin-volume';
|
16
17
|
import { prepareZip } from './plugin/runtime/node-worker-common';
|
@@ -20,10 +21,10 @@ import { RpcPeer } from './rpc';
|
|
20
21
|
import { createRpcDuplexSerializer } from './rpc-serializer';
|
21
22
|
import type { ScryptedRuntime } from './runtime';
|
22
23
|
import type { ClusterForkService } from './services/cluster-fork';
|
23
|
-
import { sleep } from './sleep';
|
24
|
-
import type { ServiceControl } from './services/service-control';
|
25
24
|
import { EnvControl } from './services/env';
|
26
25
|
import { Info } from './services/info';
|
26
|
+
import type { ServiceControl } from './services/service-control';
|
27
|
+
import { sleep } from './sleep';
|
27
28
|
|
28
29
|
installSourceMapSupport({
|
29
30
|
environment: 'node',
|
@@ -73,6 +74,7 @@ type ConnectForkWorker = (auth: ClusterObject, properties: ClusterWorkerProperti
|
|
73
74
|
|
74
75
|
export interface ClusterWorkerProperties {
|
75
76
|
labels: string[];
|
77
|
+
weight: number;
|
76
78
|
}
|
77
79
|
|
78
80
|
export interface RunningClusterWorker extends ClusterWorkerProperties {
|
@@ -82,6 +84,8 @@ export interface RunningClusterWorker extends ClusterWorkerProperties {
|
|
82
84
|
fork: Promise<ClusterForkParam>;
|
83
85
|
forks: Set<ClusterForkOptions>;
|
84
86
|
address: string;
|
87
|
+
weight: number;
|
88
|
+
cpuUsage: number;
|
85
89
|
}
|
86
90
|
|
87
91
|
export class PeerLiveness {
|
@@ -118,7 +122,7 @@ export interface ClusterForkResultInterface {
|
|
118
122
|
|
119
123
|
export type ClusterForkParam = (runtime: string, options: RuntimeWorkerOptions, peerLiveness: PeerLiveness, getZip: () => Promise<Buffer>) => Promise<ClusterForkResultInterface>;
|
120
124
|
|
121
|
-
function createClusterForkParam(mainFilename: string, clusterId: string, clusterSecret: string) {
|
125
|
+
function createClusterForkParam(mainFilename: string, clusterId: string, clusterSecret: string, clusterWorkerId: string) {
|
122
126
|
const clusterForkParam: ClusterForkParam = async (runtime, runtimeWorkerOptions, peerLiveness, getZip) => {
|
123
127
|
let runtimeWorker: RuntimeWorker;
|
124
128
|
|
@@ -164,7 +168,7 @@ function createClusterForkParam(mainFilename: string, clusterId: string, cluster
|
|
164
168
|
let ping: any;
|
165
169
|
try {
|
166
170
|
const initializeCluster: InitializeCluster = await threadPeer.getParam('initializeCluster');
|
167
|
-
await initializeCluster({ clusterId, clusterSecret });
|
171
|
+
await initializeCluster({ clusterId, clusterSecret, clusterWorkerId });
|
168
172
|
getRemote = await threadPeer.getParam('getRemote');
|
169
173
|
ping = await threadPeer.getParam('ping');
|
170
174
|
}
|
@@ -204,9 +208,11 @@ export function startClusterClient(mainFilename: string, serviceControl?: Servic
|
|
204
208
|
console.log('Cluster client starting.');
|
205
209
|
|
206
210
|
const envControl = new EnvControl();
|
211
|
+
const cpuTimer = new CpuTimer();
|
207
212
|
|
208
213
|
const originalClusterAddress = process.env.SCRYPTED_CLUSTER_ADDRESS;
|
209
214
|
const labels = getClusterLabels();
|
215
|
+
const weight = getClusterWorkerWeight();
|
210
216
|
|
211
217
|
const clusterSecret = process.env.SCRYPTED_CLUSTER_SECRET;
|
212
218
|
const clusterMode = getScryptedClusterMode();
|
@@ -256,6 +262,7 @@ export function startClusterClient(mainFilename: string, serviceControl?: Servic
|
|
256
262
|
peer.params['service-control'] = serviceControl;
|
257
263
|
peer.params['env-control'] = envControl;
|
258
264
|
peer.params['info'] = new Info();
|
265
|
+
peer.params['cpu'] = async () => cpuTimer.sample();
|
259
266
|
|
260
267
|
const { localAddress, localPort } = socket;
|
261
268
|
console.log('Cluster server connected.', localAddress, localPort);
|
@@ -277,14 +284,14 @@ export function startClusterClient(mainFilename: string, serviceControl?: Servic
|
|
277
284
|
|
278
285
|
const properties: ClusterWorkerProperties = {
|
279
286
|
labels,
|
287
|
+
weight,
|
280
288
|
};
|
281
289
|
|
282
290
|
const { clusterId, clusterWorkerId } = await connectForkWorker(auth, properties);
|
283
|
-
process.env.SCRYPTED_CLUSTER_WORKER_ID = clusterWorkerId;
|
284
291
|
const clusterPeerSetup = setupCluster(peer);
|
285
|
-
await clusterPeerSetup.initializeCluster({ clusterId, clusterSecret });
|
292
|
+
await clusterPeerSetup.initializeCluster({ clusterId, clusterSecret, clusterWorkerId });
|
286
293
|
|
287
|
-
peer.params['fork'] = createClusterForkParam(mainFilename, clusterId, clusterSecret);
|
294
|
+
peer.params['fork'] = createClusterForkParam(mainFilename, clusterId, clusterSecret, clusterWorkerId);
|
288
295
|
|
289
296
|
await peer.killed;
|
290
297
|
}
|
@@ -301,18 +308,26 @@ export function startClusterClient(mainFilename: string, serviceControl?: Servic
|
|
301
308
|
}
|
302
309
|
|
303
310
|
export function createClusterServer(mainFilename: string, scryptedRuntime: ScryptedRuntime, certificate: ReturnType<typeof createSelfSignedCertificate>) {
|
304
|
-
|
305
|
-
process.env.SCRYPTED_CLUSTER_WORKER_ID = serverClusterWorkerId;
|
311
|
+
scryptedRuntime.serverClusterWorkerId = crypto.randomUUID();
|
306
312
|
const serverWorker: RunningClusterWorker = {
|
307
313
|
labels: getClusterLabels(),
|
308
|
-
id: serverClusterWorkerId,
|
314
|
+
id: scryptedRuntime.serverClusterWorkerId,
|
309
315
|
peer: undefined,
|
310
|
-
fork: Promise.resolve(createClusterForkParam(mainFilename, scryptedRuntime.clusterId, scryptedRuntime.clusterSecret)),
|
316
|
+
fork: Promise.resolve(createClusterForkParam(mainFilename, scryptedRuntime.clusterId, scryptedRuntime.clusterSecret, scryptedRuntime.serverClusterWorkerId)),
|
311
317
|
name: process.env.SCRYPTED_CLUSTER_WORKER_NAME || os.hostname(),
|
312
318
|
address: process.env.SCRYPTED_CLUSTER_ADDRESS,
|
319
|
+
weight: getClusterWorkerWeight(),
|
313
320
|
forks: new Set(),
|
321
|
+
cpuUsage: 0,
|
314
322
|
};
|
315
|
-
scryptedRuntime.clusterWorkers.set(serverClusterWorkerId, serverWorker);
|
323
|
+
scryptedRuntime.clusterWorkers.set(scryptedRuntime.serverClusterWorkerId, serverWorker);
|
324
|
+
|
325
|
+
{
|
326
|
+
const cpuTimer = new CpuTimer();
|
327
|
+
setInterval(() => {
|
328
|
+
serverWorker.cpuUsage = cpuTimer.sample();
|
329
|
+
}, 1000);
|
330
|
+
}
|
316
331
|
|
317
332
|
const server = tls.createServer({
|
318
333
|
key: certificate.serviceKey,
|
@@ -348,6 +363,7 @@ export function createClusterServer(mainFilename: string, scryptedRuntime: Scryp
|
|
348
363
|
name: auth.id,
|
349
364
|
address: socket.remoteAddress,
|
350
365
|
forks: new Set(),
|
366
|
+
cpuUsage: 0,
|
351
367
|
};
|
352
368
|
scryptedRuntime.clusterWorkers.set(id, worker);
|
353
369
|
peer.killedSafe.finally(() => {
|
@@ -357,6 +373,16 @@ export function createClusterServer(mainFilename: string, scryptedRuntime: Scryp
|
|
357
373
|
scryptedRuntime.clusterWorkers.delete(id);
|
358
374
|
});
|
359
375
|
console.log('Cluster client authenticated.', socket.remoteAddress, socket.remotePort, properties);
|
376
|
+
|
377
|
+
let cpu: Promise<() => Promise<number>>;
|
378
|
+
const cpuTimer = setInterval(async () => {
|
379
|
+
cpu ||= peer.getParam('cpu');
|
380
|
+
const usage = await (await cpu)();
|
381
|
+
worker.cpuUsage = usage;
|
382
|
+
}, 1000);
|
383
|
+
peer.killedSafe.finally(() => {
|
384
|
+
clearInterval(cpuTimer);
|
385
|
+
});
|
360
386
|
}
|
361
387
|
catch (e) {
|
362
388
|
peer.kill(e);
|
@@ -378,18 +404,18 @@ export class ClusterManagerImpl implements ClusterManager {
|
|
378
404
|
private clusterServicePromise: Promise<ClusterForkService>;
|
379
405
|
private clusterMode = getScryptedClusterMode()?.[0];
|
380
406
|
|
381
|
-
constructor(private api: PluginAPI) {
|
407
|
+
constructor(private api: PluginAPI, private clusterWorkerId: string) {
|
382
408
|
}
|
383
409
|
|
384
410
|
getClusterWorkerId(): string {
|
385
|
-
return
|
411
|
+
return this.clusterWorkerId;
|
386
412
|
}
|
387
413
|
|
388
414
|
getClusterMode(): 'server' | 'client' | undefined {
|
389
415
|
return this.clusterMode;
|
390
416
|
}
|
391
417
|
|
392
|
-
async getClusterWorkers()
|
418
|
+
async getClusterWorkers() {
|
393
419
|
const clusterFork = await this.getClusterService();
|
394
420
|
return clusterFork.getClusterWorkers();
|
395
421
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { ClusterFork, ClusterWorker } from "@scrypted/types";
|
1
2
|
import { matchesClusterLabels } from "../cluster/cluster-labels";
|
2
3
|
import type { RuntimeWorkerOptions } from "../plugin/runtime/runtime-worker";
|
3
4
|
import { RpcPeer } from "../rpc";
|
@@ -68,7 +69,7 @@ export class ClusterForkService {
|
|
68
69
|
// this enforces the "prefer" label.
|
69
70
|
matchingWorkers = matchingWorkers.filter(({ matches }) => matches === bestMatch.matches)
|
70
71
|
// sort by number of forks, to distribute load.
|
71
|
-
.sort((a, b) => a.worker.forks.size - b.worker.forks.size);
|
72
|
+
.sort((a, b) => a.worker.forks.size * a.worker.weight - b.worker.forks.size * b.worker.weight);
|
72
73
|
|
73
74
|
worker = matchingWorkers[0]?.worker;
|
74
75
|
}
|
@@ -98,13 +99,15 @@ export class ClusterForkService {
|
|
98
99
|
return ret;
|
99
100
|
};
|
100
101
|
|
101
|
-
async getClusterWorkers() {
|
102
|
-
const ret:
|
102
|
+
async getClusterWorkers(): Promise<Record<string, ClusterWorker>> {
|
103
|
+
const ret: Record<string, ClusterWorker> = {};
|
103
104
|
for (const worker of this.runtime.clusterWorkers.values()) {
|
104
105
|
ret[worker.id] = {
|
106
|
+
id: worker.id,
|
105
107
|
name: worker.name,
|
106
108
|
labels: worker.labels,
|
107
|
-
forks: [...worker.forks],
|
109
|
+
forks: [...worker.forks] as ClusterFork[],
|
110
|
+
cpuUsage: worker.cpuUsage,
|
108
111
|
};
|
109
112
|
}
|
110
113
|
return ret;
|