@hotmeshio/hotmesh 0.0.1
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/LICENSE +214 -0
- package/README.md +241 -0
- package/build/index.d.ts +4 -0
- package/build/index.js +7 -0
- package/build/modules/errors.d.ts +28 -0
- package/build/modules/errors.js +50 -0
- package/build/modules/key.d.ts +75 -0
- package/build/modules/key.js +116 -0
- package/build/modules/utils.d.ts +34 -0
- package/build/modules/utils.js +173 -0
- package/build/package.json +73 -0
- package/build/services/activities/activity.d.ts +59 -0
- package/build/services/activities/activity.js +396 -0
- package/build/services/activities/await.d.ts +16 -0
- package/build/services/activities/await.js +143 -0
- package/build/services/activities/emit.d.ts +9 -0
- package/build/services/activities/emit.js +13 -0
- package/build/services/activities/index.d.ts +15 -0
- package/build/services/activities/index.js +16 -0
- package/build/services/activities/iterate.d.ts +9 -0
- package/build/services/activities/iterate.js +13 -0
- package/build/services/activities/trigger.d.ts +22 -0
- package/build/services/activities/trigger.js +161 -0
- package/build/services/activities/worker.d.ts +17 -0
- package/build/services/activities/worker.js +164 -0
- package/build/services/collator/index.d.ts +54 -0
- package/build/services/collator/index.js +171 -0
- package/build/services/compiler/deployer.d.ts +35 -0
- package/build/services/compiler/deployer.js +412 -0
- package/build/services/compiler/index.d.ts +30 -0
- package/build/services/compiler/index.js +111 -0
- package/build/services/compiler/validator.d.ts +32 -0
- package/build/services/compiler/validator.js +134 -0
- package/build/services/connector/clients/ioredis.d.ts +13 -0
- package/build/services/connector/clients/ioredis.js +50 -0
- package/build/services/connector/clients/redis.d.ts +13 -0
- package/build/services/connector/clients/redis.js +62 -0
- package/build/services/connector/index.d.ts +5 -0
- package/build/services/connector/index.js +31 -0
- package/build/services/dimension/index.d.ts +29 -0
- package/build/services/dimension/index.js +35 -0
- package/build/services/durable/asyncLocalStorage.d.ts +3 -0
- package/build/services/durable/asyncLocalStorage.js +5 -0
- package/build/services/durable/client.d.ts +15 -0
- package/build/services/durable/client.js +108 -0
- package/build/services/durable/connection.d.ts +4 -0
- package/build/services/durable/connection.js +51 -0
- package/build/services/durable/factory.d.ts +3 -0
- package/build/services/durable/factory.js +123 -0
- package/build/services/durable/handle.d.ts +8 -0
- package/build/services/durable/handle.js +38 -0
- package/build/services/durable/index.d.ts +57 -0
- package/build/services/durable/index.js +58 -0
- package/build/services/durable/native.d.ts +4 -0
- package/build/services/durable/native.js +47 -0
- package/build/services/durable/worker.d.ts +36 -0
- package/build/services/durable/worker.js +266 -0
- package/build/services/durable/workflow.d.ts +6 -0
- package/build/services/durable/workflow.js +135 -0
- package/build/services/engine/index.d.ts +82 -0
- package/build/services/engine/index.js +511 -0
- package/build/services/hotmesh/index.d.ts +45 -0
- package/build/services/hotmesh/index.js +134 -0
- package/build/services/logger/index.d.ts +17 -0
- package/build/services/logger/index.js +73 -0
- package/build/services/mapper/index.d.ts +24 -0
- package/build/services/mapper/index.js +72 -0
- package/build/services/pipe/functions/array.d.ts +24 -0
- package/build/services/pipe/functions/array.js +69 -0
- package/build/services/pipe/functions/bitwise.d.ts +9 -0
- package/build/services/pipe/functions/bitwise.js +24 -0
- package/build/services/pipe/functions/conditional.d.ts +10 -0
- package/build/services/pipe/functions/conditional.js +27 -0
- package/build/services/pipe/functions/date.d.ts +57 -0
- package/build/services/pipe/functions/date.js +167 -0
- package/build/services/pipe/functions/index.d.ts +25 -0
- package/build/services/pipe/functions/index.js +26 -0
- package/build/services/pipe/functions/json.d.ts +5 -0
- package/build/services/pipe/functions/json.js +12 -0
- package/build/services/pipe/functions/math.d.ts +38 -0
- package/build/services/pipe/functions/math.js +111 -0
- package/build/services/pipe/functions/number.d.ts +25 -0
- package/build/services/pipe/functions/number.js +133 -0
- package/build/services/pipe/functions/object.d.ts +22 -0
- package/build/services/pipe/functions/object.js +63 -0
- package/build/services/pipe/functions/string.d.ts +23 -0
- package/build/services/pipe/functions/string.js +69 -0
- package/build/services/pipe/functions/symbol.d.ts +12 -0
- package/build/services/pipe/functions/symbol.js +33 -0
- package/build/services/pipe/functions/unary.d.ts +7 -0
- package/build/services/pipe/functions/unary.js +18 -0
- package/build/services/pipe/index.d.ts +30 -0
- package/build/services/pipe/index.js +128 -0
- package/build/services/quorum/index.d.ts +34 -0
- package/build/services/quorum/index.js +147 -0
- package/build/services/reporter/index.d.ts +47 -0
- package/build/services/reporter/index.js +330 -0
- package/build/services/serializer/index.d.ts +36 -0
- package/build/services/serializer/index.js +222 -0
- package/build/services/signaler/store.d.ts +15 -0
- package/build/services/signaler/store.js +53 -0
- package/build/services/signaler/stream.d.ts +43 -0
- package/build/services/signaler/stream.js +317 -0
- package/build/services/store/cache.d.ts +66 -0
- package/build/services/store/cache.js +127 -0
- package/build/services/store/clients/ioredis.d.ts +27 -0
- package/build/services/store/clients/ioredis.js +96 -0
- package/build/services/store/clients/redis.d.ts +29 -0
- package/build/services/store/clients/redis.js +143 -0
- package/build/services/store/index.d.ts +88 -0
- package/build/services/store/index.js +657 -0
- package/build/services/stream/clients/ioredis.d.ts +23 -0
- package/build/services/stream/clients/ioredis.js +115 -0
- package/build/services/stream/clients/redis.d.ts +23 -0
- package/build/services/stream/clients/redis.js +119 -0
- package/build/services/stream/index.d.ts +21 -0
- package/build/services/stream/index.js +9 -0
- package/build/services/sub/clients/ioredis.d.ts +20 -0
- package/build/services/sub/clients/ioredis.js +72 -0
- package/build/services/sub/clients/redis.d.ts +20 -0
- package/build/services/sub/clients/redis.js +63 -0
- package/build/services/sub/index.d.ts +18 -0
- package/build/services/sub/index.js +9 -0
- package/build/services/task/index.d.ts +18 -0
- package/build/services/task/index.js +73 -0
- package/build/services/telemetry/index.d.ts +49 -0
- package/build/services/telemetry/index.js +223 -0
- package/build/services/worker/index.d.ts +30 -0
- package/build/services/worker/index.js +105 -0
- package/build/types/activity.d.ts +86 -0
- package/build/types/activity.js +2 -0
- package/build/types/app.d.ts +16 -0
- package/build/types/app.js +2 -0
- package/build/types/async.d.ts +5 -0
- package/build/types/async.js +2 -0
- package/build/types/cache.d.ts +1 -0
- package/build/types/cache.js +2 -0
- package/build/types/collator.d.ts +8 -0
- package/build/types/collator.js +11 -0
- package/build/types/durable.d.ts +59 -0
- package/build/types/durable.js +2 -0
- package/build/types/hook.d.ts +31 -0
- package/build/types/hook.js +9 -0
- package/build/types/hotmesh.d.ts +82 -0
- package/build/types/hotmesh.js +2 -0
- package/build/types/index.d.ts +20 -0
- package/build/types/index.js +21 -0
- package/build/types/ioredisclient.d.ts +5 -0
- package/build/types/ioredisclient.js +5 -0
- package/build/types/job.d.ts +50 -0
- package/build/types/job.js +2 -0
- package/build/types/logger.d.ts +6 -0
- package/build/types/logger.js +2 -0
- package/build/types/map.d.ts +4 -0
- package/build/types/map.js +2 -0
- package/build/types/pipe.d.ts +4 -0
- package/build/types/pipe.js +2 -0
- package/build/types/quorum.d.ts +46 -0
- package/build/types/quorum.js +2 -0
- package/build/types/redis.d.ts +8 -0
- package/build/types/redis.js +2 -0
- package/build/types/redisclient.d.ts +25 -0
- package/build/types/redisclient.js +2 -0
- package/build/types/serializer.d.ts +33 -0
- package/build/types/serializer.js +2 -0
- package/build/types/stats.d.ts +83 -0
- package/build/types/stats.js +2 -0
- package/build/types/stream.d.ts +67 -0
- package/build/types/stream.js +25 -0
- package/build/types/telemetry.d.ts +1 -0
- package/build/types/telemetry.js +11 -0
- package/build/types/transition.d.ts +17 -0
- package/build/types/transition.js +2 -0
- package/index.ts +5 -0
- package/modules/errors.ts +55 -0
- package/modules/key.ts +129 -0
- package/modules/utils.ts +170 -0
- package/package.json +73 -0
- package/services/activities/activity.ts +473 -0
- package/services/activities/await.ts +172 -0
- package/services/activities/emit.ts +25 -0
- package/services/activities/index.ts +15 -0
- package/services/activities/iterate.ts +26 -0
- package/services/activities/trigger.ts +196 -0
- package/services/activities/worker.ts +190 -0
- package/services/collator/README.md +102 -0
- package/services/collator/index.ts +182 -0
- package/services/compiler/deployer.ts +432 -0
- package/services/compiler/index.ts +98 -0
- package/services/compiler/validator.ts +154 -0
- package/services/connector/clients/ioredis.ts +57 -0
- package/services/connector/clients/redis.ts +72 -0
- package/services/connector/index.ts +44 -0
- package/services/dimension/README.md +73 -0
- package/services/dimension/index.ts +39 -0
- package/services/durable/asyncLocalStorage.ts +3 -0
- package/services/durable/client.ts +116 -0
- package/services/durable/connection.ts +50 -0
- package/services/durable/factory.ts +124 -0
- package/services/durable/handle.ts +43 -0
- package/services/durable/index.ts +60 -0
- package/services/durable/native.ts +46 -0
- package/services/durable/worker.ts +254 -0
- package/services/durable/workflow.ts +136 -0
- package/services/engine/index.ts +615 -0
- package/services/hotmesh/index.ts +182 -0
- package/services/logger/index.ts +79 -0
- package/services/mapper/index.ts +84 -0
- package/services/pipe/functions/array.ts +87 -0
- package/services/pipe/functions/bitwise.ts +27 -0
- package/services/pipe/functions/conditional.ts +31 -0
- package/services/pipe/functions/date.ts +214 -0
- package/services/pipe/functions/index.ts +25 -0
- package/services/pipe/functions/json.ts +11 -0
- package/services/pipe/functions/math.ts +143 -0
- package/services/pipe/functions/number.ts +150 -0
- package/services/pipe/functions/object.ts +79 -0
- package/services/pipe/functions/string.ts +86 -0
- package/services/pipe/functions/symbol.ts +39 -0
- package/services/pipe/functions/unary.ts +19 -0
- package/services/pipe/index.ts +138 -0
- package/services/quorum/index.ts +200 -0
- package/services/reporter/index.ts +379 -0
- package/services/serializer/README.md +10 -0
- package/services/serializer/index.ts +243 -0
- package/services/signaler/store.ts +61 -0
- package/services/signaler/stream.ts +354 -0
- package/services/store/cache.ts +172 -0
- package/services/store/clients/ioredis.ts +123 -0
- package/services/store/clients/redis.ts +169 -0
- package/services/store/index.ts +757 -0
- package/services/stream/clients/ioredis.ts +148 -0
- package/services/stream/clients/redis.ts +144 -0
- package/services/stream/index.ts +57 -0
- package/services/sub/clients/ioredis.ts +83 -0
- package/services/sub/clients/redis.ts +74 -0
- package/services/sub/index.ts +25 -0
- package/services/task/index.ts +86 -0
- package/services/telemetry/index.ts +267 -0
- package/services/worker/index.ts +165 -0
- package/types/activity.ts +115 -0
- package/types/app.ts +20 -0
- package/types/async.ts +7 -0
- package/types/cache.ts +1 -0
- package/types/collator.ts +9 -0
- package/types/durable.ts +81 -0
- package/types/hook.ts +32 -0
- package/types/hotmesh.ts +102 -0
- package/types/index.ts +138 -0
- package/types/ioredisclient.ts +10 -0
- package/types/job.ts +59 -0
- package/types/logger.ts +6 -0
- package/types/map.ts +5 -0
- package/types/ms.d.ts +7 -0
- package/types/pipe.ts +7 -0
- package/types/quorum.ts +59 -0
- package/types/redis.ts +27 -0
- package/types/redisclient.ts +29 -0
- package/types/serializer.ts +38 -0
- package/types/stats.ts +100 -0
- package/types/stream.ts +75 -0
- package/types/telemetry.ts +15 -0
- package/types/transition.ts +20 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Pipe = void 0;
|
|
7
|
+
const functions_1 = __importDefault(require("./functions"));
|
|
8
|
+
class Pipe {
|
|
9
|
+
constructor(rules, jobData) {
|
|
10
|
+
this.rules = rules;
|
|
11
|
+
this.jobData = jobData;
|
|
12
|
+
}
|
|
13
|
+
isPipeType(currentRow) {
|
|
14
|
+
return !Array.isArray(currentRow) && '@pipe' in currentRow;
|
|
15
|
+
}
|
|
16
|
+
static isPipeObject(obj) {
|
|
17
|
+
return typeof obj === 'object' && obj !== null && !Array.isArray(obj) && '@pipe' in obj;
|
|
18
|
+
}
|
|
19
|
+
static resolve(unresolved, context) {
|
|
20
|
+
let pipe;
|
|
21
|
+
if (Pipe.isPipeObject(unresolved)) {
|
|
22
|
+
pipe = new Pipe(unresolved['@pipe'], context);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
pipe = new Pipe([[unresolved]], context);
|
|
26
|
+
}
|
|
27
|
+
return pipe.process();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* loop through each PipeItem row in this Pipe, resolving and transforming line by line
|
|
31
|
+
* @returns {any} the result of the pipe
|
|
32
|
+
*/
|
|
33
|
+
process() {
|
|
34
|
+
let resolved = this.processCells(this.rules[0]);
|
|
35
|
+
const len = this.rules.length;
|
|
36
|
+
for (let i = 1; i < len; i++) {
|
|
37
|
+
resolved = this.processRow(this.rules[i], resolved, []);
|
|
38
|
+
}
|
|
39
|
+
return resolved[0];
|
|
40
|
+
}
|
|
41
|
+
processRow(currentRow, resolvedPriorRow, subPipeQueue) {
|
|
42
|
+
if (this.isPipeType(currentRow)) {
|
|
43
|
+
//currentRow is a recursive subPipe
|
|
44
|
+
const subPipe = new Pipe(currentRow['@pipe'], this.jobData);
|
|
45
|
+
subPipeQueue.push(subPipe.process());
|
|
46
|
+
//return prior row as if nothing happened
|
|
47
|
+
return resolvedPriorRow;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (subPipeQueue.length > 0) {
|
|
51
|
+
//if items in subPipeQueue, flush and use as resolvedPriorRow
|
|
52
|
+
resolvedPriorRow = [...subPipeQueue];
|
|
53
|
+
subPipeQueue.length = 0;
|
|
54
|
+
}
|
|
55
|
+
else if (!resolvedPriorRow) {
|
|
56
|
+
//if no prior row, use current row as prior row
|
|
57
|
+
return [].concat(this.processCells([...currentRow]));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const [functionName, ...params] = currentRow;
|
|
61
|
+
//use resolved values from prior row (n - 1) as input params to cell 1 function
|
|
62
|
+
const resolvedValue = Pipe.resolveFunction(functionName)(...resolvedPriorRow);
|
|
63
|
+
//resolve remaining cells in row and return concatenated with resolvedValue
|
|
64
|
+
return [resolvedValue].concat(this.processCells([...params]));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
static resolveFunction(functionName) {
|
|
69
|
+
let [prefix, suffix] = functionName.split('.');
|
|
70
|
+
prefix = prefix.substring(2);
|
|
71
|
+
suffix = suffix.substring(0, suffix.length - 1);
|
|
72
|
+
const domain = functions_1.default[prefix];
|
|
73
|
+
if (!domain) {
|
|
74
|
+
throw new Error(`Unknown domain name [${functionName}]: ${prefix}`);
|
|
75
|
+
}
|
|
76
|
+
if (!domain[suffix]) {
|
|
77
|
+
throw new Error(`Unknown domain function [${functionName}]: ${prefix}.${suffix}`);
|
|
78
|
+
}
|
|
79
|
+
return domain[suffix];
|
|
80
|
+
}
|
|
81
|
+
processCells(cells) {
|
|
82
|
+
const resolved = [];
|
|
83
|
+
for (const currentCell of cells) {
|
|
84
|
+
resolved.push(this.resolveCellValue(currentCell));
|
|
85
|
+
}
|
|
86
|
+
return resolved;
|
|
87
|
+
}
|
|
88
|
+
isFunction(currentCell) {
|
|
89
|
+
return typeof currentCell === 'string' && currentCell.startsWith('{@') && currentCell.endsWith('}');
|
|
90
|
+
}
|
|
91
|
+
isMappable(currentCell) {
|
|
92
|
+
return typeof currentCell === 'string' && currentCell.startsWith('{') && currentCell.endsWith('}');
|
|
93
|
+
}
|
|
94
|
+
resolveCellValue(currentCell) {
|
|
95
|
+
if (this.isFunction(currentCell)) {
|
|
96
|
+
const fn = Pipe.resolveFunction(currentCell);
|
|
97
|
+
return fn.call();
|
|
98
|
+
}
|
|
99
|
+
else if (this.isMappable(currentCell)) {
|
|
100
|
+
return this.resolveMappableValue(currentCell);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
return currentCell;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
getNestedProperty(obj, path) {
|
|
107
|
+
const pathParts = path.split('.');
|
|
108
|
+
let current = obj;
|
|
109
|
+
for (const part of pathParts) {
|
|
110
|
+
if (current === null || typeof current !== 'object' || !current.hasOwnProperty(part)) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
current = current[part];
|
|
114
|
+
}
|
|
115
|
+
return current;
|
|
116
|
+
}
|
|
117
|
+
resolveMappableValue(currentCell) {
|
|
118
|
+
const term = this.resolveMapTerm(currentCell);
|
|
119
|
+
return this.getNestedProperty(this.jobData, term);
|
|
120
|
+
}
|
|
121
|
+
resolveFunctionTerm(currentCell) {
|
|
122
|
+
return currentCell.substring(2, currentCell.length - 1);
|
|
123
|
+
}
|
|
124
|
+
resolveMapTerm(currentCell) {
|
|
125
|
+
return currentCell.substring(1, currentCell.length - 1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.Pipe = Pipe;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { EngineService } from '../engine';
|
|
2
|
+
import { ILogger } from '../logger';
|
|
3
|
+
import { StoreService } from '../store';
|
|
4
|
+
import { SubService } from '../sub';
|
|
5
|
+
import { CacheMode } from '../../types/cache';
|
|
6
|
+
import { QuorumMessageCallback, SubscriptionCallback, ThrottleMessage } from '../../types/quorum';
|
|
7
|
+
import { HotMeshApps, HotMeshConfig } from '../../types/hotmesh';
|
|
8
|
+
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
9
|
+
declare class QuorumService {
|
|
10
|
+
namespace: string;
|
|
11
|
+
apps: HotMeshApps | null;
|
|
12
|
+
appId: string;
|
|
13
|
+
guid: string;
|
|
14
|
+
engine: EngineService;
|
|
15
|
+
store: StoreService<RedisClient, RedisMulti> | null;
|
|
16
|
+
subscribe: SubService<RedisClient, RedisMulti> | null;
|
|
17
|
+
logger: ILogger;
|
|
18
|
+
cacheMode: CacheMode;
|
|
19
|
+
untilVersion: string | null;
|
|
20
|
+
quorum: number | null;
|
|
21
|
+
callbacks: QuorumMessageCallback[];
|
|
22
|
+
static init(namespace: string, appId: string, guid: string, config: HotMeshConfig, engine: EngineService, logger: ILogger): Promise<QuorumService>;
|
|
23
|
+
verifyQuorumFields(config: HotMeshConfig): void;
|
|
24
|
+
initStoreChannel(store: RedisClient): Promise<void>;
|
|
25
|
+
initSubChannel(sub: RedisClient): Promise<void>;
|
|
26
|
+
subscriptionHandler(): SubscriptionCallback;
|
|
27
|
+
sayPong(appId: string, guid: string, originator: string): Promise<void>;
|
|
28
|
+
requestQuorum(delay?: number): Promise<number>;
|
|
29
|
+
pub(quorumMessage: ThrottleMessage): Promise<boolean>;
|
|
30
|
+
sub(callback: QuorumMessageCallback): Promise<void>;
|
|
31
|
+
unsub(callback: QuorumMessageCallback): Promise<void>;
|
|
32
|
+
activate(version: string, delay?: number): Promise<boolean>;
|
|
33
|
+
}
|
|
34
|
+
export { QuorumService };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuorumService = void 0;
|
|
4
|
+
const key_1 = require("../../modules/key");
|
|
5
|
+
const utils_1 = require("../../modules/utils");
|
|
6
|
+
const compiler_1 = require("../compiler");
|
|
7
|
+
const redis_1 = require("../store/clients/redis");
|
|
8
|
+
const ioredis_1 = require("../store/clients/ioredis");
|
|
9
|
+
const ioredis_2 = require("../sub/clients/ioredis");
|
|
10
|
+
const redis_2 = require("../sub/clients/redis");
|
|
11
|
+
//wait time to see if quorum is reached
|
|
12
|
+
const QUORUM_DELAY = 250;
|
|
13
|
+
class QuorumService {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.cacheMode = 'cache';
|
|
16
|
+
this.untilVersion = null;
|
|
17
|
+
this.quorum = null;
|
|
18
|
+
this.callbacks = [];
|
|
19
|
+
}
|
|
20
|
+
static async init(namespace, appId, guid, config, engine, logger) {
|
|
21
|
+
if (config.engine) {
|
|
22
|
+
const instance = new QuorumService();
|
|
23
|
+
instance.verifyQuorumFields(config);
|
|
24
|
+
instance.namespace = namespace;
|
|
25
|
+
instance.appId = appId;
|
|
26
|
+
instance.guid = guid;
|
|
27
|
+
instance.logger = logger;
|
|
28
|
+
instance.engine = engine;
|
|
29
|
+
//note: `quorum` shares/re-uses the engine's `store`/`sub` Redis clients
|
|
30
|
+
await instance.initStoreChannel(config.engine.store);
|
|
31
|
+
await instance.initSubChannel(config.engine.sub);
|
|
32
|
+
await instance.subscribe.subscribe(key_1.KeyType.QUORUM, instance.subscriptionHandler(), appId); //general quorum subscription
|
|
33
|
+
await instance.subscribe.subscribe(key_1.KeyType.QUORUM, instance.subscriptionHandler(), appId, instance.guid); //app-specific quorum subscription (used for pubsub one-time request/response)
|
|
34
|
+
instance.engine.processWebHooks();
|
|
35
|
+
instance.engine.processTimeHooks();
|
|
36
|
+
return instance;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
verifyQuorumFields(config) {
|
|
40
|
+
if (!(0, utils_1.identifyRedisType)(config.engine.store) ||
|
|
41
|
+
!(0, utils_1.identifyRedisType)(config.engine.sub)) {
|
|
42
|
+
throw new Error('quorum config must include `store` and `sub` fields.');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async initStoreChannel(store) {
|
|
46
|
+
if ((0, utils_1.identifyRedisType)(store) === 'redis') {
|
|
47
|
+
this.store = new redis_1.RedisStoreService(store);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.store = new ioredis_1.IORedisStoreService(store);
|
|
51
|
+
}
|
|
52
|
+
await this.store.init(this.namespace, this.appId, this.logger);
|
|
53
|
+
}
|
|
54
|
+
async initSubChannel(sub) {
|
|
55
|
+
if ((0, utils_1.identifyRedisType)(sub) === 'redis') {
|
|
56
|
+
this.subscribe = new redis_2.RedisSubService(sub);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.subscribe = new ioredis_2.IORedisSubService(sub);
|
|
60
|
+
}
|
|
61
|
+
await this.subscribe.init(this.namespace, this.appId, this.guid, this.logger);
|
|
62
|
+
}
|
|
63
|
+
subscriptionHandler() {
|
|
64
|
+
const self = this;
|
|
65
|
+
return async (topic, message) => {
|
|
66
|
+
self.logger.debug('quorum-event-received', { topic, type: message.type });
|
|
67
|
+
if (message.type === 'activate') {
|
|
68
|
+
self.engine.setCacheMode(message.cache_mode, message.until_version);
|
|
69
|
+
}
|
|
70
|
+
else if (message.type === 'ping') {
|
|
71
|
+
this.sayPong(self.appId, self.guid, message.originator);
|
|
72
|
+
}
|
|
73
|
+
else if (message.type === 'pong' && self.guid === message.originator) {
|
|
74
|
+
self.quorum = self.quorum + 1;
|
|
75
|
+
}
|
|
76
|
+
else if (message.type === 'throttle') {
|
|
77
|
+
self.engine.throttle(message.throttle);
|
|
78
|
+
}
|
|
79
|
+
else if (message.type === 'work') {
|
|
80
|
+
self.engine.processWebHooks();
|
|
81
|
+
}
|
|
82
|
+
else if (message.type === 'job') {
|
|
83
|
+
self.engine.routeToSubscribers(message.topic, message.job);
|
|
84
|
+
}
|
|
85
|
+
//if there are any callbacks, call them
|
|
86
|
+
if (self.callbacks.length > 0) {
|
|
87
|
+
self.callbacks.forEach(cb => cb(topic, message));
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async sayPong(appId, guid, originator) {
|
|
92
|
+
this.store.publish(key_1.KeyType.QUORUM, { type: 'pong', guid, originator }, appId);
|
|
93
|
+
}
|
|
94
|
+
async requestQuorum(delay = QUORUM_DELAY) {
|
|
95
|
+
const quorum = this.quorum;
|
|
96
|
+
this.quorum = 0;
|
|
97
|
+
await this.store.publish(key_1.KeyType.QUORUM, { type: 'ping', originator: this.guid }, this.appId);
|
|
98
|
+
await (0, utils_1.sleepFor)(delay);
|
|
99
|
+
return quorum;
|
|
100
|
+
}
|
|
101
|
+
// ************* PUB/SUB METHODS *************
|
|
102
|
+
//publish a message to the quorum
|
|
103
|
+
async pub(quorumMessage) {
|
|
104
|
+
return await this.store.publish(key_1.KeyType.QUORUM, quorumMessage, this.appId, quorumMessage.topic || quorumMessage.guid);
|
|
105
|
+
}
|
|
106
|
+
//subscribe user to quorum messages
|
|
107
|
+
async sub(callback) {
|
|
108
|
+
//the quorum is always subscribed to the `quorum` topic; just register the fn
|
|
109
|
+
this.callbacks.push(callback);
|
|
110
|
+
}
|
|
111
|
+
//unsubscribe user from quorum messages
|
|
112
|
+
async unsub(callback) {
|
|
113
|
+
//the quorum is always subscribed to the `quorum` topic; just unregister the fn
|
|
114
|
+
this.callbacks = this.callbacks.filter(cb => cb !== callback);
|
|
115
|
+
}
|
|
116
|
+
// ************* COMPILER METHODS *************
|
|
117
|
+
async activate(version, delay = QUORUM_DELAY) {
|
|
118
|
+
version = version.toString();
|
|
119
|
+
const config = await this.engine.getVID();
|
|
120
|
+
//request a quorum to activate the version
|
|
121
|
+
await this.requestQuorum(delay);
|
|
122
|
+
const q1 = await this.requestQuorum(delay);
|
|
123
|
+
const q2 = await this.requestQuorum(delay);
|
|
124
|
+
const q3 = await this.requestQuorum(delay);
|
|
125
|
+
if (q1 && q1 === q2 && q2 === q3) {
|
|
126
|
+
this.logger.info('quorum-rollcall-succeeded', { q1, q2, q3 });
|
|
127
|
+
this.store.publish(key_1.KeyType.QUORUM, { type: 'activate', cache_mode: 'nocache', until_version: version }, this.appId);
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
129
|
+
//confirm we received the activation message
|
|
130
|
+
if (this.engine.untilVersion === version) {
|
|
131
|
+
this.logger.info('quorum-activation-succeeded', { version });
|
|
132
|
+
const { id } = config;
|
|
133
|
+
const compiler = new compiler_1.CompilerService(this.store, this.logger);
|
|
134
|
+
return await compiler.activate(id, version);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
this.logger.error('quorum-activation-error', { version });
|
|
138
|
+
throw new Error(`UntilVersion Not Received. Version ${version} not activated`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
this.logger.info('quorum-rollcall-error', { q1, q2, q3 });
|
|
143
|
+
throw new Error(`Quorum not reached. Version ${version} not activated.`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.QuorumService = QuorumService;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ILogger } from '../logger';
|
|
2
|
+
import { StoreService } from '../store';
|
|
3
|
+
import { TriggerActivity } from '../../types/activity';
|
|
4
|
+
import { AppVID } from '../../types/app';
|
|
5
|
+
import { JobState } from '../../types/job';
|
|
6
|
+
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
7
|
+
import { GetStatsOptions, StatsResponse, IdsResponse, StatsType, StatType } from '../../types/stats';
|
|
8
|
+
declare class ReporterService {
|
|
9
|
+
private appVersion;
|
|
10
|
+
private logger;
|
|
11
|
+
private store;
|
|
12
|
+
static DEFAULT_GRANULARITY: string;
|
|
13
|
+
constructor(appVersion: AppVID, store: StoreService<RedisClient, RedisMulti>, logger: ILogger);
|
|
14
|
+
getStats(options: GetStatsOptions): Promise<StatsResponse>;
|
|
15
|
+
private validateOptions;
|
|
16
|
+
private generateDateTimeSets;
|
|
17
|
+
private convertRangeToMinutes;
|
|
18
|
+
private buildRedisKey;
|
|
19
|
+
private aggregateData;
|
|
20
|
+
private buildStatsResponse;
|
|
21
|
+
private handleSegments;
|
|
22
|
+
private isoTimestampFromKeyTimestamp;
|
|
23
|
+
getIds(options: GetStatsOptions, facets: string[], idRange?: [number, number]): Promise<IdsResponse>;
|
|
24
|
+
private buildIdsResponse;
|
|
25
|
+
private buildTimeSegments;
|
|
26
|
+
getUniqueFacets(data: StatsResponse): string[];
|
|
27
|
+
getTargetForKey(key: string): string;
|
|
28
|
+
getTargetForTime(key: string): string;
|
|
29
|
+
getWorkItems(options: GetStatsOptions, facets: string[]): Promise<string[]>;
|
|
30
|
+
private buildWorkerLists;
|
|
31
|
+
/**
|
|
32
|
+
* called by `trigger` activity to generate the stats that should
|
|
33
|
+
* be saved to the database. doesn't actually save the stats, but
|
|
34
|
+
* just generates the info that should be saved
|
|
35
|
+
*/
|
|
36
|
+
resolveTriggerStatistics({ stats: statsConfig }: TriggerActivity, context: JobState): StatsType;
|
|
37
|
+
isGeneralMetric(metric: string): boolean;
|
|
38
|
+
isMedianMetric(metric: string): boolean;
|
|
39
|
+
isIndexMetric(metric: string): boolean;
|
|
40
|
+
resolveMetric({ metric, target }: {
|
|
41
|
+
metric: any;
|
|
42
|
+
target: any;
|
|
43
|
+
}, context: JobState): StatType;
|
|
44
|
+
isCardinalMetric(metric: string): boolean;
|
|
45
|
+
resolveTarget(metric: string, target: string, resolvedValue: string): string;
|
|
46
|
+
}
|
|
47
|
+
export { ReporterService };
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReporterService = void 0;
|
|
4
|
+
const pipe_1 = require("../pipe");
|
|
5
|
+
class ReporterService {
|
|
6
|
+
constructor(appVersion, store, logger) {
|
|
7
|
+
this.appVersion = appVersion;
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
this.store = store;
|
|
10
|
+
}
|
|
11
|
+
async getStats(options) {
|
|
12
|
+
this.logger.debug('reporter-getstats-started', options);
|
|
13
|
+
const { key, granularity, range, end, start } = options;
|
|
14
|
+
this.validateOptions(options);
|
|
15
|
+
const dateTimeSets = this.generateDateTimeSets(granularity, range, end, start);
|
|
16
|
+
const redisKeys = dateTimeSets.map((dateTime) => this.buildRedisKey(key, dateTime));
|
|
17
|
+
const rawData = await this.store.getJobStats(redisKeys);
|
|
18
|
+
const [count, aggregatedData] = this.aggregateData(rawData);
|
|
19
|
+
const statsResponse = this.buildStatsResponse(rawData, redisKeys, aggregatedData, count, options);
|
|
20
|
+
return statsResponse;
|
|
21
|
+
}
|
|
22
|
+
validateOptions(options) {
|
|
23
|
+
const { start, end, range } = options;
|
|
24
|
+
if (start && end && range || !start && !end && !range) {
|
|
25
|
+
throw new Error('Invalid combination of start, end, and range values. Provide either start+end, end+range, or start+range.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
generateDateTimeSets(granularity, range, end, start) {
|
|
29
|
+
if (!range) {
|
|
30
|
+
//pluck just a single value when no range provided
|
|
31
|
+
range = '0m';
|
|
32
|
+
}
|
|
33
|
+
const granularitiesInMinutes = {
|
|
34
|
+
'5m': 5,
|
|
35
|
+
'10m': 10,
|
|
36
|
+
'15m': 15,
|
|
37
|
+
'30m': 30,
|
|
38
|
+
'1h': 60,
|
|
39
|
+
};
|
|
40
|
+
const granularityMinutes = granularitiesInMinutes[granularity];
|
|
41
|
+
if (!granularityMinutes) {
|
|
42
|
+
throw new Error('Invalid granularity value.');
|
|
43
|
+
}
|
|
44
|
+
const rangeMinutes = this.convertRangeToMinutes(range);
|
|
45
|
+
if (rangeMinutes === null) {
|
|
46
|
+
throw new Error('Invalid range value.');
|
|
47
|
+
}
|
|
48
|
+
// If start is provided, use it. Otherwise, calculate it from the end time and range.
|
|
49
|
+
let startTime;
|
|
50
|
+
let endTime;
|
|
51
|
+
if (start) {
|
|
52
|
+
startTime = new Date(start);
|
|
53
|
+
endTime = new Date(startTime.getTime() + rangeMinutes * 60 * 1000);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
endTime = end === 'NOW' ? new Date() : new Date(end);
|
|
57
|
+
startTime = new Date(endTime.getTime() - rangeMinutes * 60 * 1000);
|
|
58
|
+
}
|
|
59
|
+
// Round the start time to the nearest granularity unit
|
|
60
|
+
startTime.setUTCMinutes(Math.floor(startTime.getUTCMinutes() / granularityMinutes) * granularityMinutes);
|
|
61
|
+
const dateTimeSets = [];
|
|
62
|
+
for (let time = startTime; time <= endTime; time.setUTCMinutes(time.getUTCMinutes() + granularityMinutes)) {
|
|
63
|
+
const formattedTime = [
|
|
64
|
+
time.getUTCFullYear(),
|
|
65
|
+
String(time.getUTCMonth() + 1).padStart(2, '0'),
|
|
66
|
+
String(time.getUTCDate()).padStart(2, '0'),
|
|
67
|
+
String(time.getUTCHours()).padStart(2, '0'),
|
|
68
|
+
String(time.getUTCMinutes()).padStart(2, '0'),
|
|
69
|
+
].join('');
|
|
70
|
+
dateTimeSets.push(formattedTime);
|
|
71
|
+
}
|
|
72
|
+
return dateTimeSets;
|
|
73
|
+
}
|
|
74
|
+
convertRangeToMinutes(range) {
|
|
75
|
+
const timeUnit = range.slice(-1);
|
|
76
|
+
const value = parseInt(range.slice(0, -1), 10);
|
|
77
|
+
if (isNaN(value)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
switch (timeUnit) {
|
|
81
|
+
case 'm':
|
|
82
|
+
return value;
|
|
83
|
+
case 'h':
|
|
84
|
+
return value * 60;
|
|
85
|
+
case 'd':
|
|
86
|
+
return value * 60 * 24;
|
|
87
|
+
default:
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
buildRedisKey(key, dateTime, subTarget = '') {
|
|
92
|
+
return `hmsh:${this.appVersion.id}:s:${key}:${dateTime}${subTarget ? ':' + subTarget : ''}`;
|
|
93
|
+
}
|
|
94
|
+
aggregateData(rawData) {
|
|
95
|
+
const aggregatedData = {};
|
|
96
|
+
let count = 0;
|
|
97
|
+
Object.entries(rawData).forEach(([_, data]) => {
|
|
98
|
+
for (const key in data) {
|
|
99
|
+
if (key.startsWith('count:')) {
|
|
100
|
+
const target = key.slice('count:'.length);
|
|
101
|
+
if (!aggregatedData[target]) {
|
|
102
|
+
aggregatedData[target] = 0;
|
|
103
|
+
}
|
|
104
|
+
aggregatedData[target] += data[key];
|
|
105
|
+
}
|
|
106
|
+
else if (key === 'count') {
|
|
107
|
+
count += data[key];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return [count, aggregatedData];
|
|
112
|
+
}
|
|
113
|
+
buildStatsResponse(rawData, redisKeys, aggregatedData, count, options) {
|
|
114
|
+
const measures = [];
|
|
115
|
+
const measureKeys = Object.keys(aggregatedData).filter((key) => key !== "count");
|
|
116
|
+
let segments = undefined;
|
|
117
|
+
if (options.sparse !== true) {
|
|
118
|
+
segments = this.handleSegments(rawData, redisKeys);
|
|
119
|
+
}
|
|
120
|
+
measureKeys.forEach((key) => {
|
|
121
|
+
const measure = {
|
|
122
|
+
target: key,
|
|
123
|
+
type: "count",
|
|
124
|
+
value: aggregatedData[key],
|
|
125
|
+
};
|
|
126
|
+
measures.push(measure);
|
|
127
|
+
});
|
|
128
|
+
const response = {
|
|
129
|
+
key: options.key,
|
|
130
|
+
granularity: options.granularity,
|
|
131
|
+
range: options.range,
|
|
132
|
+
end: options.end,
|
|
133
|
+
count,
|
|
134
|
+
measures: measures,
|
|
135
|
+
};
|
|
136
|
+
if (segments) {
|
|
137
|
+
response.segments = segments;
|
|
138
|
+
}
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
141
|
+
handleSegments(data, hashKeys) {
|
|
142
|
+
const segments = [];
|
|
143
|
+
hashKeys.forEach((hashKey, index) => {
|
|
144
|
+
const segmentData = [];
|
|
145
|
+
data[hashKey] && Object.entries(data[hashKey]).forEach(([key, value]) => {
|
|
146
|
+
if (key.startsWith('count:')) {
|
|
147
|
+
const target = key.slice('count:'.length);
|
|
148
|
+
segmentData.push({ target, type: 'count', value });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
const isoTimestamp = this.isoTimestampFromKeyTimestamp(hashKey);
|
|
152
|
+
const count = data[hashKey] ? data[hashKey].count : 0;
|
|
153
|
+
segments.push({ count, time: isoTimestamp, measures: segmentData });
|
|
154
|
+
});
|
|
155
|
+
return segments;
|
|
156
|
+
}
|
|
157
|
+
isoTimestampFromKeyTimestamp(hashKey) {
|
|
158
|
+
const keyTimestamp = hashKey.slice(-12);
|
|
159
|
+
const year = keyTimestamp.slice(0, 4);
|
|
160
|
+
const month = keyTimestamp.slice(4, 6);
|
|
161
|
+
const day = keyTimestamp.slice(6, 8);
|
|
162
|
+
const hour = keyTimestamp.slice(8, 10);
|
|
163
|
+
const minute = keyTimestamp.slice(10, 12);
|
|
164
|
+
return `${year}-${month}-${day}T${hour}:${minute}Z`;
|
|
165
|
+
}
|
|
166
|
+
async getIds(options, facets, idRange = [0, -1]) {
|
|
167
|
+
if (!facets.length) {
|
|
168
|
+
const stats = await this.getStats(options);
|
|
169
|
+
facets = this.getUniqueFacets(stats);
|
|
170
|
+
}
|
|
171
|
+
const { key, granularity, range, end, start } = options;
|
|
172
|
+
this.validateOptions(options);
|
|
173
|
+
let redisKeys = [];
|
|
174
|
+
facets.forEach((facet) => {
|
|
175
|
+
const dateTimeSets = this.generateDateTimeSets(granularity, range, end, start);
|
|
176
|
+
redisKeys = redisKeys.concat(dateTimeSets.map((dateTime) => this.buildRedisKey(key, dateTime, `index:${facet}`)));
|
|
177
|
+
});
|
|
178
|
+
const idsData = await this.store.getJobIds(redisKeys, idRange);
|
|
179
|
+
const idsResponse = this.buildIdsResponse(idsData, options, facets);
|
|
180
|
+
return idsResponse;
|
|
181
|
+
}
|
|
182
|
+
buildIdsResponse(idsData, options, facets) {
|
|
183
|
+
const countsByFacet = {};
|
|
184
|
+
const measureKeys = Object.keys(idsData);
|
|
185
|
+
measureKeys.forEach((key) => {
|
|
186
|
+
const target = this.getTargetForKey(key);
|
|
187
|
+
const count = idsData[key].length;
|
|
188
|
+
if (countsByFacet[target]) {
|
|
189
|
+
countsByFacet[target] += count;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
countsByFacet[target] = count;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
const counts = Object.entries(countsByFacet).map(([facet, count]) => ({ facet, count }));
|
|
196
|
+
const response = {
|
|
197
|
+
key: options.key,
|
|
198
|
+
facets,
|
|
199
|
+
granularity: options.granularity,
|
|
200
|
+
range: options.range,
|
|
201
|
+
start: options.start,
|
|
202
|
+
counts,
|
|
203
|
+
segments: this.buildTimeSegments(idsData),
|
|
204
|
+
};
|
|
205
|
+
return response;
|
|
206
|
+
}
|
|
207
|
+
buildTimeSegments(idsData) {
|
|
208
|
+
const measureKeys = Object.keys(idsData);
|
|
209
|
+
const timeSegments = {};
|
|
210
|
+
measureKeys.forEach((key) => {
|
|
211
|
+
const measure = {
|
|
212
|
+
type: 'ids',
|
|
213
|
+
target: this.getTargetForKey(key),
|
|
214
|
+
time: this.isoTimestampFromKeyTimestamp(this.getTargetForTime(key)),
|
|
215
|
+
count: idsData[key].length,
|
|
216
|
+
ids: idsData[key],
|
|
217
|
+
};
|
|
218
|
+
if (timeSegments[measure.time]) {
|
|
219
|
+
timeSegments[measure.time].push(measure);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
timeSegments[measure.time] = [measure];
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
const segments = Object.entries(timeSegments).map(([time, measures]) => ({
|
|
226
|
+
time,
|
|
227
|
+
measures,
|
|
228
|
+
}));
|
|
229
|
+
return segments;
|
|
230
|
+
}
|
|
231
|
+
getUniqueFacets(data) {
|
|
232
|
+
const targets = data.measures.map(measure => measure.target);
|
|
233
|
+
return Array.from(new Set(targets));
|
|
234
|
+
}
|
|
235
|
+
getTargetForKey(key) {
|
|
236
|
+
return key.split(':index:')[1];
|
|
237
|
+
}
|
|
238
|
+
getTargetForTime(key) {
|
|
239
|
+
return key.split(':index:')[0];
|
|
240
|
+
}
|
|
241
|
+
async getWorkItems(options, facets) {
|
|
242
|
+
if (!facets.length) {
|
|
243
|
+
const stats = await this.getStats(options);
|
|
244
|
+
facets = this.getUniqueFacets(stats);
|
|
245
|
+
}
|
|
246
|
+
const { key, granularity, range, end, start } = options;
|
|
247
|
+
this.validateOptions(options);
|
|
248
|
+
let redisKeys = [];
|
|
249
|
+
facets.forEach((facet) => {
|
|
250
|
+
const dateTimeSets = this.generateDateTimeSets(granularity, range, end, start);
|
|
251
|
+
redisKeys = redisKeys.concat(dateTimeSets.map((dateTime) => this.buildRedisKey(key, dateTime, `index:${facet}`)));
|
|
252
|
+
});
|
|
253
|
+
const idsData = await this.store.getJobIds(redisKeys, [0, 1]);
|
|
254
|
+
const workerLists = this.buildWorkerLists(idsData);
|
|
255
|
+
return workerLists;
|
|
256
|
+
}
|
|
257
|
+
buildWorkerLists(idsData) {
|
|
258
|
+
const workerLists = [];
|
|
259
|
+
for (const key in idsData) {
|
|
260
|
+
if (idsData[key].length) {
|
|
261
|
+
workerLists.push(key);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return workerLists;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* called by `trigger` activity to generate the stats that should
|
|
268
|
+
* be saved to the database. doesn't actually save the stats, but
|
|
269
|
+
* just generates the info that should be saved
|
|
270
|
+
*/
|
|
271
|
+
resolveTriggerStatistics({ stats: statsConfig }, context) {
|
|
272
|
+
const stats = {
|
|
273
|
+
general: [],
|
|
274
|
+
index: [],
|
|
275
|
+
median: []
|
|
276
|
+
};
|
|
277
|
+
stats.general.push({ metric: 'count', target: 'count', value: 1 });
|
|
278
|
+
for (const measure of statsConfig.measures) {
|
|
279
|
+
const metric = this.resolveMetric({ metric: measure.measure, target: measure.target }, context);
|
|
280
|
+
if (this.isGeneralMetric(measure.measure)) {
|
|
281
|
+
stats.general.push(metric);
|
|
282
|
+
}
|
|
283
|
+
else if (this.isMedianMetric(measure.measure)) {
|
|
284
|
+
stats.median.push(metric);
|
|
285
|
+
}
|
|
286
|
+
else if (this.isIndexMetric(measure.measure)) {
|
|
287
|
+
stats.index.push(metric);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return stats;
|
|
291
|
+
}
|
|
292
|
+
isGeneralMetric(metric) {
|
|
293
|
+
return metric === 'sum' || metric === 'avg' || metric === 'count';
|
|
294
|
+
}
|
|
295
|
+
isMedianMetric(metric) {
|
|
296
|
+
return metric === 'mdn';
|
|
297
|
+
}
|
|
298
|
+
isIndexMetric(metric) {
|
|
299
|
+
return metric === 'index';
|
|
300
|
+
}
|
|
301
|
+
resolveMetric({ metric, target }, context) {
|
|
302
|
+
const pipe = new pipe_1.Pipe([[target]], context);
|
|
303
|
+
const resolvedValue = pipe.process().toString();
|
|
304
|
+
const resolvedTarget = this.resolveTarget(metric, target, resolvedValue);
|
|
305
|
+
if (metric === 'index') {
|
|
306
|
+
return { metric, target: resolvedTarget, value: context.metadata.jid };
|
|
307
|
+
}
|
|
308
|
+
else if (metric === 'count') {
|
|
309
|
+
return { metric, target: resolvedTarget, value: 1 };
|
|
310
|
+
}
|
|
311
|
+
return { metric, target: resolvedTarget, value: resolvedValue };
|
|
312
|
+
}
|
|
313
|
+
isCardinalMetric(metric) {
|
|
314
|
+
return metric === 'index' || metric === 'count';
|
|
315
|
+
}
|
|
316
|
+
resolveTarget(metric, target, resolvedValue) {
|
|
317
|
+
const trimmed = target.substring(1, target.length - 1);
|
|
318
|
+
const trimmedTarget = trimmed.split('.').slice(3).join('/');
|
|
319
|
+
let resolvedTarget;
|
|
320
|
+
if (this.isCardinalMetric(metric)) {
|
|
321
|
+
resolvedTarget = `${metric}:${trimmedTarget}:${resolvedValue}`;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
resolvedTarget = `${metric}:${trimmedTarget}`;
|
|
325
|
+
}
|
|
326
|
+
return resolvedTarget;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
exports.ReporterService = ReporterService;
|
|
330
|
+
ReporterService.DEFAULT_GRANULARITY = '5m';
|