@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.
- package/build/modules/utils.d.ts +2 -0
- package/build/modules/utils.js +40 -1
- package/build/package.json +10 -2
- package/build/services/durable/meshos.d.ts +4 -0
- package/build/services/durable/meshos.js +9 -1
- package/build/services/durable/search.d.ts +8 -1
- package/build/services/durable/search.js +21 -11
- package/build/services/hotmesh/index.d.ts +2 -1
- package/build/services/hotmesh/index.js +15 -1
- package/build/services/quorum/index.js +2 -1
- package/build/services/worker/index.js +1 -0
- package/build/types/index.d.ts +1 -1
- package/build/types/quorum.d.ts +34 -0
- package/modules/utils.ts +41 -0
- package/package.json +10 -2
- package/services/durable/meshos.ts +10 -1
- package/services/durable/search.ts +21 -10
- package/services/durable/worker.ts +1 -1
- package/services/hotmesh/index.ts +17 -2
- package/services/quorum/index.ts +10 -3
- package/services/worker/index.ts +2 -1
- package/types/index.ts +2 -0
- package/types/quorum.ts +37 -1
package/build/modules/utils.d.ts
CHANGED
|
@@ -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;
|
package/build/modules/utils.js
CHANGED
|
@@ -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
|
}
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.0.
|
|
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": [
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
// *************
|
|
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, {
|
package/build/types/index.d.ts
CHANGED
|
@@ -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';
|
package/build/types/quorum.d.ts
CHANGED
|
@@ -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.
|
|
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": [
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
}
|
package/services/quorum/index.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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(
|
package/services/worker/index.ts
CHANGED
|
@@ -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
package/types/quorum.ts
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import { JobOutput } from "./job";
|
|
2
2
|
|
|
3
|
-
|
|
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
|