teraslice 2.11.0 → 2.12.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/dist/src/interfaces.js +12 -0
- package/dist/src/lib/cluster/cluster_master.js +246 -0
- package/dist/src/lib/cluster/node_master.js +355 -0
- package/dist/src/lib/cluster/services/api.js +663 -0
- package/dist/src/lib/cluster/services/assets.js +226 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/index.js +192 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8s.js +481 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sResource.js +414 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sState.js +59 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/utils.js +43 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/index.js +192 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/interfaces.js +2 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8s.js +423 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sDeploymentResource.js +60 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sJobResource.js +55 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sResource.js +359 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sServiceResource.js +37 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sState.js +60 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/utils.js +170 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/dispatch.js +13 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/index.js +526 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/messaging.js +547 -0
- package/dist/src/lib/cluster/services/cluster/backends/state-utils.js +26 -0
- package/dist/src/lib/cluster/services/cluster/index.js +17 -0
- package/dist/src/lib/cluster/services/execution.js +435 -0
- package/dist/src/lib/cluster/services/index.js +6 -0
- package/dist/src/lib/cluster/services/interfaces.js +2 -0
- package/dist/src/lib/cluster/services/jobs.js +454 -0
- package/dist/src/lib/config/default-sysconfig.js +26 -0
- package/dist/src/lib/config/index.js +22 -0
- package/dist/src/lib/config/schemas/system.js +360 -0
- package/dist/src/lib/storage/analytics.js +86 -0
- package/dist/src/lib/storage/assets.js +401 -0
- package/dist/src/lib/storage/backends/elasticsearch_store.js +494 -0
- package/dist/src/lib/storage/backends/mappings/analytics.js +50 -0
- package/dist/src/lib/storage/backends/mappings/asset.js +41 -0
- package/dist/src/lib/storage/backends/mappings/ex.js +62 -0
- package/dist/src/lib/storage/backends/mappings/job.js +38 -0
- package/dist/src/lib/storage/backends/mappings/state.js +38 -0
- package/dist/src/lib/storage/backends/s3_store.js +237 -0
- package/dist/src/lib/storage/execution.js +300 -0
- package/dist/src/lib/storage/index.js +7 -0
- package/dist/src/lib/storage/jobs.js +81 -0
- package/dist/src/lib/storage/state.js +255 -0
- package/dist/src/lib/utils/api_utils.js +157 -0
- package/dist/src/lib/utils/asset_utils.js +94 -0
- package/dist/src/lib/utils/date_utils.js +52 -0
- package/dist/src/lib/utils/encoding_utils.js +27 -0
- package/dist/src/lib/utils/events.js +4 -0
- package/dist/src/lib/utils/file_utils.js +124 -0
- package/dist/src/lib/utils/id_utils.js +15 -0
- package/dist/src/lib/utils/port_utils.js +32 -0
- package/dist/src/lib/workers/assets/index.js +3 -0
- package/dist/src/lib/workers/assets/loader-executable.js +40 -0
- package/dist/src/lib/workers/assets/loader.js +73 -0
- package/dist/src/lib/workers/assets/spawn.js +55 -0
- package/dist/src/lib/workers/context/execution-context.js +12 -0
- package/dist/src/lib/workers/context/terafoundation-context.js +8 -0
- package/dist/src/lib/workers/execution-controller/execution-analytics.js +188 -0
- package/dist/src/lib/workers/execution-controller/index.js +1024 -0
- package/dist/src/lib/workers/execution-controller/recovery.js +151 -0
- package/dist/src/lib/workers/execution-controller/scheduler.js +390 -0
- package/dist/src/lib/workers/execution-controller/slice-analytics.js +96 -0
- package/dist/src/lib/workers/helpers/job.js +80 -0
- package/dist/src/lib/workers/helpers/op-analytics.js +22 -0
- package/dist/src/lib/workers/helpers/terafoundation.js +34 -0
- package/dist/src/lib/workers/helpers/worker-shutdown.js +169 -0
- package/dist/src/lib/workers/metrics/index.js +108 -0
- package/dist/src/lib/workers/worker/index.js +378 -0
- package/dist/src/lib/workers/worker/slice.js +122 -0
- package/dist/test/config/schemas/system_schema-spec.js +37 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8s-spec.js +316 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sResource-spec.js +795 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-multicluster-spec.js +67 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-spec.js +84 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/utils-spec.js +132 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8s-v2-spec.js +455 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sResource-v2-spec.js +818 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-multicluster-v2-spec.js +67 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-v2-spec.js +84 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/utils-v2-spec.js +320 -0
- package/dist/test/lib/cluster/services/cluster/backends/state-utils-spec.js +37 -0
- package/dist/test/node_master-spec.js +188 -0
- package/dist/test/services/api-spec.js +80 -0
- package/dist/test/services/assets-spec.js +158 -0
- package/dist/test/services/messaging-spec.js +440 -0
- package/dist/test/storage/assets_storage-spec.js +95 -0
- package/dist/test/storage/s3_store-spec.js +138 -0
- package/dist/test/test.config.js +8 -0
- package/dist/test/test.setup.js +6 -0
- package/dist/test/utils/api_utils-spec.js +86 -0
- package/dist/test/utils/asset_utils-spec.js +141 -0
- package/dist/test/utils/elastic_utils-spec.js +25 -0
- package/dist/test/workers/execution-controller/execution-controller-spec.js +371 -0
- package/dist/test/workers/execution-controller/execution-special-test-cases-spec.js +520 -0
- package/dist/test/workers/execution-controller/execution-test-cases-spec.js +338 -0
- package/dist/test/workers/execution-controller/recovery-spec.js +160 -0
- package/dist/test/workers/execution-controller/scheduler-spec.js +249 -0
- package/dist/test/workers/execution-controller/slice-analytics-spec.js +121 -0
- package/dist/test/workers/fixtures/ops/example-op/processor.js +20 -0
- package/dist/test/workers/fixtures/ops/example-op/schema.js +19 -0
- package/dist/test/workers/fixtures/ops/example-reader/fetcher.js +20 -0
- package/dist/test/workers/fixtures/ops/example-reader/schema.js +41 -0
- package/dist/test/workers/fixtures/ops/example-reader/slicer.js +37 -0
- package/dist/test/workers/fixtures/ops/new-op/processor.js +29 -0
- package/dist/test/workers/fixtures/ops/new-op/schema.js +18 -0
- package/dist/test/workers/fixtures/ops/new-reader/fetcher.js +19 -0
- package/dist/test/workers/fixtures/ops/new-reader/schema.js +23 -0
- package/dist/test/workers/fixtures/ops/new-reader/slicer.js +13 -0
- package/dist/test/workers/helpers/configs.js +130 -0
- package/dist/test/workers/helpers/execution-controller-helper.js +49 -0
- package/dist/test/workers/helpers/index.js +5 -0
- package/dist/test/workers/helpers/test-context.js +210 -0
- package/dist/test/workers/helpers/zip-directory.js +25 -0
- package/dist/test/workers/worker/slice-spec.js +333 -0
- package/dist/test/workers/worker/worker-spec.js +356 -0
- package/package.json +94 -94
- package/service.js +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { pRaceWithTimeout, logError, Queue } from '@terascope/utils';
|
|
2
|
+
import { makeLogger } from '../helpers/terafoundation.js';
|
|
3
|
+
export class RecoveryModule {
|
|
4
|
+
logger;
|
|
5
|
+
events;
|
|
6
|
+
slicersToRecover;
|
|
7
|
+
recoveryQueue;
|
|
8
|
+
recoverComplete = true;
|
|
9
|
+
isShutdown = false;
|
|
10
|
+
autorecover;
|
|
11
|
+
cleanupType;
|
|
12
|
+
slicerID = 0;
|
|
13
|
+
retryState = new Map();
|
|
14
|
+
recoverExecution;
|
|
15
|
+
exId;
|
|
16
|
+
stateStore;
|
|
17
|
+
timeout;
|
|
18
|
+
constructor(context, executionContext) {
|
|
19
|
+
const { exId } = executionContext;
|
|
20
|
+
this.events = context.apis.foundation.getSystemEvents();
|
|
21
|
+
this.slicersToRecover = executionContext.config.slicers;
|
|
22
|
+
this.recoveryQueue = new Queue();
|
|
23
|
+
this.cleanupType = executionContext.config.recovered_slice_type;
|
|
24
|
+
this.recoverExecution = executionContext.config.recovered_execution;
|
|
25
|
+
this.autorecover = Boolean(executionContext.config.autorecover);
|
|
26
|
+
this.exId = exId;
|
|
27
|
+
this.logger = makeLogger(context, 'execution_recovery');
|
|
28
|
+
this.timeout = context.sysconfig.teraslice.shutdown_timeout;
|
|
29
|
+
}
|
|
30
|
+
initialize(stateStore) {
|
|
31
|
+
this.stateStore = stateStore;
|
|
32
|
+
this.events.on('slice:success', this._sliceComplete.bind(this));
|
|
33
|
+
this.recoverComplete = false;
|
|
34
|
+
// once we have fully recovered, clean up event listeners
|
|
35
|
+
this.events.once('execution:recovery:complete', () => {
|
|
36
|
+
this.events.removeListener('slice:success', this._sliceComplete.bind(this));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// TODO: this is wrong
|
|
40
|
+
_sliceComplete(sliceData) {
|
|
41
|
+
this.retryState.set(sliceData.slice.slice_id, false);
|
|
42
|
+
}
|
|
43
|
+
_setId(slice) {
|
|
44
|
+
this.retryState.set(slice.slice_id, true);
|
|
45
|
+
}
|
|
46
|
+
async _processIncompleteSlices(slicerID) {
|
|
47
|
+
const slices = await this.stateStore.recoverSlices(this.recoverExecution, slicerID, this.cleanupType);
|
|
48
|
+
for (const slice of slices) {
|
|
49
|
+
this._setId(slice);
|
|
50
|
+
this.recoveryQueue.enqueue(slice);
|
|
51
|
+
}
|
|
52
|
+
return slices.length;
|
|
53
|
+
}
|
|
54
|
+
_recoveryBatchCompleted() {
|
|
55
|
+
return [...this.retryState.values()].every((v) => v === false);
|
|
56
|
+
}
|
|
57
|
+
_retryState() {
|
|
58
|
+
return Object.fromEntries(this.retryState);
|
|
59
|
+
}
|
|
60
|
+
// TODO: refactor to use pwhile
|
|
61
|
+
async _waitForRecoveryBatchCompletion() {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const checkingBatch = setInterval(() => {
|
|
64
|
+
if (this._recoveryBatchCompleted()) {
|
|
65
|
+
clearInterval(checkingBatch);
|
|
66
|
+
this.events.emit('execution:recovery:complete');
|
|
67
|
+
this.recoverComplete = true;
|
|
68
|
+
resolve(true);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (this.isShutdown) {
|
|
72
|
+
clearInterval(checkingBatch);
|
|
73
|
+
this.recoverComplete = false;
|
|
74
|
+
resolve(true);
|
|
75
|
+
}
|
|
76
|
+
}, 100);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async handle() {
|
|
80
|
+
if (this.recoverComplete) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
const recoveredSlicesCount = await this._processIncompleteSlices(this.slicerID);
|
|
84
|
+
if (recoveredSlicesCount === 0) {
|
|
85
|
+
this.slicerID++;
|
|
86
|
+
// all slicers have been recovered
|
|
87
|
+
if (this.slicerID > this.slicersToRecover) {
|
|
88
|
+
this.logger.warn(`recovered data for execution: ${this.exId} has successfully been enqueued`);
|
|
89
|
+
await this._waitForRecoveryBatchCompletion();
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
await this._waitForRecoveryBatchCompletion();
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
getSlice() {
|
|
97
|
+
if (this.recoveryQueue.size() > 0) {
|
|
98
|
+
return this.recoveryQueue.dequeue();
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
getSlices(max = 1) {
|
|
103
|
+
const count = max > this.sliceCount() ? this.sliceCount() : max;
|
|
104
|
+
const slices = [];
|
|
105
|
+
for (let i = 0; i < count; i++) {
|
|
106
|
+
const slice = this.recoveryQueue.dequeue();
|
|
107
|
+
if (!slice)
|
|
108
|
+
return slices;
|
|
109
|
+
slices.push(slice);
|
|
110
|
+
}
|
|
111
|
+
return slices;
|
|
112
|
+
}
|
|
113
|
+
sliceCount() {
|
|
114
|
+
return this.recoveryQueue.size();
|
|
115
|
+
}
|
|
116
|
+
async shutdown() {
|
|
117
|
+
let checkInterval;
|
|
118
|
+
try {
|
|
119
|
+
await pRaceWithTimeout(new Promise((resolve) => {
|
|
120
|
+
checkInterval = setInterval(() => {
|
|
121
|
+
if (this.recoverComplete) {
|
|
122
|
+
resolve(true);
|
|
123
|
+
}
|
|
124
|
+
}, 100);
|
|
125
|
+
}), this.timeout, (err) => {
|
|
126
|
+
logError(this.logger, err);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
this.isShutdown = true;
|
|
131
|
+
clearInterval(checkInterval);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
recoveryComplete() {
|
|
135
|
+
return this.recoverComplete;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Whether or not the execution will continue to process
|
|
139
|
+
* slices after recovering.
|
|
140
|
+
*
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
exitAfterComplete() {
|
|
144
|
+
if (this.autorecover)
|
|
145
|
+
return false;
|
|
146
|
+
if (!this.cleanupType)
|
|
147
|
+
return false;
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { Queue, noop, pDelay, get, toString, makeISODate, logError, pWhile } from '@terascope/utils';
|
|
2
|
+
import { RecoveryModule } from './recovery.js';
|
|
3
|
+
import { makeLogger } from '../helpers/terafoundation.js';
|
|
4
|
+
export class Scheduler {
|
|
5
|
+
context;
|
|
6
|
+
logger;
|
|
7
|
+
events;
|
|
8
|
+
executionContext;
|
|
9
|
+
exId;
|
|
10
|
+
recoverFromExId;
|
|
11
|
+
recoverExecution;
|
|
12
|
+
recovering;
|
|
13
|
+
autorecover;
|
|
14
|
+
_creating = 0;
|
|
15
|
+
ready = false;
|
|
16
|
+
paused = true;
|
|
17
|
+
stopped = false;
|
|
18
|
+
slicersDone = false;
|
|
19
|
+
slicersFailed = false;
|
|
20
|
+
queue = new Queue();
|
|
21
|
+
recover;
|
|
22
|
+
stateStorage;
|
|
23
|
+
executionStorage;
|
|
24
|
+
_resolveRun = noop;
|
|
25
|
+
_processCleanup = noop;
|
|
26
|
+
startingPoints = [];
|
|
27
|
+
constructor(context, executionContext) {
|
|
28
|
+
this.context = context;
|
|
29
|
+
this.logger = makeLogger(context, 'execution_scheduler');
|
|
30
|
+
this.events = context.apis.foundation.getSystemEvents();
|
|
31
|
+
this.executionContext = executionContext;
|
|
32
|
+
this.exId = executionContext.exId;
|
|
33
|
+
this.recover = new RecoveryModule(context, executionContext);
|
|
34
|
+
const recoverFromExId = get(executionContext.config, 'recovered_execution');
|
|
35
|
+
const slicerCanRecover = executionContext.slicer().isRecoverable();
|
|
36
|
+
if (recoverFromExId && !slicerCanRecover) {
|
|
37
|
+
throw new Error('Slicer is not recoverable');
|
|
38
|
+
}
|
|
39
|
+
this.recoverFromExId = recoverFromExId;
|
|
40
|
+
this.recoverExecution = Boolean(recoverFromExId && slicerCanRecover);
|
|
41
|
+
this.recovering = Boolean(this.recoverExecution);
|
|
42
|
+
this.autorecover = Boolean(executionContext.config.autorecover);
|
|
43
|
+
this._processSlicers();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the recovery instance or the execution context,
|
|
47
|
+
* if recovery is initialized, the execution context will not be
|
|
48
|
+
* initialized until the execution if finished and the cleanup
|
|
49
|
+
* type is set.
|
|
50
|
+
*/
|
|
51
|
+
async initialize(stateStorage, executionStorage) {
|
|
52
|
+
this.stateStorage = stateStorage;
|
|
53
|
+
this.executionStorage = executionStorage;
|
|
54
|
+
if (this.recoverExecution) {
|
|
55
|
+
await this._initializeRecovery();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await this._initializeExecution();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Run the execution, this will block until complete (or failed)
|
|
62
|
+
*/
|
|
63
|
+
async run() {
|
|
64
|
+
if (this.recoverExecution) {
|
|
65
|
+
await this.executionStorage.setStatus(this.exId, 'recovering');
|
|
66
|
+
this.logger.info(`execution: ${this.exId} is starting in recovery mode`);
|
|
67
|
+
this.ready = true;
|
|
68
|
+
this.start();
|
|
69
|
+
await this._waitForRecovery();
|
|
70
|
+
await this._recoveryComplete();
|
|
71
|
+
if (this.recover.exitAfterComplete()) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await this._initializeExecution();
|
|
75
|
+
}
|
|
76
|
+
await this.executionStorage.setStatus(this.exId, 'running');
|
|
77
|
+
this.ready = true;
|
|
78
|
+
const promise = new Promise((resolve) => {
|
|
79
|
+
const handler = (err) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
this.slicersFailed = true;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.slicersDone = true;
|
|
85
|
+
}
|
|
86
|
+
this._resolveRun();
|
|
87
|
+
this._resolveRun = noop;
|
|
88
|
+
};
|
|
89
|
+
this._resolveRun = () => {
|
|
90
|
+
this.events.removeListener('slicers:finished', handler);
|
|
91
|
+
resolve(true);
|
|
92
|
+
};
|
|
93
|
+
this.events.once('slicers:finished', handler);
|
|
94
|
+
});
|
|
95
|
+
this.start();
|
|
96
|
+
this.logger.trace('running the scheduler');
|
|
97
|
+
await promise;
|
|
98
|
+
const n = this.pendingSlices + this.pendingSlicerCount;
|
|
99
|
+
this.logger.debug(`execution ${this.exId} is finished scheduling, ${n} remaining slices in the queue`);
|
|
100
|
+
await pWhile(async () => {
|
|
101
|
+
if (!this._creating) {
|
|
102
|
+
this.logger.debug('done creating remaining slices');
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
this.logger.debug(`waiting for ${this._creating} remaining slices to be created...`);
|
|
106
|
+
await pDelay(100);
|
|
107
|
+
return false;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
start() {
|
|
111
|
+
this.paused = false;
|
|
112
|
+
}
|
|
113
|
+
async stop() {
|
|
114
|
+
if (this.stopped)
|
|
115
|
+
return;
|
|
116
|
+
this.logger.debug('stopping scheduler...');
|
|
117
|
+
this.stopped = true;
|
|
118
|
+
const drain = this._drainPendingSlices(false);
|
|
119
|
+
this._processCleanup();
|
|
120
|
+
this._processCleanup = noop;
|
|
121
|
+
this._resolveRun();
|
|
122
|
+
this._resolveRun = noop;
|
|
123
|
+
await drain;
|
|
124
|
+
}
|
|
125
|
+
pause() {
|
|
126
|
+
this.paused = true;
|
|
127
|
+
}
|
|
128
|
+
get maxQueueLength() {
|
|
129
|
+
return this.executionContext.slicer().maxQueueLength();
|
|
130
|
+
}
|
|
131
|
+
get queueLength() {
|
|
132
|
+
return this.queue.size();
|
|
133
|
+
}
|
|
134
|
+
get isQueueFull() {
|
|
135
|
+
const maxLength = this.maxQueueLength;
|
|
136
|
+
return this.pendingSlices + this.pendingSlicerCount > maxLength;
|
|
137
|
+
}
|
|
138
|
+
get pendingSlicerCount() {
|
|
139
|
+
if (!this.ready)
|
|
140
|
+
return 0;
|
|
141
|
+
if (this.recovering && this.recover) {
|
|
142
|
+
return this.recover.sliceCount();
|
|
143
|
+
}
|
|
144
|
+
return this.executionContext.slicer().sliceCount();
|
|
145
|
+
}
|
|
146
|
+
get pendingSlices() {
|
|
147
|
+
return this.queueLength + this._creating;
|
|
148
|
+
}
|
|
149
|
+
get queueRemainder() {
|
|
150
|
+
const remainder = this.maxQueueLength - this.pendingSlices;
|
|
151
|
+
return remainder > 0 ? remainder : 0;
|
|
152
|
+
}
|
|
153
|
+
get isFinished() {
|
|
154
|
+
const isDone = this.slicersDone || this.slicersFailed || this.stopped;
|
|
155
|
+
return isDone && !this.pendingSlices;
|
|
156
|
+
}
|
|
157
|
+
canAllocateSlice() {
|
|
158
|
+
return this.ready && !this.paused && !this.isQueueFull;
|
|
159
|
+
}
|
|
160
|
+
canComplete() {
|
|
161
|
+
const { lifecycle } = this.executionContext.config;
|
|
162
|
+
return this.ready && lifecycle === 'once' && !this.recovering;
|
|
163
|
+
}
|
|
164
|
+
isRecovering() {
|
|
165
|
+
return this.ready && this.recovering;
|
|
166
|
+
}
|
|
167
|
+
async shutdown() {
|
|
168
|
+
await this.stop();
|
|
169
|
+
if (this.recover) {
|
|
170
|
+
try {
|
|
171
|
+
await this.recover.shutdown();
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
logError(this.logger, err, 'failed to shutdown recovery');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
this.queue.each((slice) => {
|
|
178
|
+
this.queue.remove(slice.slice_id, 'slice_id');
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
getSlices(limit = 1) {
|
|
182
|
+
if (this.queue.size() === 0)
|
|
183
|
+
return [];
|
|
184
|
+
if (limit < 1)
|
|
185
|
+
return [];
|
|
186
|
+
const slices = [];
|
|
187
|
+
for (let i = 0; i < limit; i += 1) {
|
|
188
|
+
const slice = this.queue.dequeue();
|
|
189
|
+
if (slice == null)
|
|
190
|
+
break;
|
|
191
|
+
slices.push(slice);
|
|
192
|
+
}
|
|
193
|
+
if (slices.length > 0) {
|
|
194
|
+
this.events.emit('slicers:queued', this.queueLength);
|
|
195
|
+
}
|
|
196
|
+
return slices;
|
|
197
|
+
}
|
|
198
|
+
enqueueSlice(slice, addToStart) {
|
|
199
|
+
return this.enqueueSlices([slice], addToStart);
|
|
200
|
+
}
|
|
201
|
+
enqueueSlices(slices, addToStart = false) {
|
|
202
|
+
if (this.stopped)
|
|
203
|
+
return;
|
|
204
|
+
slices.forEach((slice) => {
|
|
205
|
+
if (this.queue.exists('slice_id', slice.slice_id)) {
|
|
206
|
+
this.logger.warn(`slice ${slice.slice_id} has already been enqueued`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this.logger.trace('enqueuing slice', slice);
|
|
210
|
+
if (addToStart) {
|
|
211
|
+
this.queue.unshift(slice);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this.queue.enqueue(slice);
|
|
215
|
+
this.executionContext.onSliceEnqueued(slice);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
this.events.emit('slicers:queued', this.queueLength);
|
|
219
|
+
}
|
|
220
|
+
_processSlicers() {
|
|
221
|
+
const { events, logger, exId } = this;
|
|
222
|
+
let _handling = false;
|
|
223
|
+
let _finished = false;
|
|
224
|
+
let createInterval;
|
|
225
|
+
let handleInterval;
|
|
226
|
+
const resetCleanup = () => {
|
|
227
|
+
this._processCleanup = noop;
|
|
228
|
+
};
|
|
229
|
+
const cleanup = () => {
|
|
230
|
+
clearInterval(createInterval);
|
|
231
|
+
createInterval = undefined;
|
|
232
|
+
clearInterval(handleInterval);
|
|
233
|
+
handleInterval = undefined;
|
|
234
|
+
resetCleanup();
|
|
235
|
+
};
|
|
236
|
+
const drain = () => {
|
|
237
|
+
const n = this.pendingSlicerCount;
|
|
238
|
+
if (n) {
|
|
239
|
+
logger.debug(`draining the remaining ${n} pending slices from the slicer`);
|
|
240
|
+
}
|
|
241
|
+
return this._drainPendingSlices(false);
|
|
242
|
+
};
|
|
243
|
+
const onSlicerFinished = async () => {
|
|
244
|
+
cleanup();
|
|
245
|
+
logger.info(`all slicers for execution: ${exId} have been completed`);
|
|
246
|
+
await drain();
|
|
247
|
+
events.emit('slicers:finished');
|
|
248
|
+
};
|
|
249
|
+
const onSlicerFailure = async (err) => {
|
|
250
|
+
cleanup();
|
|
251
|
+
logger.warn('slicer failed', toString(err));
|
|
252
|
+
await drain();
|
|
253
|
+
// before removing listeners make sure we've received all of the events
|
|
254
|
+
await pDelay(100);
|
|
255
|
+
events.emit('slicers:finished', err);
|
|
256
|
+
};
|
|
257
|
+
const makeSlices = async () => {
|
|
258
|
+
try {
|
|
259
|
+
if (this.recovering && this.recover) {
|
|
260
|
+
_finished = await this.recover.handle();
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
_finished = await this.executionContext.slicer().handle();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
await onSlicerFailure(err);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (!_finished) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (!this.recovering) {
|
|
274
|
+
clearInterval(handleInterval);
|
|
275
|
+
handleInterval = undefined;
|
|
276
|
+
}
|
|
277
|
+
if (this.canComplete()) {
|
|
278
|
+
await onSlicerFinished();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
handleInterval = setInterval(() => {
|
|
282
|
+
if (!this.canAllocateSlice())
|
|
283
|
+
return;
|
|
284
|
+
if (_handling)
|
|
285
|
+
return;
|
|
286
|
+
_handling = true;
|
|
287
|
+
makeSlices()
|
|
288
|
+
.then(() => {
|
|
289
|
+
_handling = false;
|
|
290
|
+
})
|
|
291
|
+
.catch((err) => {
|
|
292
|
+
_handling = false;
|
|
293
|
+
logError(this.logger, err, 'failure to run slicers');
|
|
294
|
+
});
|
|
295
|
+
}, 3);
|
|
296
|
+
createInterval = setInterval(() => {
|
|
297
|
+
if (!this.pendingSlicerCount)
|
|
298
|
+
return;
|
|
299
|
+
this._drainPendingSlices().catch(onSlicerFailure);
|
|
300
|
+
}, 5);
|
|
301
|
+
this._processCleanup = cleanup;
|
|
302
|
+
}
|
|
303
|
+
async _drainPendingSlices(once = true) {
|
|
304
|
+
const slices = this._getPendingSlices();
|
|
305
|
+
if (!slices.length)
|
|
306
|
+
return;
|
|
307
|
+
await this._createSlices(slices);
|
|
308
|
+
if (once)
|
|
309
|
+
return;
|
|
310
|
+
await this._drainPendingSlices();
|
|
311
|
+
}
|
|
312
|
+
_getPendingSlices() {
|
|
313
|
+
if (!this.ready)
|
|
314
|
+
return [];
|
|
315
|
+
if (this.recovering && this.recover) {
|
|
316
|
+
return this.recover.getSlices(this.queueRemainder);
|
|
317
|
+
}
|
|
318
|
+
return this.executionContext.slicer().getSlices(this.queueRemainder);
|
|
319
|
+
}
|
|
320
|
+
async _createSlices(allSlices) {
|
|
321
|
+
// filter out anything that doesn't need to be created
|
|
322
|
+
const slices = [];
|
|
323
|
+
for (const slice of allSlices) {
|
|
324
|
+
// In the case of recovery slices have already been
|
|
325
|
+
// created, so its important to skip this step
|
|
326
|
+
if (slice._created) {
|
|
327
|
+
this.enqueueSlice(slice);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
slice._created = makeISODate();
|
|
331
|
+
slices.push(slice);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!slices.length)
|
|
335
|
+
return;
|
|
336
|
+
this._creating += slices.length;
|
|
337
|
+
try {
|
|
338
|
+
const count = await this.stateStorage.createSlices(this.exId, slices);
|
|
339
|
+
this.enqueueSlices(slices);
|
|
340
|
+
this._creating -= count;
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
const { lifecycle } = this.executionContext.config;
|
|
344
|
+
if (lifecycle === 'once') {
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
logError(this.logger, err, 'failure creating slices');
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async _initializeRecovery() {
|
|
353
|
+
await this.recover.initialize(this.stateStorage);
|
|
354
|
+
const { slicers: prevSlicers } = await this.executionStorage.get(this.recoverFromExId);
|
|
355
|
+
this.events.emit('slicers:registered', prevSlicers);
|
|
356
|
+
}
|
|
357
|
+
async _initializeExecution() {
|
|
358
|
+
await this.executionContext.initialize(this.startingPoints);
|
|
359
|
+
const slicers = this.executionContext.slicer().slicers();
|
|
360
|
+
this.events.emit('slicers:registered', slicers);
|
|
361
|
+
}
|
|
362
|
+
async _waitForRecovery() {
|
|
363
|
+
if (!this.recoverExecution)
|
|
364
|
+
return;
|
|
365
|
+
if (!this.recover.recoveryComplete()) {
|
|
366
|
+
await new Promise((resolve) => {
|
|
367
|
+
this.events.once('execution:recovery:complete', () => {
|
|
368
|
+
resolve(true);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async _recoveryComplete() {
|
|
374
|
+
this.recovering = false;
|
|
375
|
+
this.ready = false;
|
|
376
|
+
if (this.recover.exitAfterComplete()) {
|
|
377
|
+
this.logger.warn('execution recovery has been marked as completed');
|
|
378
|
+
this.slicersDone = true;
|
|
379
|
+
this._processCleanup();
|
|
380
|
+
this._processCleanup = noop;
|
|
381
|
+
this._resolveRun();
|
|
382
|
+
this._resolveRun = noop;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const { slicers: prevSlicers } = await this.executionStorage.get(this.recoverFromExId);
|
|
386
|
+
this.startingPoints = await this.stateStorage.getStartingPoints(this.recoverFromExId, prevSlicers);
|
|
387
|
+
this.logger.info(`execution: ${this.exId} finished its recovery`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { has, isInteger, toNumber } from '@terascope/utils';
|
|
2
|
+
import { makeLogger } from '../helpers/terafoundation.js';
|
|
3
|
+
// TODO: more types
|
|
4
|
+
export class SliceAnalytics {
|
|
5
|
+
logger;
|
|
6
|
+
events;
|
|
7
|
+
sliceAnalytics;
|
|
8
|
+
operations;
|
|
9
|
+
constructor(context, executionContext) {
|
|
10
|
+
this.logger = makeLogger(context, 'slice_analytics');
|
|
11
|
+
this.events = context.apis.foundation.getSystemEvents();
|
|
12
|
+
const { operations } = executionContext.config;
|
|
13
|
+
this.operations = operations;
|
|
14
|
+
// create a container to hold all the slice analytics for this execution
|
|
15
|
+
this.sliceAnalytics = {
|
|
16
|
+
time: [],
|
|
17
|
+
size: [],
|
|
18
|
+
memory: []
|
|
19
|
+
};
|
|
20
|
+
const opsLength = this.operations.length;
|
|
21
|
+
for (let i = 0; i < opsLength; i += 1) {
|
|
22
|
+
this.sliceAnalytics.time.push({
|
|
23
|
+
min: 0,
|
|
24
|
+
max: 0,
|
|
25
|
+
sum: 0,
|
|
26
|
+
total: 0,
|
|
27
|
+
average: 0
|
|
28
|
+
});
|
|
29
|
+
this.sliceAnalytics.size.push({
|
|
30
|
+
min: 0,
|
|
31
|
+
max: 0,
|
|
32
|
+
sum: 0,
|
|
33
|
+
total: 0,
|
|
34
|
+
average: 0
|
|
35
|
+
});
|
|
36
|
+
this.sliceAnalytics.memory.push({
|
|
37
|
+
min: 0,
|
|
38
|
+
max: 0,
|
|
39
|
+
sum: 0,
|
|
40
|
+
total: 0,
|
|
41
|
+
average: 0
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
this.events.on('slice:success', this.onSliceSuccess.bind(this));
|
|
45
|
+
}
|
|
46
|
+
addStat(input, stat) {
|
|
47
|
+
if (!has(input, stat) || !has(this.sliceAnalytics, stat)) {
|
|
48
|
+
this.logger.warn(`unsupported stat "${stat}"`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (let i = 0; i < this.operations.length; i += 1) {
|
|
52
|
+
const val = input[stat][i];
|
|
53
|
+
if (!isInteger(val)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.sliceAnalytics[stat][i].sum += val;
|
|
57
|
+
this.sliceAnalytics[stat][i].total += 1;
|
|
58
|
+
const { min, max, total, sum } = this.sliceAnalytics[stat][i];
|
|
59
|
+
this.sliceAnalytics[stat][i].min = min !== 0 ? Math.min(val, min) : val;
|
|
60
|
+
this.sliceAnalytics[stat][i].max = max !== 0 ? Math.max(val, max) : val;
|
|
61
|
+
this.sliceAnalytics[stat][i].average = toNumber((sum / total).toFixed(2));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
addStats(data) {
|
|
65
|
+
this.addStat(data, 'time');
|
|
66
|
+
this.addStat(data, 'memory');
|
|
67
|
+
this.addStat(data, 'size');
|
|
68
|
+
}
|
|
69
|
+
analyzeStats() {
|
|
70
|
+
this.logger.info('calculating statistics');
|
|
71
|
+
for (let i = 0; i < this.operations.length; i += 1) {
|
|
72
|
+
const name = this.operations[i]._op;
|
|
73
|
+
const time = this.sliceAnalytics.time[i];
|
|
74
|
+
const size = this.sliceAnalytics.size[i];
|
|
75
|
+
const memory = this.sliceAnalytics.memory[i];
|
|
76
|
+
this.logger.info(`
|
|
77
|
+
operation ${name}
|
|
78
|
+
average completion time of: ${time.average} ms, min: ${time.min} ms, and max: ${time.max} ms
|
|
79
|
+
average size: ${size.average}, min: ${size.min}, and max: ${size.max}
|
|
80
|
+
average memory: ${memory.average}, min: ${memory.min}, and max: ${memory.max}
|
|
81
|
+
`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
getStats() {
|
|
85
|
+
return this.sliceAnalytics;
|
|
86
|
+
}
|
|
87
|
+
onSliceSuccess({ analytics }) {
|
|
88
|
+
if (analytics) {
|
|
89
|
+
this.addStats(analytics);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async shutdown() {
|
|
93
|
+
this.events.removeListener('slice:success', this.onSliceSuccess.bind(this));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=slice-analytics.js.map
|