@hotmeshio/hotmesh 0.0.46 → 0.0.47

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.
@@ -4,6 +4,8 @@ import { AppSubscriptions, AppTransitions, AppVID } from "../types/app";
4
4
  import { RedisClient, RedisMulti } from "../types/redis";
5
5
  import { StringAnyType } from "../types/serializer";
6
6
  import { StreamCode, StreamStatus } from "../types/stream";
7
+ import { SystemHealth } from '../types/quorum';
8
+ export declare function getSystemHealth(): Promise<SystemHealth>;
7
9
  export declare function sleepFor(ms: number): Promise<unknown>;
8
10
  export declare function guid(): string;
9
11
  export declare function deterministicRandom(seed: number): number;
@@ -1,7 +1,46 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.sleepFor = void 0;
6
+ exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.sleepFor = exports.getSystemHealth = void 0;
7
+ const os_1 = __importDefault(require("os"));
8
+ const systeminformation_1 = __importDefault(require("systeminformation"));
4
9
  const nanoid_1 = require("nanoid");
10
+ async function safeExecute(operation, defaultValue) {
11
+ try {
12
+ return await operation;
13
+ }
14
+ catch (error) {
15
+ console.error(`Operation Error: ${error}`);
16
+ return defaultValue;
17
+ }
18
+ }
19
+ async function getSystemHealth() {
20
+ const totalMemory = os_1.default.totalmem();
21
+ const freeMemory = os_1.default.freemem();
22
+ const usedMemory = totalMemory - freeMemory;
23
+ const cpus = os_1.default.cpus();
24
+ // CPU load calculation remains unchanged
25
+ const cpuLoad = cpus.map((cpu, i) => {
26
+ const total = Object.values(cpu.times).reduce((acc, tv) => acc + tv, 0);
27
+ const idle = cpu.times.idle;
28
+ const usage = ((total - idle) / total) * 100;
29
+ return { [`CPU ${i} Usage`]: `${usage.toFixed(2)}%` };
30
+ });
31
+ // Wrap each systeminformation call with safeExecute
32
+ const networkStats = await safeExecute(systeminformation_1.default.networkStats(), []);
33
+ // Construct the system health object with error handling in mind
34
+ const systemHealth = {
35
+ TotalMemoryGB: `${(totalMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
36
+ FreeMemoryGB: `${(freeMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
37
+ UsedMemoryGB: `${(usedMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
38
+ CPULoad: cpuLoad,
39
+ NetworkStats: networkStats,
40
+ };
41
+ return systemHealth;
42
+ }
43
+ exports.getSystemHealth = getSystemHealth;
5
44
  async function sleepFor(ms) {
6
45
  return new Promise((resolve) => setTimeout(resolve, ms));
7
46
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -57,7 +57,14 @@
57
57
  "test:durable:loopactivity": "NODE_ENV=test jest ./tests/durable/loopactivity/index.test.ts --detectOpenHandles --forceExit --verbose",
58
58
  "test:durable:nested": "NODE_ENV=test jest ./tests/durable/nested/index.test.ts --detectOpenHandles --forceExit --verbose"
59
59
  },
60
- "keywords": ["durable workflow", "hotmesh", "service mesh", "workflows", "operational data", "redis"],
60
+ "keywords": [
61
+ "durable workflow",
62
+ "hotmesh",
63
+ "service mesh",
64
+ "workflows",
65
+ "operational data",
66
+ "redis"
67
+ ],
61
68
  "author": "luke.birdeau@gmail.com",
62
69
  "license": "SEE LICENSE IN LICENSE",
63
70
  "dependencies": {
@@ -66,6 +73,7 @@
66
73
  "js-yaml": "^4.1.0",
67
74
  "ms": "^2.1.3",
68
75
  "nanoid": "^3.3.6",
76
+ "systeminformation": "^5.22.2",
69
77
  "winston": "^3.8.2"
70
78
  },
71
79
  "devDependencies": {
@@ -77,6 +77,10 @@ export declare class MeshOSService {
77
77
  * Creates an FT search index
78
78
  */
79
79
  static createIndex(): Promise<void>;
80
+ /**
81
+ * Lists FT search indexes
82
+ */
83
+ static listIndexes(): Promise<string[]>;
80
84
  /**
81
85
  * stop the workers
82
86
  * @returns {Promise<void>}
@@ -39,7 +39,15 @@ class MeshOSService {
39
39
  static async createIndex() {
40
40
  const my = new this();
41
41
  const hmClient = await MeshOSService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
42
- search_1.Search.configureSearchIndex(hmClient, my.search);
42
+ await search_1.Search.configureSearchIndex(hmClient, my.search);
43
+ }
44
+ /**
45
+ * Lists FT search indexes
46
+ */
47
+ static async listIndexes() {
48
+ const my = new this();
49
+ const hmClient = await MeshOSService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
50
+ return await search_1.Search.listSearchIndexes(hmClient);
43
51
  }
44
52
  /**
45
53
  * stop the workers
@@ -8,6 +8,7 @@ export declare class Search {
8
8
  searchSessionIndex: number;
9
9
  hotMeshClient: HotMesh;
10
10
  store: StoreService<RedisClient, RedisMulti> | null;
11
+ constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string);
11
12
  safeKey(key: string): string;
12
13
  /**
13
14
  * For those deployments with a redis stack backend (with the FT module),
@@ -17,7 +18,13 @@ export declare class Search {
17
18
  * in the HASH.
18
19
  */
19
20
  static configureSearchIndex(hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void>;
20
- constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string);
21
+ /**
22
+ * For those deployments with a redis stack backend (with the FT module),
23
+ * this method will list all search indexes.
24
+ * @param {HotMesh} hotMeshClient - the hotmesh client
25
+ * @returns {Promise<string[]>} - the list of search indexes
26
+ */
27
+ static listSearchIndexes(hotMeshClient: HotMesh): Promise<string[]>;
21
28
  /**
22
29
  * increments the index to return a unique search session guid when
23
30
  * calling any method that produces side effects (changes the value)
@@ -3,8 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Search = void 0;
4
4
  const key_1 = require("../../modules/key");
5
5
  class Search {
6
+ constructor(workflowId, hotMeshClient, searchSessionId) {
7
+ this.searchSessionIndex = 0;
8
+ const keyParams = {
9
+ appId: hotMeshClient.appId,
10
+ jobId: workflowId
11
+ };
12
+ this.jobId = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
13
+ this.searchSessionId = searchSessionId;
14
+ this.hotMeshClient = hotMeshClient;
15
+ this.store = hotMeshClient.engine.store;
16
+ }
6
17
  safeKey(key) {
7
- //note: protect the execution namespace with a prefix
8
18
  return `_${key}`;
9
19
  }
10
20
  /**
@@ -40,16 +50,16 @@ class Search {
40
50
  }
41
51
  }
42
52
  }
43
- constructor(workflowId, hotMeshClient, searchSessionId) {
44
- this.searchSessionIndex = 0;
45
- const keyParams = {
46
- appId: hotMeshClient.appId,
47
- jobId: workflowId
48
- };
49
- this.jobId = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
50
- this.searchSessionId = searchSessionId;
51
- this.hotMeshClient = hotMeshClient;
52
- this.store = hotMeshClient.engine.store;
53
+ /**
54
+ * For those deployments with a redis stack backend (with the FT module),
55
+ * this method will list all search indexes.
56
+ * @param {HotMesh} hotMeshClient - the hotmesh client
57
+ * @returns {Promise<string[]>} - the list of search indexes
58
+ */
59
+ static async listSearchIndexes(hotMeshClient) {
60
+ const store = hotMeshClient.engine.store;
61
+ const searchIndexes = await store.exec('FT._LIST');
62
+ return searchIndexes;
53
63
  }
54
64
  /**
55
65
  * increments the index to return a unique search session guid when
@@ -4,7 +4,7 @@ import { QuorumService } from '../quorum';
4
4
  import { WorkerService } from '../worker';
5
5
  import { JobState, JobData, JobOutput, JobStatus, JobInterruptOptions } from '../../types/job';
6
6
  import { HotMeshConfig, HotMeshManifest } from '../../types/hotmesh';
7
- import { JobMessageCallback, QuorumProfile } from '../../types/quorum';
7
+ import { JobMessageCallback, QuorumProfile, ThrottleOptions } from '../../types/quorum';
8
8
  import { JobStatsInput, GetStatsOptions, IdsResponse, StatsResponse } from '../../types/stats';
9
9
  import { StreamCode, StreamData, StreamDataResponse, StreamStatus } from '../../types/stream';
10
10
  import { StringAnyType, StringStringType } from '../../types/serializer';
@@ -33,6 +33,7 @@ declare class HotMeshService {
33
33
  pubsub(topic: string, data?: JobData, context?: JobState | null, timeout?: number): Promise<JobOutput>;
34
34
  add(streamData: StreamData | StreamDataResponse): Promise<string>;
35
35
  rollCall(delay?: number): Promise<QuorumProfile[]>;
36
+ throttle(options: ThrottleOptions): Promise<boolean>;
36
37
  plan(path: string): Promise<HotMeshManifest>;
37
38
  deploy(pathOrYAML: string): Promise<HotMeshManifest>;
38
39
  activate(version: string, delay?: number): Promise<boolean>;
@@ -89,10 +89,24 @@ class HotMeshService {
89
89
  async add(streamData) {
90
90
  return await this.engine.add(streamData);
91
91
  }
92
- // ************* COMPILER METHODS *************
92
+ // ************* QUORUM METHODS *************
93
93
  async rollCall(delay) {
94
94
  return await this.quorum?.rollCall(delay);
95
95
  }
96
+ async throttle(options) {
97
+ const throttleMessage = {
98
+ type: 'throttle',
99
+ throttle: options.throttle,
100
+ };
101
+ if (options.guid) {
102
+ throttleMessage.guid = options.guid;
103
+ }
104
+ else if (options.topic) {
105
+ throttleMessage.topic = options.topic;
106
+ }
107
+ return await this.quorum?.pub(throttleMessage);
108
+ }
109
+ // ************* COMPILER METHODS *************
96
110
  async plan(path) {
97
111
  return await this.engine?.plan(path);
98
112
  }
@@ -4,8 +4,8 @@ exports.QuorumService = void 0;
4
4
  const enums_1 = require("../../modules/enums");
5
5
  const utils_1 = require("../../modules/utils");
6
6
  const compiler_1 = require("../compiler");
7
- const redis_1 = require("../store/clients/redis");
8
7
  const ioredis_1 = require("../store/clients/ioredis");
8
+ const redis_1 = require("../store/clients/redis");
9
9
  const ioredis_2 = require("../sub/clients/ioredis");
10
10
  const redis_2 = require("../sub/clients/redis");
11
11
  const hotmesh_1 = require("../../types/hotmesh");
@@ -107,6 +107,7 @@ class QuorumService {
107
107
  stream,
108
108
  counts: this.engine.router.counts,
109
109
  timestamp: (0, utils_1.formatISODate)(new Date()),
110
+ system: await (0, utils_1.getSystemHealth)(),
110
111
  };
111
112
  }
112
113
  this.store.publish(hotmesh_1.KeyType.QUORUM, {
@@ -116,6 +116,7 @@ class WorkerService {
116
116
  stream: this.stream.mintKey(key_1.KeyType.STREAMS, params),
117
117
  counts: this.router.counts,
118
118
  timestamp: (0, utils_1.formatISODate)(new Date()),
119
+ system: await (0, utils_1.getSystemHealth)(),
119
120
  };
120
121
  }
121
122
  this.store.publish(key_1.KeyType.QUORUM, {
@@ -12,7 +12,7 @@ export { JobData, JobsData, JobMetadata, JobOutput, JobState, JobStatus, Partial
12
12
  export { MappingStatements } from './map';
13
13
  export { Pipe, PipeItem, PipeItems } from './pipe';
14
14
  export { HotMesh, HotMeshApp, HotMeshApps, HotMeshConfig, HotMeshEngine, RedisConfig, HotMeshGraph, HotMeshManifest, HotMeshSettings, HotMeshWorker, KeyStoreParams, KeyType } from './hotmesh';
15
- export { ActivateMessage, JobMessage, JobMessageCallback, PingMessage, PongMessage, QuorumMessage, QuorumMessageCallback, QuorumProfile, SubscriptionCallback, ThrottleMessage, WorkMessage } from './quorum';
15
+ export { ActivateMessage, JobMessage, JobMessageCallback, PingMessage, PongMessage, QuorumMessage, QuorumMessageCallback, QuorumProfile, SubscriptionCallback, SystemHealth, ThrottleMessage, ThrottleOptions, WorkMessage } from './quorum';
16
16
  export { MultiResponseFlags, RedisClient, RedisMulti } from './redis';
17
17
  export { RedisClientType, RedisMultiType } from './redisclient';
18
18
  export { JSONSchema, StringAnyType, StringScalarType, StringStringType, SymbolMap, SymbolMaps, SymbolRanges, Symbols, SymbolSets } from './serializer';
@@ -1,4 +1,36 @@
1
1
  import { JobOutput } from "./job";
2
+ interface CPULoad {
3
+ [cpu: string]: string;
4
+ }
5
+ interface NetworkStat {
6
+ iface: string;
7
+ operstate: string;
8
+ rx_bytes: number;
9
+ rx_dropped: number;
10
+ rx_errors: number;
11
+ tx_bytes: number;
12
+ tx_dropped: number;
13
+ tx_errors: number;
14
+ rx_sec: number;
15
+ tx_sec: number;
16
+ ms: number;
17
+ }
18
+ /** reveals: memory, cpu, network */
19
+ export interface SystemHealth {
20
+ TotalMemoryGB: string;
21
+ FreeMemoryGB: string;
22
+ UsedMemoryGB: string;
23
+ CPULoad: CPULoad[];
24
+ NetworkStats: NetworkStat[];
25
+ }
26
+ export type ThrottleOptions = {
27
+ /** target an engine OR worker by GUID */
28
+ guid?: string;
29
+ /** target a worker quorum */
30
+ topic?: string;
31
+ /** in milliseconds; default is 0 */
32
+ throttle: number;
33
+ };
2
34
  export interface QuorumProfile {
3
35
  namespace: string;
4
36
  app_id: string;
@@ -8,6 +40,7 @@ export interface QuorumProfile {
8
40
  stream_depth?: number;
9
41
  counts?: Record<string, number>;
10
42
  timestamp?: string;
43
+ system?: SystemHealth;
11
44
  }
12
45
  export interface PingMessage {
13
46
  type: 'ping';
@@ -60,3 +93,4 @@ export interface QuorumMessageCallback {
60
93
  * to the new version without any downtime and a coordinating parent server.
61
94
  */
62
95
  export type QuorumMessage = PingMessage | PongMessage | ActivateMessage | WorkMessage | JobMessage | ThrottleMessage | CronMessage;
96
+ export {};
package/modules/utils.ts CHANGED
@@ -1,9 +1,50 @@
1
+ import os from 'os';
2
+ import si from 'systeminformation';
1
3
  import { nanoid } from "nanoid";
2
4
  import { StoreService } from "../services/store";
3
5
  import { AppSubscriptions, AppTransitions, AppVID } from "../types/app";
4
6
  import { RedisClient, RedisMulti } from "../types/redis";
5
7
  import { StringAnyType } from "../types/serializer";
6
8
  import { StreamCode, StreamStatus } from "../types/stream";
9
+ import { SystemHealth } from '../types/quorum';
10
+
11
+ async function safeExecute<T>(operation: Promise<T>, defaultValue: T): Promise<T> {
12
+ try {
13
+ return await operation;
14
+ } catch (error) {
15
+ console.error(`Operation Error: ${error}`);
16
+ return defaultValue;
17
+ }
18
+ }
19
+
20
+ export async function getSystemHealth(): Promise<SystemHealth> {
21
+ const totalMemory = os.totalmem();
22
+ const freeMemory = os.freemem();
23
+ const usedMemory = totalMemory - freeMemory;
24
+ const cpus = os.cpus();
25
+
26
+ // CPU load calculation remains unchanged
27
+ const cpuLoad = cpus.map((cpu, i) => {
28
+ const total = Object.values(cpu.times).reduce((acc, tv) => acc + tv, 0);
29
+ const idle = cpu.times.idle;
30
+ const usage = ((total - idle) / total) * 100;
31
+ return { [`CPU ${i} Usage`]: `${usage.toFixed(2)}%` };
32
+ });
33
+
34
+ // Wrap each systeminformation call with safeExecute
35
+ const networkStats = await safeExecute(si.networkStats(), []);
36
+
37
+ // Construct the system health object with error handling in mind
38
+ const systemHealth = {
39
+ TotalMemoryGB: `${(totalMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
40
+ FreeMemoryGB: `${(freeMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
41
+ UsedMemoryGB: `${(usedMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
42
+ CPULoad: cpuLoad,
43
+ NetworkStats: networkStats,
44
+ };
45
+
46
+ return systemHealth as SystemHealth;
47
+ }
7
48
 
8
49
  export async function sleepFor(ms: number) {
9
50
  return new Promise((resolve) => setTimeout(resolve, ms));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -57,7 +57,14 @@
57
57
  "test:durable:loopactivity": "NODE_ENV=test jest ./tests/durable/loopactivity/index.test.ts --detectOpenHandles --forceExit --verbose",
58
58
  "test:durable:nested": "NODE_ENV=test jest ./tests/durable/nested/index.test.ts --detectOpenHandles --forceExit --verbose"
59
59
  },
60
- "keywords": ["durable workflow", "hotmesh", "service mesh", "workflows", "operational data", "redis"],
60
+ "keywords": [
61
+ "durable workflow",
62
+ "hotmesh",
63
+ "service mesh",
64
+ "workflows",
65
+ "operational data",
66
+ "redis"
67
+ ],
61
68
  "author": "luke.birdeau@gmail.com",
62
69
  "license": "SEE LICENSE IN LICENSE",
63
70
  "dependencies": {
@@ -66,6 +73,7 @@
66
73
  "js-yaml": "^4.1.0",
67
74
  "ms": "^2.1.3",
68
75
  "nanoid": "^3.3.6",
76
+ "systeminformation": "^5.22.2",
69
77
  "winston": "^3.8.2"
70
78
  },
71
79
  "devDependencies": {
@@ -125,7 +125,16 @@ export class MeshOSService {
125
125
  static async createIndex() {
126
126
  const my = new this();
127
127
  const hmClient = await MeshOSService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
128
- Search.configureSearchIndex(hmClient, my.search)
128
+ await Search.configureSearchIndex(hmClient, my.search);
129
+ }
130
+
131
+ /**
132
+ * Lists FT search indexes
133
+ */
134
+ static async listIndexes() {
135
+ const my = new this();
136
+ const hmClient = await MeshOSService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
137
+ return await Search.listSearchIndexes(hmClient)
129
138
  }
130
139
 
131
140
  /**
@@ -11,8 +11,18 @@ export class Search {
11
11
  hotMeshClient: HotMesh;
12
12
  store: StoreService<RedisClient, RedisMulti> | null;
13
13
 
14
+ constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string) {
15
+ const keyParams = {
16
+ appId: hotMeshClient.appId,
17
+ jobId: workflowId
18
+ }
19
+ this.jobId = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
20
+ this.searchSessionId = searchSessionId;
21
+ this.hotMeshClient = hotMeshClient;
22
+ this.store = hotMeshClient.engine.store as StoreService<RedisClient, RedisMulti>;
23
+ }
24
+
14
25
  safeKey(key:string): string {
15
- //note: protect the execution namespace with a prefix
16
26
  return `_${key}`;
17
27
  }
18
28
 
@@ -49,15 +59,16 @@ export class Search {
49
59
  }
50
60
  }
51
61
 
52
- constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string) {
53
- const keyParams = {
54
- appId: hotMeshClient.appId,
55
- jobId: workflowId
56
- }
57
- this.jobId = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
58
- this.searchSessionId = searchSessionId;
59
- this.hotMeshClient = hotMeshClient;
60
- this.store = hotMeshClient.engine.store as StoreService<RedisClient, RedisMulti>;
62
+ /**
63
+ * For those deployments with a redis stack backend (with the FT module),
64
+ * this method will list all search indexes.
65
+ * @param {HotMesh} hotMeshClient - the hotmesh client
66
+ * @returns {Promise<string[]>} - the list of search indexes
67
+ */
68
+ static async listSearchIndexes(hotMeshClient: HotMesh): Promise<string[]> {
69
+ const store = hotMeshClient.engine.store;
70
+ const searchIndexes = await store.exec('FT._LIST');
71
+ return searchIndexes as string[];
61
72
  }
62
73
 
63
74
  /**
@@ -93,7 +93,7 @@ export class WorkerService {
93
93
  const worker = new WorkerService();
94
94
  worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
95
95
  worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
96
- Search.configureSearchIndex(worker.workflowRunner, config.search)
96
+ Search.configureSearchIndex(worker.workflowRunner, config.search);
97
97
  await WorkerService.activateWorkflow(worker.workflowRunner);
98
98
  return worker;
99
99
  }
@@ -16,7 +16,7 @@ import {
16
16
  import {
17
17
  HotMeshConfig,
18
18
  HotMeshManifest } from '../../types/hotmesh';
19
- import { JobMessageCallback, QuorumProfile } from '../../types/quorum';
19
+ import { JobMessageCallback, QuorumProfile, ThrottleMessage, ThrottleOptions } from '../../types/quorum';
20
20
  import {
21
21
  JobStatsInput,
22
22
  GetStatsOptions,
@@ -137,10 +137,25 @@ class HotMeshService {
137
137
  return await this.engine.add(streamData) as string;
138
138
  }
139
139
 
140
- // ************* COMPILER METHODS *************
140
+
141
+ // ************* QUORUM METHODS *************
141
142
  async rollCall(delay?: number): Promise<QuorumProfile[]> {
142
143
  return await this.quorum?.rollCall(delay);
143
144
  }
145
+ async throttle(options: ThrottleOptions): Promise<boolean> {
146
+ const throttleMessage: ThrottleMessage = {
147
+ type: 'throttle',
148
+ throttle: options.throttle,
149
+ };
150
+ if (options.guid) {
151
+ throttleMessage.guid = options.guid;
152
+ } else if (options.topic) {
153
+ throttleMessage.topic = options.topic;
154
+ }
155
+ return await this.quorum?.pub(throttleMessage);
156
+ }
157
+
158
+ // ************* COMPILER METHODS *************
144
159
  async plan(path: string): Promise<HotMeshManifest> {
145
160
  return await this.engine?.plan(path);
146
161
  }
@@ -1,11 +1,17 @@
1
- import { HMSH_ACTIVATION_MAX_RETRY, HMSH_QUORUM_DELAY_MS } from '../../modules/enums';
2
- import { formatISODate, identifyRedisType, sleepFor } from '../../modules/utils';
1
+ import {
2
+ HMSH_ACTIVATION_MAX_RETRY,
3
+ HMSH_QUORUM_DELAY_MS } from '../../modules/enums';
4
+ import {
5
+ formatISODate,
6
+ getSystemHealth,
7
+ identifyRedisType,
8
+ sleepFor } from '../../modules/utils';
3
9
  import { CompilerService } from '../compiler';
4
10
  import { EngineService } from '../engine';
5
11
  import { ILogger } from '../logger';
6
12
  import { StoreService } from '../store';
7
- import { RedisStoreService as RedisStore } from '../store/clients/redis';
8
13
  import { IORedisStoreService as IORedisStore } from '../store/clients/ioredis';
14
+ import { RedisStoreService as RedisStore } from '../store/clients/redis';
9
15
  import { SubService } from '../sub';
10
16
  import { IORedisSubService as IORedisSub } from '../sub/clients/ioredis';
11
17
  import { RedisSubService as RedisSub } from '../sub/clients/redis';
@@ -152,6 +158,7 @@ class QuorumService {
152
158
  stream,
153
159
  counts: this.engine.router.counts,
154
160
  timestamp: formatISODate(new Date()),
161
+ system: await getSystemHealth(),
155
162
  };
156
163
  }
157
164
  this.store.publish(
@@ -1,5 +1,5 @@
1
1
  import { KeyType } from "../../modules/key";
2
- import { formatISODate, identifyRedisType } from "../../modules/utils";
2
+ import { formatISODate, getSystemHealth, identifyRedisType } from "../../modules/utils";
3
3
  import { ConnectorService } from "../connector";
4
4
  import { ILogger } from "../logger";
5
5
  import { Router } from "../router";
@@ -176,6 +176,7 @@ class WorkerService {
176
176
  stream: this.stream.mintKey(KeyType.STREAMS, params),
177
177
  counts: this.router.counts,
178
178
  timestamp: formatISODate(new Date()),
179
+ system: await getSystemHealth(),
179
180
  };
180
181
  }
181
182
  this.store.publish(
package/types/index.ts CHANGED
@@ -115,7 +115,9 @@ export {
115
115
  QuorumMessageCallback,
116
116
  QuorumProfile,
117
117
  SubscriptionCallback,
118
+ SystemHealth,
118
119
  ThrottleMessage,
120
+ ThrottleOptions,
119
121
  WorkMessage } from './quorum';
120
122
  export {
121
123
  MultiResponseFlags,
package/types/quorum.ts CHANGED
@@ -1,6 +1,40 @@
1
1
  import { JobOutput } from "./job";
2
2
 
3
- //used for coordination (like version activation)
3
+ interface CPULoad {
4
+ [cpu: string]: string;
5
+ }
6
+
7
+ interface NetworkStat {
8
+ iface: string;
9
+ operstate: string;
10
+ rx_bytes: number;
11
+ rx_dropped: number;
12
+ rx_errors: number;
13
+ tx_bytes: number;
14
+ tx_dropped: number;
15
+ tx_errors: number;
16
+ rx_sec: number;
17
+ tx_sec: number;
18
+ ms: number;
19
+ }
20
+
21
+ /** reveals: memory, cpu, network */
22
+ export interface SystemHealth {
23
+ TotalMemoryGB: string;
24
+ FreeMemoryGB: string;
25
+ UsedMemoryGB: string;
26
+ CPULoad: CPULoad[];
27
+ NetworkStats: NetworkStat[];
28
+ }
29
+
30
+ export type ThrottleOptions = {
31
+ /** target an engine OR worker by GUID */
32
+ guid?: string;
33
+ /** target a worker quorum */
34
+ topic?: string;
35
+ /** in milliseconds; default is 0 */
36
+ throttle: number;
37
+ };
4
38
 
5
39
  export interface QuorumProfile {
6
40
  namespace: string;
@@ -11,8 +45,10 @@ export interface QuorumProfile {
11
45
  stream_depth?: number;
12
46
  counts?: Record<string, number>;
13
47
  timestamp?: string;
48
+ system?: SystemHealth;
14
49
  }
15
50
 
51
+ //used for coordination (like version activation)
16
52
  export interface PingMessage {
17
53
  type: 'ping';
18
54
  originator: string; //guid