@hotmeshio/hotmesh 0.0.55 → 0.0.57
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/README.md +1 -1
- package/build/modules/enums.js +1 -10
- package/build/modules/key.d.ts +0 -38
- package/build/modules/key.js +4 -46
- package/build/modules/utils.d.ts +0 -8
- package/build/modules/utils.js +0 -14
- package/build/package.json +11 -4
- package/build/services/activities/activity.d.ts +0 -28
- package/build/services/activities/activity.js +1 -46
- package/build/services/activities/await.js +0 -4
- package/build/services/activities/cycle.d.ts +0 -7
- package/build/services/activities/cycle.js +1 -16
- package/build/services/activities/hook.d.ts +0 -6
- package/build/services/activities/hook.js +2 -12
- package/build/services/activities/interrupt.js +0 -8
- package/build/services/activities/signal.d.ts +0 -6
- package/build/services/activities/signal.js +0 -15
- package/build/services/activities/trigger.d.ts +0 -4
- package/build/services/activities/trigger.js +1 -7
- package/build/services/activities/worker.js +0 -4
- package/build/services/collator/index.d.ts +0 -70
- package/build/services/collator/index.js +1 -91
- package/build/services/compiler/deployer.js +6 -38
- package/build/services/compiler/index.d.ts +0 -15
- package/build/services/compiler/index.js +0 -20
- package/build/services/compiler/validator.d.ts +0 -3
- package/build/services/compiler/validator.js +0 -25
- package/build/services/connector/clients/ioredis.d.ts +2 -2
- package/build/services/connector/clients/ioredis.js +0 -2
- package/build/services/connector/clients/redis.d.ts +4 -4
- package/build/services/connector/clients/redis.js +1 -3
- package/build/services/connector/index.d.ts +1 -1
- package/build/services/connector/index.js +0 -2
- package/build/services/durable/client.d.ts +1 -26
- package/build/services/durable/client.js +0 -56
- package/build/services/durable/exporter.d.ts +0 -22
- package/build/services/durable/exporter.js +1 -30
- package/build/services/durable/handle.d.ts +0 -36
- package/build/services/durable/handle.js +0 -46
- package/build/services/durable/index.d.ts +0 -4
- package/build/services/durable/index.js +0 -4
- package/build/services/durable/schemas/factory.d.ts +0 -29
- package/build/services/durable/schemas/factory.js +0 -29
- package/build/services/durable/search.d.ts +1 -36
- package/build/services/durable/search.js +56 -56
- package/build/services/durable/worker.js +2 -22
- package/build/services/durable/workflow.d.ts +0 -114
- package/build/services/durable/workflow.js +1 -141
- package/build/services/engine/index.d.ts +1 -6
- package/build/services/engine/index.js +1 -43
- package/build/services/exporter/index.d.ts +0 -27
- package/build/services/exporter/index.js +0 -33
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +1 -9
- package/build/services/logger/index.js +0 -2
- package/build/services/mapper/index.d.ts +0 -14
- package/build/services/mapper/index.js +0 -14
- package/build/services/pipe/functions/date.d.ts +0 -7
- package/build/services/pipe/functions/date.js +0 -7
- package/build/services/pipe/functions/math.js +0 -2
- package/build/services/pipe/index.d.ts +0 -15
- package/build/services/pipe/index.js +2 -23
- package/build/services/quorum/index.d.ts +0 -7
- package/build/services/quorum/index.js +0 -21
- package/build/services/reporter/index.d.ts +0 -5
- package/build/services/reporter/index.js +0 -9
- package/build/services/router/index.d.ts +0 -9
- package/build/services/router/index.js +2 -38
- package/build/services/serializer/index.js +7 -26
- package/build/services/store/cache.d.ts +0 -18
- package/build/services/store/cache.js +0 -18
- package/build/services/store/clients/ioredis.d.ts +1 -1
- package/build/services/store/clients/ioredis.js +0 -1
- package/build/services/store/clients/redis.d.ts +1 -1
- package/build/services/store/index.d.ts +0 -55
- package/build/services/store/index.js +5 -81
- package/build/services/stream/clients/ioredis.d.ts +1 -1
- package/build/services/stream/clients/ioredis.js +1 -4
- package/build/services/stream/clients/redis.d.ts +1 -1
- package/build/services/sub/clients/ioredis.d.ts +1 -1
- package/build/services/sub/clients/redis.d.ts +1 -1
- package/build/services/task/index.d.ts +0 -9
- package/build/services/task/index.js +0 -31
- package/build/services/telemetry/index.d.ts +0 -7
- package/build/services/telemetry/index.js +1 -13
- package/build/services/worker/index.d.ts +0 -4
- package/build/services/worker/index.js +2 -6
- package/build/types/activity.d.ts +0 -81
- package/build/types/durable.d.ts +26 -177
- package/build/types/exporter.d.ts +0 -13
- package/build/types/hotmesh.d.ts +4 -16
- package/build/types/hotmesh.js +0 -3
- package/build/types/index.d.ts +4 -6
- package/build/types/index.js +4 -3
- package/build/types/job.d.ts +1 -86
- package/build/types/pipe.d.ts +0 -65
- package/build/types/quorum.d.ts +15 -10
- package/build/types/redis.d.ts +225 -7
- package/build/types/redis.js +9 -0
- package/build/types/stream.d.ts +0 -58
- package/build/types/stream.js +0 -4
- package/package.json +11 -4
- package/types/durable.ts +131 -4
- package/types/hotmesh.ts +3 -6
- package/types/index.ts +23 -10
- package/types/job.ts +1 -1
- package/types/quorum.ts +22 -0
- package/types/redis.ts +267 -18
- package/build/types/ioredisclient.d.ts +0 -5
- package/build/types/ioredisclient.js +0 -5
- package/build/types/redisclient.d.ts +0 -26
- package/build/types/redisclient.js +0 -2
- package/modules/enums.ts +0 -62
- package/modules/errors.ts +0 -280
- package/modules/key.ts +0 -101
- package/modules/storage.ts +0 -3
- package/modules/utils.ts +0 -242
- package/services/activities/activity.ts +0 -589
- package/services/activities/await.ts +0 -113
- package/services/activities/cycle.ts +0 -115
- package/services/activities/hook.ts +0 -197
- package/services/activities/index.ts +0 -19
- package/services/activities/interrupt.ts +0 -172
- package/services/activities/signal.ts +0 -148
- package/services/activities/trigger.ts +0 -295
- package/services/activities/worker.ts +0 -107
- package/services/collator/README.md +0 -102
- package/services/collator/index.ts +0 -291
- package/services/compiler/deployer.ts +0 -504
- package/services/compiler/index.ts +0 -98
- package/services/compiler/validator.ts +0 -158
- package/services/connector/clients/ioredis.ts +0 -57
- package/services/connector/clients/redis.ts +0 -72
- package/services/connector/index.ts +0 -42
- package/services/durable/client.ts +0 -266
- package/services/durable/connection.ts +0 -10
- package/services/durable/exporter.ts +0 -232
- package/services/durable/handle.ts +0 -160
- package/services/durable/index.ts +0 -27
- package/services/durable/schemas/factory.ts +0 -2358
- package/services/durable/search.ts +0 -196
- package/services/durable/worker.ts +0 -401
- package/services/durable/workflow.ts +0 -557
- package/services/engine/index.ts +0 -761
- package/services/exporter/index.ts +0 -146
- package/services/hotmesh/index.ts +0 -237
- package/services/logger/index.ts +0 -79
- package/services/mapper/index.ts +0 -89
- package/services/pipe/functions/array.ts +0 -78
- package/services/pipe/functions/bitwise.ts +0 -27
- package/services/pipe/functions/conditional.ts +0 -35
- package/services/pipe/functions/date.ts +0 -220
- package/services/pipe/functions/index.ts +0 -27
- package/services/pipe/functions/json.ts +0 -11
- package/services/pipe/functions/logical.ts +0 -11
- package/services/pipe/functions/math.ts +0 -217
- package/services/pipe/functions/number.ts +0 -75
- package/services/pipe/functions/object.ts +0 -98
- package/services/pipe/functions/string.ts +0 -86
- package/services/pipe/functions/symbol.ts +0 -39
- package/services/pipe/functions/unary.ts +0 -19
- package/services/pipe/index.ts +0 -216
- package/services/quorum/index.ts +0 -319
- package/services/reporter/index.ts +0 -387
- package/services/router/index.ts +0 -426
- package/services/serializer/README.md +0 -10
- package/services/serializer/index.ts +0 -285
- package/services/store/cache.ts +0 -172
- package/services/store/clients/ioredis.ts +0 -145
- package/services/store/clients/redis.ts +0 -191
- package/services/store/index.ts +0 -1091
- package/services/stream/clients/ioredis.ts +0 -157
- package/services/stream/clients/redis.ts +0 -158
- package/services/stream/index.ts +0 -58
- package/services/sub/clients/ioredis.ts +0 -83
- package/services/sub/clients/redis.ts +0 -74
- package/services/sub/index.ts +0 -25
- package/services/task/index.ts +0 -250
- package/services/telemetry/index.ts +0 -273
- package/services/worker/index.ts +0 -248
- package/types/ioredisclient.ts +0 -10
- package/types/redisclient.ts +0 -30
package/services/task/index.ts
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HMSH_EXPIRE_DURATION,
|
|
3
|
-
HMSH_FIDELITY_SECONDS,
|
|
4
|
-
HMSH_SCOUT_INTERVAL_SECONDS} from '../../modules/enums';
|
|
5
|
-
import { XSleepFor, sleepFor } from '../../modules/utils';
|
|
6
|
-
import { ILogger } from '../logger';
|
|
7
|
-
import { Pipe } from '../pipe';
|
|
8
|
-
import { StoreService } from '../store';
|
|
9
|
-
import { HookInterface, HookRule, HookSignal } from '../../types/hook';
|
|
10
|
-
import { KeyType } from '../../types/hotmesh';
|
|
11
|
-
import { JobCompletionOptions, JobState } from '../../types/job';
|
|
12
|
-
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
13
|
-
import { WorkListTaskType } from '../../types/task';
|
|
14
|
-
import { VALSEP, WEBSEP } from '../../modules/key';
|
|
15
|
-
|
|
16
|
-
class TaskService {
|
|
17
|
-
store: StoreService<RedisClient, RedisMulti>;
|
|
18
|
-
logger: ILogger;
|
|
19
|
-
cleanupTimeout: NodeJS.Timeout | null = null;
|
|
20
|
-
isScout: boolean = false;
|
|
21
|
-
errorCount = 0;
|
|
22
|
-
|
|
23
|
-
constructor(
|
|
24
|
-
store: StoreService<RedisClient, RedisMulti>,
|
|
25
|
-
logger: ILogger
|
|
26
|
-
) {
|
|
27
|
-
this.logger = logger;
|
|
28
|
-
this.store = store;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async processWebHooks(hookEventCallback: HookInterface): Promise<void> {
|
|
32
|
-
const workItemKey = await this.store.getActiveTaskQueue();
|
|
33
|
-
if (workItemKey) {
|
|
34
|
-
const [topic, sourceKey, scrub, ...sdata] = workItemKey.split(WEBSEP);
|
|
35
|
-
const data = JSON.parse(sdata.join(WEBSEP));
|
|
36
|
-
const destinationKey = `${sourceKey}:processed`;
|
|
37
|
-
const jobId = await this.store.processTaskQueue(sourceKey, destinationKey);
|
|
38
|
-
if (jobId) {
|
|
39
|
-
//todo: don't use 'id', make configurable using hook rule
|
|
40
|
-
await hookEventCallback(topic, { ...data, id: jobId });
|
|
41
|
-
} else {
|
|
42
|
-
await this.store.deleteProcessedTaskQueue(
|
|
43
|
-
workItemKey,
|
|
44
|
-
sourceKey,
|
|
45
|
-
destinationKey,
|
|
46
|
-
scrub === 'true'
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
setImmediate(() => this.processWebHooks(hookEventCallback));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async enqueueWorkItems(keys: string[]): Promise<void> {
|
|
54
|
-
await this.store.addTaskQueues(keys);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async registerJobForCleanup(jobId: string, inSeconds = HMSH_EXPIRE_DURATION, options: JobCompletionOptions): Promise<void> {
|
|
58
|
-
if (inSeconds > 0) {
|
|
59
|
-
await this.store.expireJob(jobId, inSeconds);
|
|
60
|
-
const fromNow = Date.now() + (inSeconds * 1000);
|
|
61
|
-
const fidelityMS = HMSH_FIDELITY_SECONDS * 1000;
|
|
62
|
-
const timeSlot = Math.floor(fromNow / fidelityMS) * fidelityMS;
|
|
63
|
-
await this.store.registerDependenciesForCleanup(
|
|
64
|
-
jobId,
|
|
65
|
-
timeSlot,
|
|
66
|
-
options,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async registerTimeHook(
|
|
72
|
-
jobId: string,
|
|
73
|
-
gId: string,
|
|
74
|
-
activityId: string,
|
|
75
|
-
type: WorkListTaskType,
|
|
76
|
-
inSeconds = HMSH_FIDELITY_SECONDS,
|
|
77
|
-
dad: string,
|
|
78
|
-
multi?: RedisMulti,
|
|
79
|
-
): Promise<void> {
|
|
80
|
-
const fromNow = Date.now() + (inSeconds * 1000);
|
|
81
|
-
const fidelityMS = HMSH_FIDELITY_SECONDS * 1000;
|
|
82
|
-
const awakenTimeSlot = Math.floor(fromNow / fidelityMS) * fidelityMS;
|
|
83
|
-
await this.store.registerTimeHook(
|
|
84
|
-
jobId,
|
|
85
|
-
gId,
|
|
86
|
-
activityId,
|
|
87
|
-
type,
|
|
88
|
-
awakenTimeSlot,
|
|
89
|
-
dad,
|
|
90
|
-
multi,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Should this engine instance play the role of 'scout' on behalf
|
|
96
|
-
* of the entire quorum? The scout role is responsible for processing
|
|
97
|
-
* task lists on behalf of the collective.
|
|
98
|
-
*/
|
|
99
|
-
async shouldScout() {
|
|
100
|
-
const wasScout = this.isScout;
|
|
101
|
-
const isScout = wasScout || (this.isScout = await this.store.reserveScoutRole('time'));
|
|
102
|
-
if (isScout) {
|
|
103
|
-
if (!wasScout) {
|
|
104
|
-
setTimeout(() => {
|
|
105
|
-
this.isScout = false;
|
|
106
|
-
}, HMSH_SCOUT_INTERVAL_SECONDS * 1_000);
|
|
107
|
-
}
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Callback handler that takes an item from a work list and
|
|
115
|
-
* processes according to its type
|
|
116
|
-
*/
|
|
117
|
-
async processTimeHooks(timeEventCallback: (jobId: string, gId: string, activityId: string, type: WorkListTaskType) => Promise<void>, listKey?: string): Promise<void> {
|
|
118
|
-
if (await this.shouldScout()) {
|
|
119
|
-
try {
|
|
120
|
-
const workListTask = await this.store.getNextTask(listKey);
|
|
121
|
-
|
|
122
|
-
if (Array.isArray(workListTask)) {
|
|
123
|
-
const [listKey, target, gId, activityId, type] = workListTask;
|
|
124
|
-
if (type === 'child') {
|
|
125
|
-
//continue; this child is listed here for convenience, but
|
|
126
|
-
// will be expired by an origin ancestor and is listed there
|
|
127
|
-
} else if (type === 'delist') {
|
|
128
|
-
//delist the signalKey (target)
|
|
129
|
-
const key = this.store.mintKey(KeyType.SIGNALS, { appId: this.store.appId });
|
|
130
|
-
await this.store.redisClient[this.store.commands.hdel](key, target);
|
|
131
|
-
} else {
|
|
132
|
-
//awaken/expire/interrupt
|
|
133
|
-
await timeEventCallback(target, gId, activityId, type);
|
|
134
|
-
}
|
|
135
|
-
await sleepFor(0);
|
|
136
|
-
this.errorCount = 0;
|
|
137
|
-
this.processTimeHooks(timeEventCallback, listKey);
|
|
138
|
-
} else if (workListTask) {
|
|
139
|
-
//a worklist was just emptied; try again immediately
|
|
140
|
-
await sleepFor(0);
|
|
141
|
-
this.errorCount = 0;
|
|
142
|
-
this.processTimeHooks(timeEventCallback);
|
|
143
|
-
} else {
|
|
144
|
-
//no worklists exist; sleep before checking
|
|
145
|
-
let sleep = XSleepFor(HMSH_FIDELITY_SECONDS * 1000);
|
|
146
|
-
this.cleanupTimeout = sleep.timerId;
|
|
147
|
-
await sleep.promise;
|
|
148
|
-
this.errorCount = 0;
|
|
149
|
-
this.processTimeHooks(timeEventCallback);
|
|
150
|
-
}
|
|
151
|
-
} catch (err) {
|
|
152
|
-
//most common reasons: deleted job not found; container stopping; test stopping
|
|
153
|
-
//less common: redis/cluster down; retry with fallback (5s max main reassignment)
|
|
154
|
-
this.logger.warn('task-process-timehooks-error', err);
|
|
155
|
-
await sleepFor(1_000 * this.errorCount++);
|
|
156
|
-
if (this.errorCount < 5) {
|
|
157
|
-
this.processTimeHooks(timeEventCallback);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} else {
|
|
161
|
-
//didn't get the scout role; try again in 'one-ish' minutes
|
|
162
|
-
let sleep = XSleepFor(HMSH_SCOUT_INTERVAL_SECONDS * 1_000 * 2 * Math.random());
|
|
163
|
-
this.cleanupTimeout = sleep.timerId;
|
|
164
|
-
await sleep.promise;
|
|
165
|
-
this.processTimeHooks(timeEventCallback);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
cancelCleanup() {
|
|
170
|
-
if (this.cleanupTimeout !== undefined) {
|
|
171
|
-
clearTimeout(this.cleanupTimeout);
|
|
172
|
-
this.cleanupTimeout = undefined;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async getHookRule(topic: string): Promise<HookRule | undefined> {
|
|
177
|
-
const rules = await this.store.getHookRules();
|
|
178
|
-
return rules?.[topic]?.[0] as HookRule;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async registerWebHook(topic: string, context: JobState, dad: string, multi?: RedisMulti): Promise<string> {
|
|
182
|
-
const hookRule = await this.getHookRule(topic);
|
|
183
|
-
if (hookRule) {
|
|
184
|
-
const mapExpression = hookRule.conditions.match[0].expected;
|
|
185
|
-
const resolved = Pipe.resolve(mapExpression, context);
|
|
186
|
-
const jobId = context.metadata.jid;
|
|
187
|
-
const gId = context.metadata.gid;
|
|
188
|
-
const activityId = hookRule.to;
|
|
189
|
-
//composite keys are used to fully describe the task target
|
|
190
|
-
const compositeJobKey = [
|
|
191
|
-
activityId,
|
|
192
|
-
dad,
|
|
193
|
-
gId,
|
|
194
|
-
jobId
|
|
195
|
-
].join(WEBSEP);
|
|
196
|
-
|
|
197
|
-
const hook: HookSignal = {
|
|
198
|
-
topic,
|
|
199
|
-
resolved,
|
|
200
|
-
jobId: compositeJobKey,
|
|
201
|
-
}
|
|
202
|
-
await this.store.setHookSignal(hook, multi);
|
|
203
|
-
return jobId;
|
|
204
|
-
} else {
|
|
205
|
-
throw new Error('signaler.registerWebHook:error: hook rule not found');
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async processWebHookSignal(topic: string, data: Record<string, unknown>): Promise<[string, string, string, string] | undefined> {
|
|
210
|
-
const hookRule = await this.getHookRule(topic);
|
|
211
|
-
if (hookRule) {
|
|
212
|
-
//NOTE: both formats are supported by the mapping engine:
|
|
213
|
-
// `$self.hook.data` OR `$hook.data`
|
|
214
|
-
const context = { $self: { hook: { data }}, $hook: { data }};
|
|
215
|
-
const mapExpression = hookRule.conditions.match[0].actual;
|
|
216
|
-
const resolved = Pipe.resolve(mapExpression, context);
|
|
217
|
-
const hookSignalId = await this.store.getHookSignal(topic, resolved);
|
|
218
|
-
if (!hookSignalId) {
|
|
219
|
-
//messages can be double-processed; not an issue; return `undefined`
|
|
220
|
-
//users can also provide a bogus topic; not an issue; return `undefined`
|
|
221
|
-
return undefined;
|
|
222
|
-
}
|
|
223
|
-
//`aid` is part of composite key, but the hook `topic` is its public interface;
|
|
224
|
-
// this means that a new version of the graph can be deployed and the
|
|
225
|
-
// topic can be re-mapped to a different activity id. Outside callers
|
|
226
|
-
// can adhere to the unchanged contract (calling the same topic),
|
|
227
|
-
// while the internal system can be updated in real-time as necessary.
|
|
228
|
-
const [_aid, dad, gid, ...jid] = hookSignalId.split(WEBSEP);
|
|
229
|
-
return [jid.join(WEBSEP), hookRule.to, dad, gid];
|
|
230
|
-
} else {
|
|
231
|
-
throw new Error('signal-not-found');
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
async deleteWebHookSignal(topic: string, data: Record<string, unknown>): Promise<number> {
|
|
236
|
-
const hookRule = await this.getHookRule(topic);
|
|
237
|
-
if (hookRule) {
|
|
238
|
-
//NOTE: both formats are supported by the mapping engine:
|
|
239
|
-
// `$self.hook.data` OR `$hook.data`
|
|
240
|
-
const context = { $self: { hook: { data }}, $hook: { data }};
|
|
241
|
-
const mapExpression = hookRule.conditions.match[0].actual;
|
|
242
|
-
const resolved = Pipe.resolve(mapExpression, context);
|
|
243
|
-
return await this.store.deleteHookSignal(topic, resolved);
|
|
244
|
-
} else {
|
|
245
|
-
throw new Error('signaler.process:error: hook rule not found');
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export { TaskService };
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import packageJson from '../../package.json';
|
|
2
|
-
import { MapperService } from '../mapper';
|
|
3
|
-
import {
|
|
4
|
-
ActivityMetadata,
|
|
5
|
-
ActivityType,
|
|
6
|
-
Consumes } from '../../types/activity';
|
|
7
|
-
import { JobState } from '../../types/job';
|
|
8
|
-
import {
|
|
9
|
-
StringAnyType,
|
|
10
|
-
StringScalarType,
|
|
11
|
-
StringStringType } from '../../types/serializer';
|
|
12
|
-
import { StreamData, StreamDataType, StreamRole } from '../../types/stream';
|
|
13
|
-
import {
|
|
14
|
-
Span,
|
|
15
|
-
SpanContext,
|
|
16
|
-
SpanKind,
|
|
17
|
-
trace,
|
|
18
|
-
Context,
|
|
19
|
-
context,
|
|
20
|
-
SpanStatusCode } from '../../types/telemetry';
|
|
21
|
-
import { polyfill } from '../../modules/utils';
|
|
22
|
-
|
|
23
|
-
class TelemetryService {
|
|
24
|
-
span: Span;
|
|
25
|
-
jobSpan: Span;
|
|
26
|
-
config: ActivityType;
|
|
27
|
-
traceId: string | null;
|
|
28
|
-
spanId: string | null;
|
|
29
|
-
appId: string;
|
|
30
|
-
metadata: ActivityMetadata;
|
|
31
|
-
context: JobState;
|
|
32
|
-
leg = 1;
|
|
33
|
-
|
|
34
|
-
constructor(
|
|
35
|
-
appId: string,
|
|
36
|
-
config?: ActivityType,
|
|
37
|
-
metadata?: ActivityMetadata,
|
|
38
|
-
context?: JobState,
|
|
39
|
-
) {
|
|
40
|
-
this.appId = appId;
|
|
41
|
-
//these are REQUIRED for job and activity spans
|
|
42
|
-
this.config = config;
|
|
43
|
-
this.metadata = metadata;
|
|
44
|
-
this.context = context;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
getJobParentSpanId(): string | undefined {
|
|
48
|
-
return this.context.metadata.spn;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getActivityParentSpanId(leg: number): string | undefined {
|
|
52
|
-
if (leg === 1) {
|
|
53
|
-
return this.context[this.config.parent].output?.metadata?.l2s;
|
|
54
|
-
} else {
|
|
55
|
-
return this.context['$self'].output?.metadata?.l1s;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
getTraceId(): string | undefined {
|
|
60
|
-
return this.context.metadata.trc;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
startJobSpan(): TelemetryService {
|
|
64
|
-
const spanName = `JOB/${this.appId}/${this.config.subscribes}/1`;
|
|
65
|
-
const traceId = this.getTraceId();
|
|
66
|
-
const spanId = this.getJobParentSpanId();
|
|
67
|
-
const attributes = this.getSpanAttrs(1);
|
|
68
|
-
const span: Span = this.startSpan(traceId, spanId, spanName, attributes);
|
|
69
|
-
this.jobSpan = span;
|
|
70
|
-
this.setTelemetryContext(span, 1);
|
|
71
|
-
return this;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
startActivitySpan(leg = this.leg): TelemetryService {
|
|
75
|
-
const spanName = `${this.config.type.toUpperCase()}/${this.appId}/${this.metadata.aid}/${leg}`;
|
|
76
|
-
const traceId = this.getTraceId();
|
|
77
|
-
const spanId = this.getActivityParentSpanId(leg);
|
|
78
|
-
const attributes = this.getSpanAttrs(leg);
|
|
79
|
-
const span: Span = this.startSpan(traceId, spanId, spanName, attributes);
|
|
80
|
-
this.setTelemetryContext(span, leg);
|
|
81
|
-
this.span = span;
|
|
82
|
-
return this;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
startStreamSpan(data: StreamData, role: StreamRole): TelemetryService {
|
|
86
|
-
let type: string;
|
|
87
|
-
if (role === StreamRole.SYSTEM) {
|
|
88
|
-
type = 'SYSTEM';
|
|
89
|
-
} else if (role === StreamRole.WORKER) {
|
|
90
|
-
type = 'EXECUTE';
|
|
91
|
-
} else if (data.type === StreamDataType.RESULT || data.type === StreamDataType.RESPONSE) {
|
|
92
|
-
type = 'FANIN';
|
|
93
|
-
} else {
|
|
94
|
-
type = 'FANOUT';
|
|
95
|
-
}
|
|
96
|
-
const topic = data.metadata.topic ? `/${data.metadata.topic}` : '';
|
|
97
|
-
const spanName = `${type}/${this.appId}/${data.metadata.aid}${topic}`;
|
|
98
|
-
const attributes = this.getStreamSpanAttrs(data);
|
|
99
|
-
const span: Span = this.startSpan(data.metadata.trc, data.metadata.spn, spanName, attributes);
|
|
100
|
-
this.span = span;
|
|
101
|
-
return this;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
startSpan(traceId: string, spanId: string, spanName: string, attributes: StringScalarType): Span {
|
|
105
|
-
this.traceId = traceId;
|
|
106
|
-
this.spanId = spanId;
|
|
107
|
-
const tracer = trace.getTracer(packageJson.name, packageJson.version);
|
|
108
|
-
let parentContext = this.getParentSpanContext();
|
|
109
|
-
const span = tracer.startSpan(
|
|
110
|
-
spanName,
|
|
111
|
-
{ kind: SpanKind.CLIENT, attributes, root: !parentContext },
|
|
112
|
-
parentContext
|
|
113
|
-
);
|
|
114
|
-
return span;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
mapActivityAttributes(): void {
|
|
118
|
-
//export user-defined span attributes (app.activity.data.*)
|
|
119
|
-
if (this.config.telemetry) {
|
|
120
|
-
const telemetryAtts = new MapperService(this.config.telemetry, this.context).mapRules();
|
|
121
|
-
const namespacedAtts = {
|
|
122
|
-
...Object.keys(telemetryAtts).reduce((result, key) => {
|
|
123
|
-
if (['string', 'boolean', 'number'].includes(typeof telemetryAtts[key])) {
|
|
124
|
-
result[`app.activity.data.${key}`] = telemetryAtts[key];
|
|
125
|
-
}
|
|
126
|
-
return result;
|
|
127
|
-
}, {})
|
|
128
|
-
};
|
|
129
|
-
this.span?.setAttributes(namespacedAtts as StringScalarType);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
setActivityAttributes(attributes: StringScalarType): void {
|
|
134
|
-
this.span?.setAttributes(attributes);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
setStreamAttributes(attributes: StringScalarType): void {
|
|
138
|
-
this.span?.setAttributes(attributes);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
setJobAttributes(attributes: StringScalarType): void {
|
|
142
|
-
this.jobSpan?.setAttributes(attributes);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
endJobSpan(): void {
|
|
146
|
-
this.endSpan(this.jobSpan);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
endActivitySpan(): void {
|
|
150
|
-
this.endSpan(this.span);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
endStreamSpan(): void {
|
|
154
|
-
this.endSpan(this.span);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
endSpan(span: Span): void {
|
|
158
|
-
span && span.end();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
getParentSpanContext(): undefined | Context {
|
|
162
|
-
if (this.traceId && this.spanId) {
|
|
163
|
-
const restoredSpanContext: SpanContext = {
|
|
164
|
-
traceId: this.traceId,
|
|
165
|
-
spanId: this.spanId,
|
|
166
|
-
isRemote: true,
|
|
167
|
-
traceFlags: 1, // (todo: revisit sampling strategy/config)
|
|
168
|
-
};
|
|
169
|
-
const parentContext = trace.setSpanContext(context.active(), restoredSpanContext);
|
|
170
|
-
return parentContext;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
getSpanAttrs(leg: number): StringAnyType {
|
|
175
|
-
return {
|
|
176
|
-
...Object.keys(this.context.metadata).reduce((result, key) => {
|
|
177
|
-
if (key !== 'trc') {
|
|
178
|
-
result[`app.job.${key}`] = this.context.metadata[key];
|
|
179
|
-
}
|
|
180
|
-
return result;
|
|
181
|
-
}, {}),
|
|
182
|
-
...Object.keys(this.metadata).reduce((result, key) => {
|
|
183
|
-
result[`app.activity.${key}`] = this.metadata[key];
|
|
184
|
-
return result;
|
|
185
|
-
}, {}),
|
|
186
|
-
'app.activity.leg': leg,
|
|
187
|
-
};
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
getStreamSpanAttrs(input: StreamData): StringAnyType {
|
|
191
|
-
return {
|
|
192
|
-
...Object.keys(input.metadata).reduce((result, key) => {
|
|
193
|
-
if (key !== 'trc' && key !== 'spn') {
|
|
194
|
-
result[`app.stream.${key}`] = input.metadata[key];
|
|
195
|
-
}
|
|
196
|
-
return result;
|
|
197
|
-
}, {})
|
|
198
|
-
};
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
setTelemetryContext(span: Span, leg: number) {
|
|
202
|
-
if (!this.context.metadata.trc) {
|
|
203
|
-
this.context.metadata.trc = span.spanContext().traceId;
|
|
204
|
-
}
|
|
205
|
-
if (leg === 1) {
|
|
206
|
-
if (!this.context['$self'].output.metadata) {
|
|
207
|
-
this.context['$self'].output.metadata = {};
|
|
208
|
-
}
|
|
209
|
-
this.context['$self'].output.metadata.l1s = span.spanContext().spanId;
|
|
210
|
-
} else {
|
|
211
|
-
if (!this.context['$self'].output.metadata) {
|
|
212
|
-
this.context['$self'].output.metadata = {};
|
|
213
|
-
}
|
|
214
|
-
this.context['$self'].output.metadata.l2s = span.spanContext().spanId;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
setActivityError(message: string) {
|
|
219
|
-
this.span?.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
setStreamError(message: string) {
|
|
223
|
-
this.span?.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Adds the paths (HGET) necessary to restore telemetry state for an activity
|
|
228
|
-
* @param consumes
|
|
229
|
-
* @param config
|
|
230
|
-
* @param metadata
|
|
231
|
-
* @param leg
|
|
232
|
-
*/
|
|
233
|
-
static addTargetTelemetryPaths(consumes: Consumes, config: ActivityType, metadata: ActivityMetadata, leg: number): void {
|
|
234
|
-
if (leg === 1) {
|
|
235
|
-
if (!(config.parent in consumes)) {
|
|
236
|
-
consumes[config.parent] = [];
|
|
237
|
-
}
|
|
238
|
-
consumes[config.parent].push(`${config.parent}/output/metadata/l2s`);
|
|
239
|
-
} else {
|
|
240
|
-
if (!(metadata.aid in consumes)) {
|
|
241
|
-
consumes[metadata.aid] = [];
|
|
242
|
-
}
|
|
243
|
-
consumes[metadata.aid].push(`${metadata.aid}/output/metadata/l1s`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
static bindJobTelemetryToState(state: StringStringType, config: ActivityType, context:JobState) {
|
|
248
|
-
if (config.type === 'trigger') {
|
|
249
|
-
state['metadata/trc'] = context.metadata.trc;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
static bindActivityTelemetryToState(state: StringAnyType, config: ActivityType, metadata: ActivityMetadata, context: JobState, leg: number): void {
|
|
254
|
-
if (config.type === 'trigger') {
|
|
255
|
-
//trigger activities run non-duplexed and only have a single leg (2)
|
|
256
|
-
state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
|
|
257
|
-
state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l2s;
|
|
258
|
-
} else if (polyfill.resolveActivityType(config.type) === 'hook' && leg === 1) {
|
|
259
|
-
//hook activities run non-duplexed and only have a single leg (1)
|
|
260
|
-
state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
|
|
261
|
-
state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l1s;
|
|
262
|
-
} else if (config.type === 'signal' && leg === 1) {
|
|
263
|
-
//signal activities run non-duplexed and only have a single leg (1)
|
|
264
|
-
state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
|
|
265
|
-
state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l1s;
|
|
266
|
-
} else {
|
|
267
|
-
const target = `l${leg}s`;
|
|
268
|
-
state[`${metadata.aid}/output/metadata/${target}`] = context['$self'].output.metadata[target];
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export { TelemetryService };
|