@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.
Files changed (39) hide show
  1. package/dist/cluster/cluster-labels.d.ts +1 -0
  2. package/dist/cluster/cluster-labels.js +4 -0
  3. package/dist/cluster/cluster-labels.js.map +1 -1
  4. package/dist/cluster/cluster-setup.d.ts +2 -0
  5. package/dist/cluster/cluster-setup.js +11 -3
  6. package/dist/cluster/cluster-setup.js.map +1 -1
  7. package/dist/cluster/connect-rpc-object.d.ts +18 -0
  8. package/dist/cluster/cpu-timer.d.ts +6 -0
  9. package/dist/cluster/cpu-timer.js +44 -0
  10. package/dist/cluster/cpu-timer.js.map +1 -0
  11. package/dist/plugin/plugin-api.d.ts +1 -0
  12. package/dist/plugin/plugin-api.js.map +1 -1
  13. package/dist/plugin/plugin-host.js +2 -0
  14. package/dist/plugin/plugin-host.js.map +1 -1
  15. package/dist/plugin/plugin-remote-worker.js +2 -1
  16. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  17. package/dist/runtime.d.ts +1 -0
  18. package/dist/runtime.js +1 -0
  19. package/dist/runtime.js.map +1 -1
  20. package/dist/scrypted-cluster-main.d.ts +7 -3
  21. package/dist/scrypted-cluster-main.js +36 -13
  22. package/dist/scrypted-cluster-main.js.map +1 -1
  23. package/dist/services/cluster-fork.d.ts +2 -1
  24. package/dist/services/cluster-fork.js +3 -1
  25. package/dist/services/cluster-fork.js.map +1 -1
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +2 -2
  28. package/python/cluster_setup.py +2 -2
  29. package/python/plugin_remote.py +5 -5
  30. package/src/cluster/cluster-labels.ts +4 -0
  31. package/src/cluster/cluster-setup.ts +13 -5
  32. package/src/cluster/connect-rpc-object.ts +18 -0
  33. package/src/cluster/cpu-timer.ts +48 -0
  34. package/src/plugin/plugin-api.ts +1 -0
  35. package/src/plugin/plugin-host.ts +2 -0
  36. package/src/plugin/plugin-remote-worker.ts +2 -1
  37. package/src/runtime.ts +1 -0
  38. package/src/scrypted-cluster-main.ts +43 -17
  39. 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
+ }
@@ -164,6 +164,7 @@ export interface PluginRemoteLoadZipOptions {
164
164
  main?: string;
165
165
 
166
166
  clusterId: string;
167
+ clusterWorkerId: string;
167
168
  clusterSecret: string;
168
169
  }
169
170
 
@@ -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, ClusterWorker, ForkOptions } from '@scrypted/types';
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
- const serverClusterWorkerId = crypto.randomUUID();
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 process.env.SCRYPTED_CLUSTER_WORKER_ID;
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(): Promise<Record<string, ClusterWorker>> {
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: any = {};
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;