@nsshunt/stsappframework 3.1.159 → 3.1.161

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.
Files changed (44) hide show
  1. package/dist/testertesting/app.js +194 -0
  2. package/dist/testertesting/app.js.map +1 -0
  3. package/dist/testertesting/commonTypes.js +16 -0
  4. package/dist/testertesting/commonTypes.js.map +1 -0
  5. package/dist/testertesting/requestResponseHelper.js +83 -0
  6. package/dist/testertesting/requestResponseHelper.js.map +1 -0
  7. package/dist/testertesting/stsTestWorkerDefinitions.js +34 -0
  8. package/dist/testertesting/stsTestWorkerDefinitions.js.map +1 -0
  9. package/dist/testertesting/workerInstance.js +131 -0
  10. package/dist/testertesting/workerInstance.js.map +1 -0
  11. package/dist/testertesting/workerManager.js +417 -0
  12. package/dist/testertesting/workerManager.js.map +1 -0
  13. package/dist/testertesting/workerPrimaryTestRunner01.js +62 -0
  14. package/dist/testertesting/workerPrimaryTestRunner01.js.map +1 -0
  15. package/dist/testertesting/workerWorkerTestRunner01.js +146 -0
  16. package/dist/testertesting/workerWorkerTestRunner01.js.map +1 -0
  17. package/dist/webworkertesting/app.js +2 -0
  18. package/dist/webworkertesting/app.js.map +1 -1
  19. package/package.json +2 -2
  20. package/src/testertesting/app.ts +238 -0
  21. package/src/testertesting/commonTypes.ts +50 -0
  22. package/src/testertesting/requestResponseHelper.ts +95 -0
  23. package/src/testertesting/stsTestWorkerDefinitions.ts +150 -0
  24. package/src/testertesting/workerInstance.ts +154 -0
  25. package/src/testertesting/workerManager.ts +478 -0
  26. package/src/testertesting/workerPrimaryTestRunner01.ts +81 -0
  27. package/src/testertesting/workerWorkerTestRunner01.ts +184 -0
  28. package/src/webworkertesting/app.ts +4 -0
  29. package/types/testertesting/app.d.ts +2 -0
  30. package/types/testertesting/app.d.ts.map +1 -0
  31. package/types/testertesting/commonTypes.d.ts +44 -0
  32. package/types/testertesting/commonTypes.d.ts.map +1 -0
  33. package/types/testertesting/requestResponseHelper.d.ts +8 -0
  34. package/types/testertesting/requestResponseHelper.d.ts.map +1 -0
  35. package/types/testertesting/stsTestWorkerDefinitions.d.ts +124 -0
  36. package/types/testertesting/stsTestWorkerDefinitions.d.ts.map +1 -0
  37. package/types/testertesting/workerInstance.d.ts +18 -0
  38. package/types/testertesting/workerInstance.d.ts.map +1 -0
  39. package/types/testertesting/workerManager.d.ts +14 -0
  40. package/types/testertesting/workerManager.d.ts.map +1 -0
  41. package/types/testertesting/workerPrimaryTestRunner01.d.ts +8 -0
  42. package/types/testertesting/workerPrimaryTestRunner01.d.ts.map +1 -0
  43. package/types/testertesting/workerWorkerTestRunner01.d.ts +16 -0
  44. package/types/testertesting/workerWorkerTestRunner01.d.ts.map +1 -0
@@ -0,0 +1,154 @@
1
+ /* eslint @typescript-eslint/no-explicit-any: 0, @typescript-eslint/no-unused-vars: 0 */ // --> OFF
2
+ import { MessagePort } from 'worker_threads';
3
+
4
+ import { IIWMessagePayload, eIWMessageCommands, IIWMessagePayloadContentBase } from './commonTypes'
5
+
6
+ import type { ISTSAgentWorkerMessagePort, IRunner, ITestRunnerTelemetryPayload } from './stsTestWorkerDefinitions'
7
+ import { IRunnerState } from './stsTestWorkerDefinitions'
8
+
9
+ import { RequestResponseHelper } from './requestResponseHelper'
10
+
11
+ import chalk from 'chalk';
12
+ // Force chalk level
13
+ chalk.level = 3;
14
+
15
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
16
+ export interface IWorkerInstanceOptions {
17
+
18
+ }
19
+
20
+ export abstract class WorkerInstance {
21
+ #collectorCollectorPort: MessagePort | null = null;
22
+ #requestResponseHelper: RequestResponseHelper | null = null;
23
+ #runners: Record<string, IRunner> = { };
24
+ #options: IWorkerInstanceOptions | null = null;
25
+
26
+ #debug = (message: string) => {
27
+ console.log(chalk.green(`pid: [${process.pid}] WorkerInstance::${message}`));
28
+ }
29
+
30
+
31
+ constructor() {
32
+ this.#debug(`constructor`)
33
+ }
34
+
35
+ GetRandomInt = (max: number) => {
36
+ this.#debug(`GetRandomInt`)
37
+ return Math.floor(Math.random() * Math.floor(max));
38
+ };
39
+
40
+
41
+ StartWork = async (runner: IRunner): Promise<void> => {
42
+ this.#debug(`StartWork`)
43
+ return;
44
+ }
45
+
46
+ PostTelemetry = (runner: IRunner) => {
47
+ //debug(`WorkerInstance::PostTelemetry`)
48
+ if (this.#collectorCollectorPort) {
49
+ const message: IIWMessagePayload = {
50
+ command: eIWMessageCommands.InstrumentTelemetry,
51
+ payload: {
52
+ runner
53
+ } as ITestRunnerTelemetryPayload
54
+ }
55
+ this.#collectorCollectorPort.postMessage(message);
56
+ }
57
+ }
58
+
59
+ get RequestResponseHelper(): RequestResponseHelper | null {
60
+ return this.#requestResponseHelper;
61
+ }
62
+
63
+ get CollectorCollectorPort(): MessagePort | null {
64
+ return this.#collectorCollectorPort;
65
+ }
66
+
67
+ get Options(): IWorkerInstanceOptions | null {
68
+ return this.#options;
69
+ }
70
+
71
+ #SetMessagePort = (workerMessagePort: ISTSAgentWorkerMessagePort) => {
72
+ this.#debug(`SetMessagePort`)
73
+ this.#collectorCollectorPort = workerMessagePort.port as MessagePort;
74
+
75
+ // NodeJS version
76
+ this.#collectorCollectorPort.on('message', (data: any) => {
77
+ this.#debug(`collectorCollectorPort onmessage: ${data.data}`);
78
+ });
79
+
80
+ // Browser Version
81
+ /*
82
+ this.#collectorCollectorPort.onmessage = function(data: MessageEvent) {
83
+ console.log(`collectorCollectorPort onmessage: ${data.data}`);
84
+ }
85
+ */
86
+
87
+ this.#requestResponseHelper = new RequestResponseHelper(this.#collectorCollectorPort);
88
+
89
+ const response: IIWMessagePayload = {
90
+ command: eIWMessageCommands.MessagePortResponse,
91
+ payload: { } as IIWMessagePayloadContentBase
92
+ }
93
+
94
+ this.#collectorCollectorPort.postMessage(response);
95
+ }
96
+
97
+ StartRunner = async (runner: IRunner) => {
98
+ this.#debug(`StartRunner`)
99
+ this.#debug(`StartTests: [${JSON.stringify(runner)}]`);
100
+ runner.state = IRunnerState.running;
101
+ this.StartWork(runner);
102
+ }
103
+
104
+ #AddAsyncRunner = (testRunnerTelemetryPayload: ITestRunnerTelemetryPayload) => {
105
+ this.#debug(`AddAsyncRunner`)
106
+ const { runner } = testRunnerTelemetryPayload;
107
+ this.#runners[runner.id] = runner;
108
+ this.StartRunner(runner);
109
+ }
110
+
111
+ #StopRunners = (testRunnerTelemetryPayload: ITestRunnerTelemetryPayload) => {
112
+ this.#debug(`StopRunners`)
113
+ if (testRunnerTelemetryPayload === null) {
114
+ for (const [, testRunner] of Object.entries(this.#runners)) {
115
+ testRunner.state = IRunnerState.stopped;
116
+ }
117
+ } else {
118
+ const runner: IRunner = this.#runners[testRunnerTelemetryPayload.runner.id];
119
+ if (runner) {
120
+ runner.state = IRunnerState.stopped;
121
+ }
122
+ }
123
+ }
124
+
125
+ // ProcessMessage = async(data: MessageEvent) => { // Browser version
126
+ ProcessMessage = async(data: any) => {
127
+ this.#debug(`ProcessMessage: data: [${JSON.stringify(data)}]`)
128
+ try {
129
+ // const payloadMessage: IIWMessagePayload = data.data as IIWMessagePayload; // browser version
130
+ const payloadMessage: IIWMessagePayload = data as IIWMessagePayload;
131
+ switch (payloadMessage.command) {
132
+ case eIWMessageCommands.MessagePort :
133
+ this.#debug(`ProcessMessage::MessagePort`)
134
+ this.#SetMessagePort(payloadMessage.payload as ISTSAgentWorkerMessagePort);
135
+ this.#options = (payloadMessage.payload as ISTSAgentWorkerMessagePort).options;
136
+ this.#debug(`ProcessMessage::#options: [${JSON.stringify(this.#options)}]`)
137
+ break;
138
+ case eIWMessageCommands.AddAsyncRunner :
139
+ this.#debug(`ProcessMessage::AddAsyncRunner`)
140
+ this.#AddAsyncRunner(payloadMessage.payload as ITestRunnerTelemetryPayload);
141
+ break;
142
+ case eIWMessageCommands.StopAllAsyncRunners :
143
+ this.#debug(`ProcessMessage::StopAllAsyncRunners`)
144
+ this.#StopRunners(payloadMessage.payload as ITestRunnerTelemetryPayload);
145
+ break;
146
+ default :
147
+ this.#debug(`ProcessMessage::default`)
148
+ this.#debug(`Invalid payloadMessage.command: [${payloadMessage.command}] - Ignoring`);
149
+ }
150
+ } catch (error) {
151
+ console.log(error);
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,478 @@
1
+ /* eslint @typescript-eslint/no-explicit-any: 0, @typescript-eslint/no-unused-vars: 0 */ // --> OFF
2
+ //import MyWorker from './sts-worker?worker' // https://vitejs.dev/guide/features.html#web-workers
3
+ import { type IAsyncRunnerContext, IIWMessagePayload, IIWMessagePayloadContentBase,
4
+ type IIWMessageCommand, eIWMessageCommands } from './commonTypes'
5
+
6
+ import { Gauge, InstrumentGaugeTelemetry } from '@nsshunt/stsobservability'
7
+
8
+ import { ModelDelimeter } from '@nsshunt/stsutils'
9
+
10
+ import type { ISTSAgentWorkerMessagePort, IWorkerEx, IRunner, IRunnerEx,
11
+ ITestRunnerTelemetryPayload, IRunnerOptions, IRunnerTelemetry,
12
+ IWorkerManagerOptions, IWorkerFactory } from './stsTestWorkerDefinitions'
13
+
14
+ import { IWorkerState, IRunnerState } from './stsTestWorkerDefinitions'
15
+ import { Sleep } from '@nsshunt/stsutils';
16
+
17
+ import { AgentInstrumentController, PublishInstrumentController } from '@nsshunt/stsobservability'
18
+
19
+ import chalk from 'chalk';
20
+ chalk.level = 3;
21
+
22
+ export class STSWorkerManager {
23
+ //#agentSession: string = null;
24
+ #workersEx: Record<string, IWorkerEx> = { };
25
+ #runner = 0;
26
+ #workerId = 0;
27
+ #options: IWorkerManagerOptions;
28
+ #STSInstrumentController: PublishInstrumentController
29
+
30
+ #app: any
31
+
32
+
33
+ constructor(app: any, options?: IWorkerManagerOptions) {
34
+ this.#app = app;
35
+ if (options) {
36
+ this.#options = options;
37
+ } else {
38
+ this.#options = { } as IWorkerManagerOptions;
39
+ }
40
+
41
+ this.#STSInstrumentController = this.#options.publishInstrumentController;
42
+ }
43
+
44
+ #debug = (message: string) => {
45
+ console.log(chalk.cyan(`pid: [${process.pid}] STSWorkerManager::${message}`));
46
+ }
47
+
48
+ get WorkersEx(): Record<string, IWorkerEx> {
49
+ return this.#workersEx;
50
+ }
51
+
52
+ AddWorker = async (useWorkerFactory?: IWorkerFactory): Promise<IWorkerEx> => {
53
+ let workerFactory: IWorkerFactory;
54
+ if (useWorkerFactory) {
55
+ // Use the supplied workFactory
56
+ workerFactory = useWorkerFactory;
57
+ } else {
58
+ // Use the default workFactory
59
+ workerFactory = this.#options.workerFactory
60
+ }
61
+ const stsWorkerEx: IWorkerEx = {
62
+ id: this.#workerId++, // uuidv4()
63
+ worker: workerFactory.createWorkerThreadWorker(),
64
+ primaryWorker: workerFactory.createPrimaryThreadWorker(this.#app, workerFactory.primaryThreadWorkerOptions),
65
+ state: IWorkerState.starting,
66
+ workerThreadWorkerOptions: workerFactory.workerThreadWorkerOptions,
67
+ primaryThreadWorkerOptions: workerFactory.primaryThreadWorkerOptions,
68
+ runnersEx: { } as Record<string, IRunnerEx>,
69
+ AddRunner: (runnerOptions: IRunnerOptions): IRunnerEx => this.AddRunnerToWorker(stsWorkerEx, runnerOptions),
70
+ StopRunner: (runner: IRunnerEx): Promise<boolean> => this.#StopRunner(stsWorkerEx, runner),
71
+ Stop: async (): Promise<boolean> => this.#StopWorker(stsWorkerEx)
72
+ }
73
+ this.#STSInstrumentController.LogEx(chalk.yellow(`pid: [${process.pid}] Creating new worker: [${stsWorkerEx.id}]`));
74
+ this.#debug(`Adding worker: [${stsWorkerEx.id}]`);
75
+
76
+ /*
77
+ stsWorkerEx.worker.onmessage = function(data: MessageEvent) {
78
+ console.log(data.data);
79
+ }
80
+
81
+ stsWorkerEx.worker.onerror = function(error) {
82
+ console.log(error);
83
+ };
84
+ */
85
+
86
+ const {
87
+ port1, // process message port
88
+ port2 // collector message port
89
+ } = new MessageChannel();
90
+
91
+ const workerPort = port1;
92
+
93
+ this.#debug(`AddWorker::workerThreadWorkerOptions: [${JSON.stringify(stsWorkerEx.workerThreadWorkerOptions)}]`);
94
+
95
+ this.#PostMessageToWorker(stsWorkerEx, eIWMessageCommands.MessagePort, {
96
+ port: port2,
97
+ //applicationStoreState: stateCopy,
98
+ options: { ...stsWorkerEx.workerThreadWorkerOptions }
99
+ } as ISTSAgentWorkerMessagePort, port2);
100
+
101
+ // Process messages received back from the worker
102
+ //workerPort.onmessage = async (data: MessageEvent) => {
103
+ workerPort.on('message', (data: any) => {
104
+ // const publishMessagePayload: IIWMessagePayload = data.data as IIWMessagePayload; browser version
105
+ const publishMessagePayload: IIWMessagePayload = data as IIWMessagePayload;
106
+ switch (publishMessagePayload.command) {
107
+ case eIWMessageCommands.MessagePortResponse :
108
+ this.#debug(`AddWorker::eIWMessageCommands.MessagePortResponse`);
109
+ stsWorkerEx.state = IWorkerState.started;
110
+ break;
111
+ case eIWMessageCommands.InstrumentTelemetry :
112
+ this.#debug(`AddWorker::eIWMessageCommands.InstrumentTelemetry`);
113
+ this.#ProcessTelemetry(stsWorkerEx, publishMessagePayload.payload as ITestRunnerTelemetryPayload);
114
+ break;
115
+ default :
116
+ this.#debug(`AddWorker::default`);
117
+ stsWorkerEx.primaryWorker.ProcessMessageFromWorker(workerPort, publishMessagePayload);
118
+ }
119
+ });
120
+
121
+ this.#workersEx[stsWorkerEx.id] = stsWorkerEx;
122
+
123
+ this.#debug(`Added worker: [${stsWorkerEx.id}]`);
124
+
125
+ return stsWorkerEx;
126
+ }
127
+
128
+ AddRunnerToWorker = (stsWorkerEx: IWorkerEx, runnerOptions: IRunnerOptions): IRunnerEx => {
129
+ const runnerEx: IRunnerEx = this.#CreateAsyncRunner(stsWorkerEx, runnerOptions);
130
+ stsWorkerEx.runnersEx[runnerEx.id] = runnerEx;
131
+ this.#SetRunnerIntoWorker(stsWorkerEx, runnerEx);
132
+ runnerEx.publishInstrumentController.LogEx(chalk.green(`Added runner: [${runnerEx.id}] into worker: [${stsWorkerEx.id}]`));
133
+ return runnerEx;
134
+ }
135
+
136
+ #CreateRunnerCopy(runnerEx: IRunnerEx): IRunner {
137
+ return {
138
+ id: runnerEx.id,
139
+ asyncRunnerContext: { ...runnerEx.asyncRunnerContext },
140
+ options: { ...runnerEx.options },
141
+ state: runnerEx.state,
142
+ instrumentData: { ...runnerEx.instrumentData }
143
+ } as IRunner
144
+ }
145
+
146
+ #SetRunnerIntoWorker = (workerEx: IWorkerEx, runnerEx: IRunnerEx): void => {
147
+ // Now that the worker is setup, send the options
148
+ //@@ wait until worker in running state
149
+ const payload: ITestRunnerTelemetryPayload = {
150
+ runner: this.#CreateRunnerCopy(runnerEx)
151
+ }
152
+ this.#PostMessageToWorker(workerEx, eIWMessageCommands.AddAsyncRunner, payload);
153
+ }
154
+
155
+ #ProcessTelemetry = (workerEx: IWorkerEx, payloadContents: ITestRunnerTelemetryPayload): void => {
156
+ //const store = TelemetryStore();
157
+
158
+ const { runner } = payloadContents;
159
+
160
+ if (workerEx.runnersEx[runner.id]) {
161
+ const runnerEx: IRunnerEx = workerEx.runnersEx[runner.id];
162
+ let update = false;
163
+
164
+ // Copy telemetry
165
+ runnerEx.instrumentData = { ...runner.instrumentData };
166
+
167
+ this.#debug(JSON.stringify(runnerEx.instrumentData));
168
+
169
+ if (runner.instrumentData.message) {
170
+ runnerEx.instrumentData.message = [...runner.instrumentData.message];
171
+ } else {
172
+ runnerEx.instrumentData.message = [ ];
173
+ }
174
+
175
+ if (runner.instrumentData.message) {
176
+ runner.instrumentData.message.forEach((message) => {
177
+ runnerEx.publishInstrumentController.LogEx(message);
178
+ });
179
+ update = true;
180
+ }
181
+
182
+ if (runner.instrumentData.requestCount) {
183
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.REQUEST_COUNT_GAUGE, {
184
+ val: runnerEx.instrumentData.requestCount
185
+ } as InstrumentGaugeTelemetry);
186
+ update = true;
187
+ }
188
+
189
+ if (runner.instrumentData.errorCount) {
190
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.ERROR_COUNT_GAUGE, {
191
+ val: runnerEx.instrumentData.errorCount
192
+ } as InstrumentGaugeTelemetry);
193
+ update = true;
194
+ }
195
+
196
+ if (runner.instrumentData.retryCount) {
197
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.RETRY_COUNT_GAUGE, {
198
+ val: runnerEx.instrumentData.retryCount
199
+ } as InstrumentGaugeTelemetry);
200
+ update = true;
201
+ }
202
+
203
+ if (runner.instrumentData.authenticationCount) {
204
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.AUTHENTICATION_COUNT_GAUGE, {
205
+ val: runnerEx.instrumentData.authenticationCount
206
+ } as InstrumentGaugeTelemetry);
207
+ update = true;
208
+ }
209
+
210
+ if (runner.instrumentData.authenticationErrorCount) {
211
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.AUTHENTICATION_ERROR_COUNT_GAUGE, {
212
+ val: runnerEx.instrumentData.authenticationCount
213
+ } as InstrumentGaugeTelemetry);
214
+ update = true;
215
+ }
216
+
217
+ if (runner.instrumentData.authenticationRetryCount) {
218
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.AUTHENTICATION_RETRY_COUNT_GAUGE, {
219
+ val: runnerEx.instrumentData.authenticationCount
220
+ } as InstrumentGaugeTelemetry);
221
+ update = true;
222
+ }
223
+
224
+ if (runner.instrumentData.coreCount) {
225
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.CORE_COUNT_GAUGE, {
226
+ val: runnerEx.instrumentData.coreCount
227
+ } as InstrumentGaugeTelemetry);
228
+ update = true;
229
+ }
230
+
231
+ if (runner.instrumentData.timer) {
232
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.TIMER_GAUGE, {
233
+ val: runnerEx.instrumentData.timer
234
+ } as InstrumentGaugeTelemetry);
235
+ update = true;
236
+ }
237
+
238
+ if (runner.instrumentData.activeRequestCount) {
239
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.ACTIVE_REQUEST_GAUGE, {
240
+ val: runnerEx.instrumentData.activeRequestCount
241
+ } as InstrumentGaugeTelemetry);
242
+ update = true;
243
+ }
244
+
245
+ if (runner.instrumentData.velocity) {
246
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.VELOCITY_GAUGE, {
247
+ Inc: runnerEx.instrumentData.velocity
248
+ } as InstrumentGaugeTelemetry);
249
+ update = true;
250
+ }
251
+
252
+ if (runner.instrumentData.duration) {
253
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.DURATION_GAUGE, {
254
+ val: runnerEx.instrumentData.duration
255
+ } as InstrumentGaugeTelemetry);
256
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.DURATION_HISTOGRAM_GAUGE, {
257
+ val: runnerEx.instrumentData.duration
258
+ } as InstrumentGaugeTelemetry);
259
+ update = true;
260
+ }
261
+
262
+ if (runner.instrumentData.latency) {
263
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.LATENCY_GAUGE, {
264
+ val: runnerEx.instrumentData.latency
265
+ } as InstrumentGaugeTelemetry);
266
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.LATENCY_HISTOGRAM_GAUGE, {
267
+ val: runnerEx.instrumentData.latency
268
+ } as InstrumentGaugeTelemetry);
269
+ update = true;
270
+ }
271
+
272
+ if (runner.instrumentData.childCount) {
273
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.CHILD_COUNT, {
274
+ val: runnerEx.instrumentData.childCount
275
+ } as InstrumentGaugeTelemetry);
276
+ update = true;
277
+ }
278
+
279
+ if (runner.instrumentData.rx) {
280
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.NETWORK_RX_GAUGE, {
281
+ Inc: runnerEx.instrumentData.rx
282
+ } as InstrumentGaugeTelemetry);
283
+ update = true;
284
+ }
285
+
286
+ if (runner.instrumentData.tx) {
287
+ runnerEx.publishInstrumentController.UpdateInstrument(Gauge.NETWORK_TX_GAUGE, {
288
+ Inc: runnerEx.instrumentData.tx
289
+ } as InstrumentGaugeTelemetry);
290
+ update = true;
291
+ }
292
+
293
+ if (update) {
294
+ //store.Update(workerEx, runnerEx);
295
+ }
296
+ }
297
+ }
298
+
299
+ #CreateAsyncRunner = (workerEx: IWorkerEx, runnerOptions: IRunnerOptions): IRunnerEx => {
300
+ //const applicationStore = ApplicationStore();
301
+ this.#runner++; // The runner number always increases
302
+ this.#STSInstrumentController.LogEx(chalk.yellow(`Creating new async runner: [${this.#runner}]`));
303
+ const asyncRunnerContext: IAsyncRunnerContext = {
304
+ nid: `\
305
+ ${workerEx.workerThreadWorkerOptions.hostName}${ModelDelimeter.COMPONENT_SEPERATOR}${workerEx.workerThreadWorkerOptions.agentId}-${workerEx.workerThreadWorkerOptions.userAgent}\
306
+ ${ModelDelimeter.NID_SEPERATOR}\
307
+ worker${workerEx.id}\
308
+ ${ModelDelimeter.SEPERATOR}\
309
+ ${this.#runner}`,
310
+ id: this.#runner.toString(),
311
+ hostName: (workerEx.workerThreadWorkerOptions.hostName ? workerEx.workerThreadWorkerOptions.hostName : 'host'),
312
+ agentName: `${workerEx.workerThreadWorkerOptions.agentId}-${workerEx.workerThreadWorkerOptions.userAgent}`,
313
+ threadId: `worker${workerEx.id}`,
314
+ asyncRunnerId: this.#runner
315
+ }
316
+ const runnerEx: IRunnerEx = {
317
+ id: this.#runner,
318
+ publishInstrumentController: (this.#STSInstrumentController as AgentInstrumentController).AddPublishInstrumentController(asyncRunnerContext),
319
+ asyncRunnerContext: asyncRunnerContext,
320
+ state: IRunnerState.created,
321
+ options: runnerOptions,
322
+ instrumentData: {
323
+ requestCount: 0,
324
+ errorCount: 0,
325
+ retryCount: 0,
326
+ authenticationCount: 0,
327
+ authenticationErrorCount: 0,
328
+ authenticationRetryCount: 0,
329
+ velocity: 0,
330
+ coreCount: 0,
331
+ timer: 0,
332
+ duration: 0,
333
+ latency: 0,
334
+ activeRequestCount: 0,
335
+ message: [ ],
336
+ childCount: 0,
337
+ rx: 0,
338
+ tx: 0
339
+ } as IRunnerTelemetry,
340
+ Stop: async (): Promise<boolean> => this.#StopRunner(workerEx, runnerEx)
341
+ }
342
+ return runnerEx;
343
+ }
344
+
345
+
346
+ #PostMessageToWorker = (workerEx: IWorkerEx, command: IIWMessageCommand, payload: IIWMessagePayloadContentBase | null, transferObject?: any) => {
347
+ if (transferObject) {
348
+ this.#debug(`#PostMessageToWorker with transfer object`);
349
+ workerEx.worker.postMessage({ command, payload }, [transferObject]);
350
+ this.#debug(`#PostMessageToWorker with transfer object - done...`);
351
+ } else {
352
+ this.#debug(`#PostMessageToWorker`);
353
+ workerEx.worker.postMessage({ command, payload });
354
+ }
355
+ }
356
+
357
+ #TerminateWorker = (workerEx: IWorkerEx) => {
358
+ if (workerEx.worker) {
359
+ //const store = TelemetryStore();
360
+ workerEx.worker.terminate();
361
+ this.#debug(`Terminated worker: [${workerEx.id}]`);
362
+ //store.RemoveWorker(workerEx);
363
+ delete this.#workersEx[workerEx.id];
364
+ } else {
365
+ // Some other runner has already removed the parent worker, do nothing
366
+ // console.log(`WORKER ALREADY NULL`);
367
+ }
368
+ }
369
+
370
+ #StopRunner = async (workerEx: IWorkerEx, runnerEx: IRunnerEx | null = null): Promise<boolean> => {
371
+ // If runnerEx not provided, Remove the first runner in the collection
372
+ if (runnerEx === null) {
373
+ const ids: string[] = Object.keys(workerEx.runnersEx);
374
+ if (ids.length > 0) {
375
+ const id = ids[0];
376
+ runnerEx = workerEx.runnersEx[id];
377
+ }
378
+ }
379
+ if (runnerEx !== null) {
380
+ this.#PostMessageToWorker(workerEx, eIWMessageCommands.StopAllAsyncRunners, {
381
+ runner: this.#CreateRunnerCopy(runnerEx)
382
+ } as ITestRunnerTelemetryPayload);
383
+
384
+ runnerEx.publishInstrumentController.LogEx(`Terminating runner: [${runnerEx.id}]`);
385
+
386
+ const promArray: Promise<boolean>[] = [ ];
387
+
388
+ promArray.push((async (): Promise<boolean> => {
389
+ await Sleep(100);
390
+ return runnerEx.publishInstrumentController.EndPublish() as Promise<boolean>
391
+ })());
392
+
393
+ //const store = TelemetryStore();
394
+ //store.RemoveRunner(workerEx, runnerEx);
395
+
396
+ delete workerEx.runnersEx[runnerEx.id];
397
+
398
+ const retVal = await Promise.all(promArray);
399
+ console.log(`Removed instrument workers: [${retVal}]`);
400
+ }
401
+ return true;
402
+ }
403
+
404
+ #StopWorker = async (workerEx: IWorkerEx): Promise<boolean> => {
405
+ try {
406
+ if (workerEx.state !== IWorkerState.stopped) {
407
+ this.#PostMessageToWorker(workerEx, eIWMessageCommands.StopAllAsyncRunners, null);
408
+
409
+ //@@ Now wait until we get an ack back from the worker
410
+ // This is because we may be trying to stop BEFORE the worker has had a chance to startup ...
411
+
412
+ console.log(`Terminating worker: [${workerEx.id}]`);
413
+ const promArray: Promise<boolean>[] = [ ];
414
+
415
+ // Terminate only those that are currently running...
416
+ const ids: string[] = Object.keys(workerEx.runnersEx);
417
+
418
+ ids.forEach((id) => {
419
+ const runnerEx: IRunnerEx = workerEx.runnersEx[id];
420
+ promArray.push(this.#StopRunner(workerEx, runnerEx));
421
+ });
422
+ await Promise.all(promArray);
423
+
424
+ this.#TerminateWorker(workerEx);
425
+ }
426
+ return true;
427
+ } catch (error) {
428
+ console.log(`Error in STSTestWorker:StopWorker: [${error}]`);
429
+ return false;
430
+ }
431
+ }
432
+
433
+ GetNextAvailableWorker = (): IWorkerEx | null => {
434
+ // Calculate the worker with the least runners
435
+ let leastRunnerWorker: IWorkerEx | null = null;
436
+ for (const [, stsWorker] of Object.entries(this.WorkersEx)) {
437
+ if (leastRunnerWorker) {
438
+ if (Object.keys(stsWorker.runnersEx).length < Object.keys(leastRunnerWorker.runnersEx).length) {
439
+ leastRunnerWorker = stsWorker;
440
+ }
441
+ } else {
442
+ leastRunnerWorker = stsWorker;
443
+ }
444
+ }
445
+ return leastRunnerWorker;
446
+ }
447
+
448
+ GetBusyWorker = (): IWorkerEx | null => {
449
+ // Calculate the worker with the least runners
450
+ let busyWorker: IWorkerEx | null = null;
451
+ for (const [, stsWorker] of Object.entries(this.WorkersEx)) {
452
+ if (busyWorker) {
453
+ if (Object.keys(stsWorker.runnersEx).length > Object.keys(busyWorker.runnersEx).length) {
454
+ busyWorker = stsWorker;
455
+ }
456
+ } else {
457
+ busyWorker = stsWorker;
458
+ }
459
+ }
460
+ return busyWorker;
461
+ }
462
+
463
+ get Options(): IWorkerManagerOptions {
464
+ return this.#options;
465
+ }
466
+
467
+ set Options(options: IWorkerManagerOptions) {
468
+ this.#options = options;
469
+ }
470
+
471
+ StopAllWorkers = async () => {
472
+ const promArray = [ ];
473
+ for (const [, stsWorker] of Object.entries(this.WorkersEx)) {
474
+ promArray.push(stsWorker.Stop());
475
+ }
476
+ await Promise.all(promArray);
477
+ }
478
+ }