@nestia/e2e 0.4.3 → 0.5.0-dev.20240617-2

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/src/ArrayUtil.ts CHANGED
@@ -1,98 +1,98 @@
1
- /**
2
- * Utility functions for arrays.
3
- *
4
- * @author Jeongho Nam - https://github.com/samchon
5
- */
6
- export namespace ArrayUtil {
7
- export const asyncFilter =
8
- <Input>(elements: readonly Input[]) =>
9
- async (
10
- pred: (
11
- elem: Input,
12
- index: number,
13
- array: readonly Input[],
14
- ) => Promise<boolean>,
15
- ): Promise<Input[]> => {
16
- const ret: Input[] = [];
17
- await asyncForEach(elements)(async (elem, index, array) => {
18
- const flag: boolean = await pred(elem, index, array);
19
- if (flag === true) ret.push(elem);
20
- });
21
- return ret;
22
- };
23
-
24
- export const asyncForEach =
25
- <Input>(elements: readonly Input[]) =>
26
- async (
27
- closure: (
28
- elem: Input,
29
- index: number,
30
- array: readonly Input[],
31
- ) => Promise<any>,
32
- ): Promise<void> => {
33
- await asyncRepeat(elements.length)((index) =>
34
- closure(elements[index], index, elements),
35
- );
36
- };
37
-
38
- export const asyncMap =
39
- <Input>(elements: readonly Input[]) =>
40
- async <Output>(
41
- closure: (
42
- elem: Input,
43
- index: number,
44
- array: readonly Input[],
45
- ) => Promise<Output>,
46
- ): Promise<Output[]> => {
47
- const ret: Output[] = [];
48
- await asyncForEach(elements)(async (elem, index, array) => {
49
- const output: Output = await closure(elem, index, array);
50
- ret.push(output);
51
- });
52
- return ret;
53
- };
54
-
55
- export const asyncRepeat =
56
- (count: number) =>
57
- async <T>(closure: (index: number) => Promise<T>): Promise<T[]> => {
58
- const indexes: number[] = new Array(count)
59
- .fill(1)
60
- .map((_, index) => index);
61
-
62
- const output: T[] = [];
63
- for (const index of indexes) output.push(await closure(index));
64
-
65
- return output;
66
- };
67
-
68
- export const has =
69
- <T>(elements: readonly T[]) =>
70
- (pred: (elem: T) => boolean): boolean =>
71
- elements.find(pred) !== undefined;
72
-
73
- export const repeat =
74
- (count: number) =>
75
- <T>(closure: (index: number) => T): T[] =>
76
- new Array(count).fill("").map((_, index) => closure(index));
77
-
78
- export const flat = <T>(matrix: T[][]): T[] => ([] as T[]).concat(...matrix);
79
-
80
- export const subsets = <T>(array: T[]): T[][] => {
81
- const check: boolean[] = new Array(array.length).fill(false);
82
- const output: T[][] = [];
83
-
84
- const dfs = (depth: number): void => {
85
- if (depth === check.length)
86
- output.push(array.filter((_v, idx) => check[idx]));
87
- else {
88
- check[depth] = true;
89
- dfs(depth + 1);
90
-
91
- check[depth] = false;
92
- dfs(depth + 1);
93
- }
94
- };
95
- dfs(0);
96
- return output;
97
- };
98
- }
1
+ /**
2
+ * Utility functions for arrays.
3
+ *
4
+ * @author Jeongho Nam - https://github.com/samchon
5
+ */
6
+ export namespace ArrayUtil {
7
+ export const asyncFilter =
8
+ <Input>(elements: readonly Input[]) =>
9
+ async (
10
+ pred: (
11
+ elem: Input,
12
+ index: number,
13
+ array: readonly Input[],
14
+ ) => Promise<boolean>,
15
+ ): Promise<Input[]> => {
16
+ const ret: Input[] = [];
17
+ await asyncForEach(elements)(async (elem, index, array) => {
18
+ const flag: boolean = await pred(elem, index, array);
19
+ if (flag === true) ret.push(elem);
20
+ });
21
+ return ret;
22
+ };
23
+
24
+ export const asyncForEach =
25
+ <Input>(elements: readonly Input[]) =>
26
+ async (
27
+ closure: (
28
+ elem: Input,
29
+ index: number,
30
+ array: readonly Input[],
31
+ ) => Promise<any>,
32
+ ): Promise<void> => {
33
+ await asyncRepeat(elements.length)((index) =>
34
+ closure(elements[index], index, elements),
35
+ );
36
+ };
37
+
38
+ export const asyncMap =
39
+ <Input>(elements: readonly Input[]) =>
40
+ async <Output>(
41
+ closure: (
42
+ elem: Input,
43
+ index: number,
44
+ array: readonly Input[],
45
+ ) => Promise<Output>,
46
+ ): Promise<Output[]> => {
47
+ const ret: Output[] = [];
48
+ await asyncForEach(elements)(async (elem, index, array) => {
49
+ const output: Output = await closure(elem, index, array);
50
+ ret.push(output);
51
+ });
52
+ return ret;
53
+ };
54
+
55
+ export const asyncRepeat =
56
+ (count: number) =>
57
+ async <T>(closure: (index: number) => Promise<T>): Promise<T[]> => {
58
+ const indexes: number[] = new Array(count)
59
+ .fill(1)
60
+ .map((_, index) => index);
61
+
62
+ const output: T[] = [];
63
+ for (const index of indexes) output.push(await closure(index));
64
+
65
+ return output;
66
+ };
67
+
68
+ export const has =
69
+ <T>(elements: readonly T[]) =>
70
+ (pred: (elem: T) => boolean): boolean =>
71
+ elements.find(pred) !== undefined;
72
+
73
+ export const repeat =
74
+ (count: number) =>
75
+ <T>(closure: (index: number) => T): T[] =>
76
+ new Array(count).fill("").map((_, index) => closure(index));
77
+
78
+ export const flat = <T>(matrix: T[][]): T[] => ([] as T[]).concat(...matrix);
79
+
80
+ export const subsets = <T>(array: T[]): T[][] => {
81
+ const check: boolean[] = new Array(array.length).fill(false);
82
+ const output: T[][] = [];
83
+
84
+ const dfs = (depth: number): void => {
85
+ if (depth === check.length)
86
+ output.push(array.filter((_v, idx) => check[idx]));
87
+ else {
88
+ check[depth] = true;
89
+ dfs(depth + 1);
90
+
91
+ check[depth] = false;
92
+ dfs(depth + 1);
93
+ }
94
+ };
95
+ dfs(0);
96
+ return output;
97
+ };
98
+ }
@@ -0,0 +1,328 @@
1
+ import { IConnection } from "@nestia/fetcher";
2
+ import fs from "fs";
3
+ import { Driver, WorkerConnector, WorkerServer } from "tgrid";
4
+ import { HashMap, hash } from "tstl";
5
+
6
+ import { IBenchmarkEvent } from "./structures/IBenchmarkEvent";
7
+ import { IBenchmarkMaster } from "./structures/IBenchmarkMaster";
8
+ import { IBenchmarkServant } from "./structures/IBenchmarkServant";
9
+
10
+ export namespace DynamicBenchmarker {
11
+ /**
12
+ * Properties of the master program.
13
+ */
14
+ export interface IMasterProps {
15
+ /**
16
+ * Total count of the requests.
17
+ */
18
+ count: number;
19
+
20
+ /**
21
+ * Number of threads.
22
+ *
23
+ * The number of threads to be executed as parallel servant.
24
+ */
25
+ threads: number;
26
+
27
+ /**
28
+ * Number of simultaneous requests.
29
+ *
30
+ * The number of requests to be executed simultaneously.
31
+ *
32
+ * This property value would be divided by the {@link threads} in the servants.
33
+ */
34
+ simultaneous: number;
35
+
36
+ /**
37
+ * Path of the servant program.
38
+ *
39
+ * The path of the servant program executing the
40
+ * {@link DynamicBenchmarker.servant} function.
41
+ */
42
+ servant: string;
43
+
44
+ /**
45
+ * Filter function.
46
+ *
47
+ * The filter function to determine whether to execute the function in
48
+ * the servant or not.
49
+ *
50
+ * @param name Function name
51
+ * @returns Whether to execute the function or not.
52
+ */
53
+ filter?: (name: string) => boolean;
54
+
55
+ /**
56
+ * Progress callback function.
57
+ *
58
+ * @param complete The number of completed requests.
59
+ */
60
+ progress?: (complete: number) => void;
61
+
62
+ /**
63
+ * Standard I/O option.
64
+ *
65
+ * The standard I/O option for the servant programs.
66
+ */
67
+ stdio?: undefined | "overlapped" | "pipe" | "ignore" | "inherit";
68
+ }
69
+
70
+ /**
71
+ * Properties of the servant progrma.
72
+ */
73
+ export interface IServantProps<Parameters extends any[]> {
74
+ /**
75
+ * Default connection.
76
+ *
77
+ * Default connection to be used in the servant.
78
+ */
79
+ connection: IConnection;
80
+
81
+ /**
82
+ * Location of the benchmark functions.
83
+ */
84
+ location: string;
85
+
86
+ /**
87
+ * Prefix of the benchmark functions.
88
+ *
89
+ * Every prefixed function will be executed in the servant.
90
+ *
91
+ * In other words, if a function name doesn't start with the prefix,
92
+ * then it would never be executed.
93
+ */
94
+ prefix: string;
95
+
96
+ /**
97
+ * Get parameters of a function.
98
+ *
99
+ * When composing the parameters, never forget to copy the
100
+ * {@link IConnection.logger} property of default connection to the
101
+ * returning parameters.
102
+ *
103
+ * @param name Function name
104
+ * @param connection Default connection instance
105
+ */
106
+ parameters: (name: string, connection: IConnection) => Parameters;
107
+ }
108
+ export interface IReport {
109
+ time: number;
110
+ statistics: IReport.IStatistics;
111
+ endpoints: Array<IReport.IEndpoint & IReport.IStatistics>;
112
+ }
113
+ export namespace IReport {
114
+ export interface IEndpoint {
115
+ method: string;
116
+ path: string;
117
+ }
118
+ export interface IStatistics {
119
+ count: number;
120
+ success: number;
121
+ mean: number | null;
122
+ stdev: number | null;
123
+ minimum: number | null;
124
+ maximum: number | null;
125
+ }
126
+ }
127
+
128
+ export const master = async (props: IMasterProps): Promise<IReport> => {
129
+ const completes: number[] = new Array(props.threads).fill(0);
130
+ const servants: WorkerConnector<
131
+ null,
132
+ IBenchmarkMaster,
133
+ IBenchmarkServant
134
+ >[] = await Promise.all(
135
+ new Array(props.threads).fill(null).map(async (i) => {
136
+ const connector: WorkerConnector<
137
+ null,
138
+ IBenchmarkMaster,
139
+ IBenchmarkServant
140
+ > = new WorkerConnector(
141
+ null,
142
+ {
143
+ filter: props.filter ?? (() => true),
144
+ progress: (current) => {
145
+ completes[i] = current;
146
+ if (props.progress)
147
+ props.progress(completes.reduce((a, b) => a + b, 0));
148
+ },
149
+ },
150
+ "process",
151
+ );
152
+ await connector.connect(props.servant, { stdio: props.stdio });
153
+ return connector;
154
+ }),
155
+ );
156
+ const started_at: Date = new Date();
157
+ const events: IBenchmarkEvent[] = (
158
+ await Promise.all(
159
+ servants.map((connector) =>
160
+ connector.getDriver().execute({
161
+ count: Math.ceil(props.count / props.threads),
162
+ simultaneous: Math.ceil(props.simultaneous / props.threads),
163
+ }),
164
+ ),
165
+ )
166
+ ).flat();
167
+ const time: number = Date.now() - started_at.getTime();
168
+ await Promise.all(servants.map((connector) => connector.close()));
169
+ if (props.progress) props.progress(props.count);
170
+
171
+ const endpoints: HashMap<IReport.IEndpoint, IBenchmarkEvent[]> =
172
+ new HashMap(
173
+ (key) => hash(key.method, key.path),
174
+ (x, y) => x.method === y.method && x.path === y.path,
175
+ );
176
+ for (const e of events)
177
+ endpoints
178
+ .take(
179
+ {
180
+ method: e.metadata.method,
181
+ path: e.metadata.template ?? e.metadata.path,
182
+ },
183
+ () => [],
184
+ )
185
+ .push(e);
186
+ return {
187
+ time,
188
+ statistics: statistics(events),
189
+ endpoints: [...endpoints].map((it) => ({
190
+ ...statistics(it.second),
191
+ ...it.first,
192
+ })),
193
+ };
194
+ };
195
+
196
+ export const servant = async <Parameters extends any[]>(
197
+ props: IServantProps<Parameters>,
198
+ ): Promise<WorkerServer<null, IBenchmarkServant, IBenchmarkMaster>> => {
199
+ const server: WorkerServer<null, IBenchmarkServant, IBenchmarkMaster> =
200
+ new WorkerServer();
201
+ await server.open({
202
+ execute: execute({
203
+ driver: server.getDriver(),
204
+ props,
205
+ }),
206
+ });
207
+ return server;
208
+ };
209
+
210
+ const execute =
211
+ <Parameters extends any[]>(ctx: {
212
+ driver: Driver<IBenchmarkMaster>;
213
+ props: IServantProps<Parameters>;
214
+ }) =>
215
+ async (mass: {
216
+ count: number;
217
+ simultaneous: number;
218
+ }): Promise<IBenchmarkEvent[]> => {
219
+ const functions: IFunction<Parameters>[] = [];
220
+ await iterate({
221
+ collection: functions,
222
+ driver: ctx.driver,
223
+ props: ctx.props,
224
+ })(ctx.props.location);
225
+ const events: IBenchmarkEvent[] = [];
226
+
227
+ ctx.props.connection.logger = async (e): Promise<void> => {
228
+ events.push({
229
+ metadata: e.route,
230
+ status: e.status,
231
+ started_at: e.started_at.toISOString(),
232
+ repond_at: e.respond_at?.toISOString() ?? null,
233
+ completed_at: e.completed_at.toISOString(),
234
+ });
235
+ };
236
+
237
+ await Promise.all(
238
+ new Array(mass.simultaneous).fill(null).map(async () => {
239
+ while (events.length < mass.count) {
240
+ const prev: number = events.length;
241
+ const func: IFunction<Parameters> =
242
+ functions[Math.floor(Math.random() * functions.length)];
243
+ try {
244
+ await func.value(
245
+ ...ctx.props.parameters(func.key, ctx.props.connection),
246
+ );
247
+ const current: number = events.length;
248
+ if (current !== prev)
249
+ ctx.driver.progress(current).catch(() => {});
250
+ } catch {}
251
+ }
252
+ }),
253
+ );
254
+ await ctx.driver.progress(events.length);
255
+ return events;
256
+ };
257
+
258
+ const statistics = (events: IBenchmarkEvent[]): IReport.IStatistics => {
259
+ const successes: IBenchmarkEvent[] = events.filter(
260
+ (event) => event.status === 200 || event.status === 201,
261
+ );
262
+ return {
263
+ count: events.length,
264
+ success: successes.length,
265
+ ...average(events),
266
+ };
267
+ };
268
+
269
+ const average = (
270
+ events: IBenchmarkEvent[],
271
+ ): Pick<IReport.IStatistics, "mean" | "stdev" | "minimum" | "maximum"> => {
272
+ if (events.length === 0)
273
+ return {
274
+ mean: null,
275
+ stdev: null,
276
+ minimum: null,
277
+ maximum: null,
278
+ };
279
+ let mean: number = 0;
280
+ let stdev: number = 0;
281
+ let minimum: number = Number.MAX_SAFE_INTEGER;
282
+ let maximum: number = Number.MIN_SAFE_INTEGER;
283
+ for (const event of events) {
284
+ const elapsed: number =
285
+ new Date(event.completed_at).getTime() -
286
+ new Date(event.started_at).getTime();
287
+ mean += elapsed;
288
+ stdev += elapsed * elapsed;
289
+ minimum = Math.min(minimum, elapsed);
290
+ maximum = Math.max(maximum, elapsed);
291
+ }
292
+ mean /= events.length;
293
+ stdev = Math.sqrt(stdev / events.length - mean * mean);
294
+ return { mean, stdev, minimum, maximum };
295
+ };
296
+ }
297
+
298
+ interface IFunction<Parameters extends any[]> {
299
+ key: string;
300
+ value: (...args: Parameters) => Promise<void>;
301
+ }
302
+
303
+ const iterate =
304
+ <Parameters extends any[]>(ctx: {
305
+ collection: IFunction<Parameters>[];
306
+ driver: Driver<IBenchmarkMaster>;
307
+ props: DynamicBenchmarker.IServantProps<Parameters>;
308
+ }) =>
309
+ async (path: string): Promise<void> => {
310
+ const directory: string[] = await fs.promises.readdir(path);
311
+ for (const file of directory) {
312
+ const location: string = `${path}/${file}`;
313
+ const stat: fs.Stats = await fs.promises.stat(location);
314
+ if (stat.isDirectory() === true) await iterate(ctx)(location);
315
+ else if (file.endsWith(".js") === true) {
316
+ const modulo = await import(location);
317
+ for (const [key, value] of Object.entries(modulo)) {
318
+ if (typeof value !== "function") continue;
319
+ else if (key.startsWith(ctx.props.prefix) === false) continue;
320
+ else if ((await ctx.driver.filter(key)) === false) continue;
321
+ ctx.collection.push({
322
+ key,
323
+ value: value as (...args: Parameters) => Promise<any>,
324
+ });
325
+ }
326
+ }
327
+ }
328
+ };