@vpalmisano/webrtcperf 4.6.1 → 4.7.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/README.md +18 -0
- package/app.min.js +1 -1
- package/build/src/app.js +8 -0
- package/build/src/app.js.map +1 -1
- package/build/src/config.d.ts +7 -0
- package/build/src/config.js +44 -2
- package/build/src/config.js.map +1 -1
- package/build/src/mcp.d.ts +1 -0
- package/build/src/mcp.js +209 -0
- package/build/src/mcp.js.map +1 -0
- package/build/src/scenarios.d.ts +1 -1
- package/build/src/scenarios.js +1 -1
- package/build/src/scenarios.js.map +1 -1
- package/build/src/server.js +5 -5
- package/build/src/server.js.map +1 -1
- package/build/src/stats.d.ts +4 -0
- package/build/src/stats.js +9 -2
- package/build/src/stats.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -12
- package/src/app.ts +9 -0
- package/src/config.ts +38 -2
- package/src/mcp.ts +248 -0
- package/src/scenarios.ts +1 -1
- package/src/server.ts +5 -5
- package/src/stats.ts +10 -2
package/build/src/mcp.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mcpRunner = mcpRunner;
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const throttler_1 = require("@vpalmisano/throttler");
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
const config_1 = require("./config");
|
|
9
|
+
const session_1 = require("./session");
|
|
10
|
+
const stats_1 = require("./stats");
|
|
11
|
+
const media_1 = require("./media");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
const log = (0, utils_1.logger)('webrtcperf:mcp');
|
|
14
|
+
const mcpServer = new mcp_js_1.McpServer({
|
|
15
|
+
name: 'webrtcperf',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
});
|
|
18
|
+
let stats;
|
|
19
|
+
let statsReady;
|
|
20
|
+
async function getStats() {
|
|
21
|
+
if (!stats) {
|
|
22
|
+
const [defaultConfig] = await (0, config_1.loadConfig)(undefined, {
|
|
23
|
+
showStats: false,
|
|
24
|
+
});
|
|
25
|
+
if (!defaultConfig.startTimestamp) {
|
|
26
|
+
defaultConfig.startTimestamp = Date.now();
|
|
27
|
+
}
|
|
28
|
+
stats = new stats_1.Stats(defaultConfig);
|
|
29
|
+
stats.on('stats', () => {
|
|
30
|
+
mcpServer.server.sendResourceUpdated({ uri: 'webrtcperf://stats' });
|
|
31
|
+
});
|
|
32
|
+
statsReady = stats.start();
|
|
33
|
+
}
|
|
34
|
+
await statsReady;
|
|
35
|
+
return stats;
|
|
36
|
+
}
|
|
37
|
+
async function startSessionHandler(args) {
|
|
38
|
+
const { config } = args;
|
|
39
|
+
log.debug('startSessionHandler', config);
|
|
40
|
+
const stats = await getStats();
|
|
41
|
+
const configs = await (0, config_1.loadConfig)(undefined, config);
|
|
42
|
+
const sessionConfig = configs[0];
|
|
43
|
+
const tabsPerSession = sessionConfig.tabsPerSession ?? 1;
|
|
44
|
+
const id = stats.consumeSessionId(tabsPerSession);
|
|
45
|
+
const throttleIndex = (0, throttler_1.getSessionThrottleIndex)(id);
|
|
46
|
+
const spawnRate = sessionConfig.spawnRate ?? 1;
|
|
47
|
+
const spawnPeriod = 1000 / spawnRate;
|
|
48
|
+
const throttleConfig = sessionConfig.throttleConfig;
|
|
49
|
+
if (throttleConfig) {
|
|
50
|
+
await (0, throttler_1.startThrottle)(throttleConfig);
|
|
51
|
+
}
|
|
52
|
+
const mediaPaths = [];
|
|
53
|
+
const videoPath = sessionConfig.videoPath;
|
|
54
|
+
if (videoPath) {
|
|
55
|
+
for (const vp of videoPath.split(',')) {
|
|
56
|
+
const ret = await (0, media_1.prepareFakeMedia)({ ...sessionConfig, videoPath: vp });
|
|
57
|
+
mediaPaths.push(ret);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const mediaPath = mediaPaths.length ? mediaPaths[id % mediaPaths.length] : undefined;
|
|
61
|
+
if (sessionConfig.url.startsWith('https://meet.google.com')) {
|
|
62
|
+
if (!sessionConfig.scriptPath) {
|
|
63
|
+
sessionConfig.scriptPath =
|
|
64
|
+
'https://raw.githubusercontent.com/vpalmisano/webrtcperf/refs/heads/devel/examples/google-meet.js';
|
|
65
|
+
}
|
|
66
|
+
if (!sessionConfig.scriptParams) {
|
|
67
|
+
sessionConfig.scriptParams = '{"enableMic": true, "enableCam": true}';
|
|
68
|
+
}
|
|
69
|
+
if (!sessionConfig.debuggingPort) {
|
|
70
|
+
sessionConfig.debuggingPort = 9000;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (sessionConfig.url.startsWith('https://meet.livekit.io')) {
|
|
74
|
+
if (!sessionConfig.scriptPath) {
|
|
75
|
+
sessionConfig.scriptPath =
|
|
76
|
+
'https://raw.githubusercontent.com/vpalmisano/webrtcperf/refs/heads/devel/examples/livekit.js';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const session = new session_1.Session({
|
|
80
|
+
...sessionConfig,
|
|
81
|
+
throttleIndex,
|
|
82
|
+
spawnPeriod,
|
|
83
|
+
mediaPath,
|
|
84
|
+
id,
|
|
85
|
+
});
|
|
86
|
+
session.once('stop', () => {
|
|
87
|
+
setTimeout(() => startSessionHandler({ config }).catch(() => { }), spawnPeriod);
|
|
88
|
+
});
|
|
89
|
+
stats.addSession(session);
|
|
90
|
+
try {
|
|
91
|
+
await session.start();
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
stats.removeSession(session.id);
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
if (sessionConfig.runDuration) {
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
session.removeAllListeners();
|
|
100
|
+
session.stop();
|
|
101
|
+
stats.removeSession(session.id);
|
|
102
|
+
}, sessionConfig.runDuration * 1000);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: 'text',
|
|
108
|
+
text: JSON.stringify({ message: 'Session created', id }, null, 2),
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function stopSessionHandler(args) {
|
|
114
|
+
const { id } = args;
|
|
115
|
+
log.debug('stopSessionHandler', id);
|
|
116
|
+
const s = await getStats();
|
|
117
|
+
const session = s.sessions.get(id);
|
|
118
|
+
if (!session) {
|
|
119
|
+
return {
|
|
120
|
+
content: [
|
|
121
|
+
{
|
|
122
|
+
type: 'text',
|
|
123
|
+
text: JSON.stringify({ message: 'Session not found', id }, null, 2),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
session.removeAllListeners();
|
|
129
|
+
s.removeSession(id);
|
|
130
|
+
await session.stop();
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: 'text',
|
|
135
|
+
text: JSON.stringify({ message: 'Session deleted', id }),
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function getSessionsHandler() {
|
|
141
|
+
const s = await getStats();
|
|
142
|
+
const sessions = Array.from(s.sessions.entries()).map(([id, session]) => ({
|
|
143
|
+
id,
|
|
144
|
+
stats: session.stats,
|
|
145
|
+
}));
|
|
146
|
+
log.debug('getSessionsHandler', sessions);
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: 'text',
|
|
151
|
+
text: JSON.stringify({ sessions, count: sessions.length }),
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function resetStatsHandler() {
|
|
157
|
+
const s = await getStats();
|
|
158
|
+
log.debug('resetStatsHandler');
|
|
159
|
+
s.resetStats();
|
|
160
|
+
await mcpServer.server.sendResourceUpdated({ uri: 'webrtcperf://stats' });
|
|
161
|
+
return {
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: 'text',
|
|
165
|
+
text: JSON.stringify({ message: 'Stats reset' }),
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async function getStatsReadHandler(uri) {
|
|
171
|
+
const stats = await getStats();
|
|
172
|
+
log.debug('getStatsReadHandler', uri.href);
|
|
173
|
+
return {
|
|
174
|
+
contents: [
|
|
175
|
+
{
|
|
176
|
+
uri: uri.href,
|
|
177
|
+
mimeType: 'application/json',
|
|
178
|
+
text: JSON.stringify(stats.collectedStats),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
mcpServer.registerTool('start_session', {
|
|
184
|
+
description: 'Start a new webrtcperf session in-process. Config is the session config object (url, tabsPerSession, throttleConfig, etc.). Returns the created session id.',
|
|
185
|
+
inputSchema: { config: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()) },
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
|
+
},
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
189
|
+
startSessionHandler);
|
|
190
|
+
mcpServer.registerTool('stop_session', {
|
|
191
|
+
description: 'Stop a webrtcperf session by id.',
|
|
192
|
+
inputSchema: { id: zod_1.z.number().int().nonnegative() },
|
|
193
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
|
+
},
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
+
stopSessionHandler);
|
|
197
|
+
mcpServer.registerTool('get_sessions', {
|
|
198
|
+
description: 'List current webrtcperf sessions (id and stats for each running session).',
|
|
199
|
+
}, getSessionsHandler);
|
|
200
|
+
mcpServer.registerTool('reset_stats', {
|
|
201
|
+
description: 'Reset the collected webrtcperf stats.',
|
|
202
|
+
}, resetStatsHandler);
|
|
203
|
+
mcpServer.registerResource('stats', 'webrtcperf://stats', { description: 'Get the current webrtcperf stats.' }, getStatsReadHandler);
|
|
204
|
+
async function mcpRunner() {
|
|
205
|
+
log.debug('mcpRunner');
|
|
206
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
207
|
+
await mcpServer.connect(transport);
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/mcp.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { getSessionThrottleIndex, startThrottle } from '@vpalmisano/throttler'\nimport { z } from 'zod'\n\nimport { loadConfig } from './config'\nimport type { Config } from './config'\nimport { Session } from './session'\nimport { Stats } from './stats'\nimport { MediaPath, prepareFakeMedia } from './media'\nimport { logger } from './utils'\n\nconst log = logger('webrtcperf:mcp')\n\nconst mcpServer = new McpServer({\n name: 'webrtcperf',\n version: '1.0.0',\n})\n\nlet stats: Stats\nlet statsReady: Promise<void>\n\nasync function getStats(): Promise<Stats> {\n if (!stats) {\n const [defaultConfig] = await loadConfig(undefined, {\n showStats: false,\n })\n if (!defaultConfig.startTimestamp) {\n defaultConfig.startTimestamp = Date.now()\n }\n stats = new Stats(defaultConfig)\n stats.on('stats', () => {\n mcpServer.server.sendResourceUpdated({ uri: 'webrtcperf://stats' })\n })\n statsReady = stats.start()\n }\n await statsReady\n return stats\n}\n\nasync function startSessionHandler(args: {\n config: Record<string, unknown>\n}): Promise<{ content: Array<{ type: 'text'; text: string }> }> {\n const { config } = args\n log.debug('startSessionHandler', config)\n const stats = await getStats()\n const configs = await loadConfig(undefined, config as Partial<Config>)\n const sessionConfig = configs[0]\n const tabsPerSession = sessionConfig.tabsPerSession ?? 1\n const id = stats.consumeSessionId(tabsPerSession)\n const throttleIndex = getSessionThrottleIndex(id)\n const spawnRate = (sessionConfig as Config & { spawnRate?: number }).spawnRate ?? 1\n const spawnPeriod = 1000 / spawnRate\n const throttleConfig = (sessionConfig as Config & { throttleConfig?: string }).throttleConfig\n if (throttleConfig) {\n await startThrottle(throttleConfig)\n }\n const mediaPaths: MediaPath[] = []\n const videoPath = sessionConfig.videoPath\n if (videoPath) {\n for (const vp of videoPath.split(',')) {\n const ret = await prepareFakeMedia({ ...sessionConfig, videoPath: vp })\n mediaPaths.push(ret)\n }\n }\n const mediaPath = mediaPaths.length ? mediaPaths[id % mediaPaths.length] : undefined\n\n if (sessionConfig.url.startsWith('https://meet.google.com')) {\n if (!sessionConfig.scriptPath) {\n sessionConfig.scriptPath =\n 'https://raw.githubusercontent.com/vpalmisano/webrtcperf/refs/heads/devel/examples/google-meet.js'\n }\n if (!sessionConfig.scriptParams) {\n sessionConfig.scriptParams = '{\"enableMic\": true, \"enableCam\": true}'\n }\n if (!sessionConfig.debuggingPort) {\n sessionConfig.debuggingPort = 9000\n }\n } else if (sessionConfig.url.startsWith('https://meet.livekit.io')) {\n if (!sessionConfig.scriptPath) {\n sessionConfig.scriptPath =\n 'https://raw.githubusercontent.com/vpalmisano/webrtcperf/refs/heads/devel/examples/livekit.js'\n }\n }\n\n const session = new Session({\n ...sessionConfig,\n throttleIndex,\n spawnPeriod,\n mediaPath,\n id,\n })\n session.once('stop', () => {\n setTimeout(() => startSessionHandler({ config }).catch(() => {}), spawnPeriod)\n })\n stats.addSession(session)\n try {\n await session.start()\n } catch (err) {\n stats.removeSession(session.id)\n throw err\n }\n if (sessionConfig.runDuration) {\n setTimeout(() => {\n session.removeAllListeners()\n session.stop()\n stats.removeSession(session.id)\n }, sessionConfig.runDuration * 1000)\n }\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ message: 'Session created', id }, null, 2),\n },\n ],\n }\n}\n\nasync function stopSessionHandler(args: { id: number }): Promise<{\n content: Array<{ type: 'text'; text: string }>\n}> {\n const { id } = args\n log.debug('stopSessionHandler', id)\n const s = await getStats()\n const session = s.sessions.get(id)\n if (!session) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ message: 'Session not found', id }, null, 2),\n },\n ],\n }\n }\n session.removeAllListeners()\n s.removeSession(id)\n await session.stop()\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ message: 'Session deleted', id }),\n },\n ],\n }\n}\n\nasync function getSessionsHandler(): Promise<{ content: Array<{ type: 'text'; text: string }> }> {\n const s = await getStats()\n const sessions = Array.from(s.sessions.entries()).map(([id, session]) => ({\n id,\n stats: session.stats,\n }))\n log.debug('getSessionsHandler', sessions)\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ sessions, count: sessions.length }),\n },\n ],\n }\n}\n\nasync function resetStatsHandler(): Promise<{ content: Array<{ type: 'text'; text: string }> }> {\n const s = await getStats()\n log.debug('resetStatsHandler')\n s.resetStats()\n await mcpServer.server.sendResourceUpdated({ uri: 'webrtcperf://stats' })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ message: 'Stats reset' }),\n },\n ],\n }\n}\n\nasync function getStatsReadHandler(uri: URL): Promise<{\n contents: Array<{ uri: string; mimeType: string; text: string }>\n}> {\n const stats = await getStats()\n log.debug('getStatsReadHandler', uri.href)\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify(stats.collectedStats),\n },\n ],\n }\n}\n\nmcpServer.registerTool(\n 'start_session',\n {\n description:\n 'Start a new webrtcperf session in-process. Config is the session config object (url, tabsPerSession, throttleConfig, etc.). Returns the created session id.',\n inputSchema: { config: z.record(z.string(), z.unknown()) },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n startSessionHandler as any,\n)\n\nmcpServer.registerTool(\n 'stop_session',\n {\n description: 'Stop a webrtcperf session by id.',\n inputSchema: { id: z.number().int().nonnegative() },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n stopSessionHandler as any,\n)\n\nmcpServer.registerTool(\n 'get_sessions',\n {\n description: 'List current webrtcperf sessions (id and stats for each running session).',\n },\n getSessionsHandler,\n)\n\nmcpServer.registerTool(\n 'reset_stats',\n {\n description: 'Reset the collected webrtcperf stats.',\n },\n resetStatsHandler,\n)\n\nmcpServer.registerResource(\n 'stats',\n 'webrtcperf://stats',\n { description: 'Get the current webrtcperf stats.' },\n getStatsReadHandler,\n)\n\nexport async function mcpRunner(): Promise<void> {\n log.debug('mcpRunner')\n const transport = new StdioServerTransport()\n await mcpServer.connect(transport)\n}\n"]}
|
package/build/src/scenarios.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ export declare function twoParticipantsWithRateLossDelay(id: string, { rate, los
|
|
|
78
78
|
loss: number;
|
|
79
79
|
delay: number;
|
|
80
80
|
direction: ThrottleDirection;
|
|
81
|
-
}, repeat
|
|
81
|
+
}, repeat?: number): Promise<Partial<{
|
|
82
82
|
url: string;
|
|
83
83
|
urlQuery: string;
|
|
84
84
|
customUrlHandler: string;
|
package/build/src/scenarios.js
CHANGED
|
@@ -240,7 +240,7 @@ async function plotStatsSummary(stats) {
|
|
|
240
240
|
* @param repeat The number of times to repeat the test scenario. Default is 1.
|
|
241
241
|
* @returns An array of partial configuration objects for each test scenario.
|
|
242
242
|
*/
|
|
243
|
-
async function twoParticipantsWithRateLossDelay(id, { rate, loss, delay, direction }, repeat) {
|
|
243
|
+
async function twoParticipantsWithRateLossDelay(id, { rate, loss, delay, direction }, repeat = 1) {
|
|
244
244
|
const throttle = {};
|
|
245
245
|
const queue = 25;
|
|
246
246
|
if (direction === 'down' || direction === 'bidi') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenarios.js","sourceRoot":"","sources":["../../src/scenarios.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport { FastStats } from './stats'\nimport { ThrottleConfig, ThrottleRule } from '@vpalmisano/throttler'\nimport { Config } from './config'\nimport { Auth, google } from 'googleapis'\nimport { logger } from './utils'\nimport { sprintf } from 'sprintf-js'\nimport { PlotData, plotHtml } from './plot'\n\nconst log = logger('webrtcperf:scenarios')\n\nexport type StatsRow = Record<string, string | number>\n\n/**\n * It parses a CSV stats file and returns an array of objects representing each row.\n * @param filePath The path to the CSV stats file.\n * @returns An array of objects where each object represents a row in the CSV file with keys as column headers.\n */\nexport async function parseStatsFile(filePath: string) {\n log.debug(`parseStatsFile: ${filePath}`)\n const fileData = await fs.promises.readFile(filePath, 'utf-8')\n const lines = fileData.split('\\n')\n const headers = lines[0].split(',')\n const data = lines.slice(1).map(line =>\n line.split(',').reduce((acc, value, index) => {\n if (value !== '') {\n acc[headers[index]] = isNaN(Number(value)) ? value : Number(value)\n }\n return acc\n }, {} as StatsRow),\n )\n return data\n}\n\nexport type StatsSummary = {\n timestamp: number\n id: string\n scenario: string\n videoRecvBitratePerPixel: FastStats\n videoRecvFps: FastStats\n videoSentFps: FastStats\n}\n\n/**\n * It aggregates the stats summary from multiple test runs in a directory.\n * @param options.dirPath Directory path containing test run subdirectories. Default is 'logs'.\n * @param options.senderParticipantName Participant name of the sender. Default is 'Participant-000001'.\n * @param options.receiverParticipantName Participant name of the receiver. Default is 'Participant-000000'.\n * @param options.nameParser Function to parse test directory names. Default splits by '_' and extracts id and scenario.\n * @returns Array of aggregated stats including timestamp, id, scenario, videoRecvBitratePerPixel, videoRecvFps, and videoSentFps.\n */\nexport async function aggregateStatsSummary({\n dirPath = 'logs',\n senderParticipantName = 'Participant-000001',\n receiverParticipantName = 'Participant-000000',\n nameParser = (name: string) => {\n const [_, id, scenario] = name.split('_')\n return { id, scenario }\n },\n}) {\n log.debug(`aggregateStatsSummary: ${dirPath}`)\n const stats: StatsSummary[] = []\n const results = await fs.promises.readdir(dirPath)\n for (const test of results) {\n const filePath = path.join(dirPath, test, 'detailed-stats-summary.csv')\n if (!fs.existsSync(filePath)) continue\n const timestamp = fs.statSync(path.join(dirPath, test)).ctime.getTime()\n const data = await parseStatsFile(filePath)\n const { id, scenario } = nameParser(test)\n\n const aggregated = {\n timestamp,\n id,\n scenario,\n videoRecvBitratePerPixel: new FastStats(),\n videoRecvFps: new FastStats(),\n videoSentFps: new FastStats(),\n }\n data.forEach(v => {\n const { participantName, trackId } = v as { participantName: string; trackId: string }\n const metrics = v as Record<string, number>\n if (participantName === receiverParticipantName) {\n if (trackId?.endsWith('-v') && metrics.videoRecvFrames > 0) {\n const videoRecvBitratePerPixel =\n metrics.videoRecvBitrates / (metrics.videoRecvWidth * metrics.videoRecvHeight)\n if (!isNaN(videoRecvBitratePerPixel)) aggregated.videoRecvBitratePerPixel.push(videoRecvBitratePerPixel)\n if (!isNaN(metrics.videoRecvFps)) aggregated.videoRecvFps.push(metrics.videoRecvFps)\n }\n } else if (participantName === senderParticipantName) {\n if (trackId?.endsWith('-v') && metrics.videoSentFrames > 0) {\n if (!isNaN(metrics.videoSentFps)) aggregated.videoSentFps.push(metrics.videoSentFps)\n }\n }\n })\n stats.push(aggregated)\n }\n return stats.sort((a, b) => a.timestamp - b.timestamp)\n}\n\n/**\n * It uploads the aggregated stats to a Google Sheet.\n * A valid Google service account credentials file must be specified\n * in the `GOOGLE_CREDENTIALS_PATH` environment variable.\n * @param stats The aggregated stats to upload.\n * @param spreadsheetId The ID of the Google Spreadsheet.\n * @param table The name of the table (sheet) within the spreadsheet. Default is 'data'.\n */\nexport async function uploadStatsToGoogleSheet(stats: StatsSummary[], spreadsheetId: string, table = 'data') {\n log.debug(`uploadResultsToGoogleSheet spreadsheetId: ${spreadsheetId} table: ${table}`)\n if (!process.env.GOOGLE_CREDENTIALS_PATH) throw new Error('GOOGLE_CREDENTIALS_PATH environment variable is not set')\n if (!fs.existsSync(process.env.GOOGLE_CREDENTIALS_PATH))\n throw new Error(`Google credentials file not found: ${process.env.GOOGLE_CREDENTIALS_PATH}`)\n if (!stats.length) return\n const auth = new Auth.GoogleAuth({\n keyFile: process.env.GOOGLE_CREDENTIALS_PATH,\n scopes: ['https://www.googleapis.com/auth/spreadsheets'],\n })\n const sheets = google.sheets({ version: 'v4', auth })\n // Update headers.\n const headers = ['datetime', 'id', 'scenario', 'videoRecvBitratePerPixel', 'videoRecvFps']\n await sheets.spreadsheets.values.update({\n spreadsheetId,\n range: `${table}!A1:E1`,\n valueInputOption: 'USER_ENTERED',\n requestBody: { majorDimension: 'ROWS', values: [headers] },\n })\n // Append values.\n const values = [] as string[][]\n stats.forEach(s => {\n const { timestamp, id, scenario, videoRecvBitratePerPixel, videoRecvFps } = s\n if (!videoRecvBitratePerPixel.length) return\n const datetime = new Date(timestamp).toLocaleString('en-US', {\n timeZone: 'UTC',\n hourCycle: 'h23',\n })\n values.push([\n datetime,\n id,\n formatThrottleRule(parseThrottleRule(scenario), true),\n videoRecvBitratePerPixel.percentile(95).toFixed(3),\n videoRecvFps.percentile(95).toFixed(3),\n ])\n })\n if (values.length) {\n await sheets.spreadsheets.values.append({\n spreadsheetId,\n range: `${table}!A:E`,\n valueInputOption: 'USER_ENTERED',\n insertDataOption: 'INSERT_ROWS',\n requestBody: { majorDimension: 'ROWS', values },\n })\n }\n}\n\nexport type ThrottleDirection = 'up' | 'down' | 'bidi'\n\nexport function formatBitrate(bitrate: number | undefined, prefix = ' ', pad = true) {\n if (bitrate === undefined) return ''\n let suffix = 'Kbps'\n if (bitrate >= 1000) {\n bitrate /= 1000\n suffix = 'Mbps'\n }\n return `${prefix}${sprintf(`%${pad ? '5' : ''}.4g`, bitrate)}${suffix}`\n}\n\nexport function formatLoss(loss: number | undefined, prefix = ' ', pad = true) {\n return loss !== undefined ? `${prefix}${loss.toFixed(0).padStart(pad ? 2 : 0, ' ')}%` : ''\n}\n\nexport function formatDelay(delay: number | undefined, prefix = ' ', pad = true) {\n return delay !== undefined ? `${prefix}${delay.toFixed(0).padStart(pad ? 3 : 0, ' ')}ms` : ''\n}\n\nexport function formatThrottleRule(\n throttleRule: ThrottleRule & { direction: ThrottleDirection },\n human = false,\n pad = true,\n) {\n const { rate, loss, delay, direction } = throttleRule\n return human\n ? `${direction.padEnd(pad ? 4 : 0, ' ')}${formatBitrate(rate, ' ', pad)}${formatLoss(loss, ' ', pad)}${formatDelay(delay, ' ', pad)}`\n : `${direction}-r${rate}-l${loss}-d${delay}`\n}\n\nexport function parseThrottleRule(throttleDesc: string) {\n const match = throttleDesc.match(/(up|down|bidi)-r(\\d+)-l([\\d.]+)-d(\\d+)/)\n if (!match) throw new Error(`Invalid throttle description: ${throttleDesc}`)\n const direction = match[1] as ThrottleDirection\n const rate = parseInt(match[2])\n const loss = parseInt(match[3])\n const delay = parseInt(match[4])\n return { direction, rate, loss, delay }\n}\n\nexport async function plotStatsSummary(stats: StatsSummary[]) {\n const labels = new Set<string>()\n const data = {} as Record<string, Record<string, FastStats>>\n stats.forEach(s => {\n const { id, scenario, videoRecvBitratePerPixel } = s\n if (!videoRecvBitratePerPixel.length) return null\n if (!data[id]) {\n data[id] = {}\n }\n const scenarioFormatted = formatThrottleRule(parseThrottleRule(scenario), true, true)\n if (!data[id][scenarioFormatted]) {\n data[id][scenarioFormatted] = new FastStats()\n }\n labels.add(scenarioFormatted)\n data[id][scenarioFormatted].push(videoRecvBitratePerPixel.percentile(95))\n })\n const series: PlotData[] = []\n Object.entries(data)\n .sort((a, b) => a[0].localeCompare(b[0]))\n .forEach(([id, data]) => {\n const plotData: PlotData = { label: id, data: [] }\n Object.entries(data)\n .sort((a, b) => a[0].localeCompare(b[0]))\n .forEach(([scenario, stats]) => {\n plotData.data.push({\n x: scenario,\n y: stats.percentile(50),\n yMin: stats.percentile(5),\n yMax: stats.percentile(95),\n })\n })\n series.push(plotData)\n })\n await plotHtml(\n {\n type: 'barWithErrorBars',\n xLabel: 'Scenario',\n yLabel: 'Video Receive Bitrate per Pixel',\n labels: Array.from(labels).sort((a, b) => a.localeCompare(b)),\n },\n series,\n )\n}\n\n/**\n * It generates a test configuration with a scenario including 2 participants.\n * The first participant sends video and the second receives it.\n * Both participants send and receive audio.\n * The network conditions are applied according to the specified direction to the sender (`up`),\n * the receiver (`down`) or both (`bidi`).\n * The test is repeated the specified number of times.\n * The output is an array of partial configuration objects that can be used to run the tests\n * with the main application, after merging it with a configuration that includes\n * the destination url (mandatory) and other optional parameters.\n * @param id The unique identifier for the test scenario.\n * @param options.rate The target bandwidth in kbps.\n * @param options.loss The packet loss percentage.\n * @param options.delay The network delay in milliseconds.\n * @param options.direction The direction of the network throttling: 'up', 'down', or 'bidi'.\n * @param repeat The number of times to repeat the test scenario. Default is 1.\n * @returns An array of partial configuration objects for each test scenario.\n */\nexport async function twoParticipantsWithRateLossDelay(\n id: string,\n { rate, loss, delay, direction }: { rate: number; loss: number; delay: number; direction: ThrottleDirection },\n repeat: 1,\n) {\n const throttle: ThrottleConfig = {}\n const queue = 25\n if (direction === 'down' || direction === 'bidi') {\n throttle.down = [\n { rate: 20000, loss: 0, delay: 0, queue },\n { rate, loss, delay, queue, at: 30 },\n ]\n }\n if (direction === 'up' || direction === 'bidi') {\n throttle.up = [\n { rate: 20000, loss: 0, delay, queue },\n { rate, loss, delay, queue, at: 30 },\n ]\n }\n const throttleDesc = formatThrottleRule({ rate, loss, delay, direction })\n const now = Date.now()\n const ret: Partial<Config>[] = []\n for (let i = 0; i < repeat; i++) {\n const basePath = `logs/${now}-${i + 1}_${id}_${throttleDesc}`\n const sessions = direction === 'bidi' ? '0-1' : direction === 'down' ? '0' : '1'\n ret.push({\n sessions: 2,\n runDuration: 60 * 3,\n debuggingPort: 9000,\n prometheusPushgateway: 'http://localhost:9091',\n prometheusPushgatewayJobName: id,\n statsPath: `${basePath}/stats.csv`,\n detailedStatsPath: `${basePath}/detailed-stats.csv`,\n showPageLog: false,\n showStats: false,\n statsInterval: 5,\n scriptParams: JSON.stringify({\n enableMic: '0-1',\n enableCam: '1',\n }),\n throttleConfig: JSON.stringify([\n {\n sessions,\n protocol: 'udp',\n skipSourcePorts: '53,80,443',\n skipDestinationPorts: '53,80,443',\n ...throttle,\n },\n ]),\n })\n }\n return ret\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scenarios.js","sourceRoot":"","sources":["../../src/scenarios.ts"],"names":[],"mappings":";;;;;AAmBA,wCAcC;AAmBD,sDA8CC;AAUD,4DA6CC;AAID,sCAQC;AAED,gCAEC;AAED,kCAEC;AAED,gDASC;AAED,8CAQC;AAED,4CA0CC;AAoBD,4EAoDC;AAtTD,4CAAmB;AACnB,gDAAuB;AACvB,mCAAmC;AAGnC,2CAAyC;AACzC,mCAAgC;AAChC,2CAAoC;AACpC,iCAA2C;AAE3C,MAAM,GAAG,GAAG,IAAA,cAAM,EAAC,sBAAsB,CAAC,CAAA;AAI1C;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,GAAG,CAAC,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAA;IACxC,MAAM,QAAQ,GAAG,MAAM,YAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC9D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAClC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACnC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACrC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAC3C,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACjB,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACpE,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC,EAAE,EAAc,CAAC,CACnB,CAAA;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAWD;;;;;;;GAOG;AACI,KAAK,UAAU,qBAAqB,CAAC,EAC1C,OAAO,GAAG,MAAM,EAChB,qBAAqB,GAAG,oBAAoB,EAC5C,uBAAuB,GAAG,oBAAoB,EAC9C,UAAU,GAAG,CAAC,IAAY,EAAE,EAAE;IAC5B,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzC,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAA;AACzB,CAAC,GACF;IACC,GAAG,CAAC,KAAK,CAAC,0BAA0B,OAAO,EAAE,CAAC,CAAA;IAC9C,MAAM,KAAK,GAAmB,EAAE,CAAA;IAChC,MAAM,OAAO,GAAG,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAClD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,4BAA4B,CAAC,CAAA;QACvE,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACtC,MAAM,SAAS,GAAG,YAAE,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;QACvE,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAA;QAC3C,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;QAEzC,MAAM,UAAU,GAAG;YACjB,SAAS;YACT,EAAE;YACF,QAAQ;YACR,wBAAwB,EAAE,IAAI,iBAAS,EAAE;YACzC,YAAY,EAAE,IAAI,iBAAS,EAAE;YAC7B,YAAY,EAAE,IAAI,iBAAS,EAAE;SAC9B,CAAA;QACD,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACf,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,CAAiD,CAAA;YACtF,MAAM,OAAO,GAAG,CAA2B,CAAA;YAC3C,IAAI,eAAe,KAAK,uBAAuB,EAAE,CAAC;gBAChD,IAAI,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,eAAe,GAAG,CAAC,EAAE,CAAC;oBAC3D,MAAM,wBAAwB,GAC5B,OAAO,CAAC,iBAAiB,GAAG,CAAC,OAAO,CAAC,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;oBAChF,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC;wBAAE,UAAU,CAAC,wBAAwB,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAA;oBACxG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;wBAAE,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;gBACtF,CAAC;YACH,CAAC;iBAAM,IAAI,eAAe,KAAK,qBAAqB,EAAE,CAAC;gBACrD,IAAI,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,eAAe,GAAG,CAAC,EAAE,CAAC;oBAC3D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;wBAAE,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;gBACtF,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAA;QACF,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACxB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAA;AACxD,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,wBAAwB,CAAC,KAAqB,EAAE,aAAqB,EAAE,KAAK,GAAG,MAAM;IACzG,GAAG,CAAC,KAAK,CAAC,6CAA6C,aAAa,WAAW,KAAK,EAAE,CAAC,CAAA;IACvF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB;QAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;IACpH,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,sCAAsC,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC,CAAA;IAC9F,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAM;IACzB,MAAM,IAAI,GAAG,IAAI,iBAAI,CAAC,UAAU,CAAC;QAC/B,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB;QAC5C,MAAM,EAAE,CAAC,8CAA8C,CAAC;KACzD,CAAC,CAAA;IACF,MAAM,MAAM,GAAG,mBAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IACrD,kBAAkB;IAClB,MAAM,OAAO,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,0BAA0B,EAAE,cAAc,CAAC,CAAA;IAC1F,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC;QACtC,aAAa;QACb,KAAK,EAAE,GAAG,KAAK,QAAQ;QACvB,gBAAgB,EAAE,cAAc;QAChC,WAAW,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE;KAC3D,CAAC,CAAA;IACF,iBAAiB;IACjB,MAAM,MAAM,GAAG,EAAgB,CAAA;IAC/B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QAChB,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,wBAAwB,EAAE,YAAY,EAAE,GAAG,CAAC,CAAA;QAC7E,IAAI,CAAC,wBAAwB,CAAC,MAAM;YAAE,OAAM;QAC5C,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE;YAC3D,QAAQ,EAAE,KAAK;YACf,SAAS,EAAE,KAAK;SACjB,CAAC,CAAA;QACF,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ;YACR,EAAE;YACF,kBAAkB,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;YACrD,wBAAwB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YAClD,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;SACvC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC;YACtC,aAAa;YACb,KAAK,EAAE,GAAG,KAAK,MAAM;YACrB,gBAAgB,EAAE,cAAc;YAChC,gBAAgB,EAAE,aAAa;YAC/B,WAAW,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE;SAChD,CAAC,CAAA;IACJ,CAAC;AACH,CAAC;AAID,SAAgB,aAAa,CAAC,OAA2B,EAAE,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,IAAI;IACjF,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IACpC,IAAI,MAAM,GAAG,MAAM,CAAA;IACnB,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QACpB,OAAO,IAAI,IAAI,CAAA;QACf,MAAM,GAAG,MAAM,CAAA;IACjB,CAAC;IACD,OAAO,GAAG,MAAM,GAAG,IAAA,oBAAO,EAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAAA;AACzE,CAAC;AAED,SAAgB,UAAU,CAAC,IAAwB,EAAE,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,IAAI;IAC3E,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;AAC5F,CAAC;AAED,SAAgB,WAAW,CAAC,KAAyB,EAAE,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,IAAI;IAC7E,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;AAC/F,CAAC;AAED,SAAgB,kBAAkB,CAChC,YAA6D,EAC7D,KAAK,GAAG,KAAK,EACb,GAAG,GAAG,IAAI;IAEV,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,YAAY,CAAA;IACrD,OAAO,KAAK;QACV,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE;QACrI,CAAC,CAAC,GAAG,SAAS,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,EAAE,CAAA;AAChD,CAAC;AAED,SAAgB,iBAAiB,CAAC,YAAoB;IACpD,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAA;IAC1E,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,YAAY,EAAE,CAAC,CAAA;IAC5E,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAsB,CAAA;IAC/C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/B,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAChC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AACzC,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,KAAqB;IAC1D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAA;IAChC,MAAM,IAAI,GAAG,EAA+C,CAAA;IAC5D,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QAChB,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,wBAAwB,EAAE,GAAG,CAAC,CAAA;QACpD,IAAI,CAAC,wBAAwB,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QACjD,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACd,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAA;QACf,CAAC;QACD,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QACrF,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,GAAG,IAAI,iBAAS,EAAE,CAAA;QAC/C,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;QAC7B,IAAI,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IACF,MAAM,MAAM,GAAe,EAAE,CAAA;IAC7B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;SACjB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACxC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;QACtB,MAAM,QAAQ,GAAa,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAA;QAClD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;aACjB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACxC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE;YAC7B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBACjB,CAAC,EAAE,QAAQ;gBACX,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBACvB,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;gBACzB,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;aAC3B,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACJ,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;IACJ,MAAM,IAAA,eAAQ,EACZ;QACE,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE,UAAU;QAClB,MAAM,EAAE,iCAAiC;QACzC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;KAC9D,EACD,MAAM,CACP,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACI,KAAK,UAAU,gCAAgC,CACpD,EAAU,EACV,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAA+E,EAC7G,MAAM,GAAG,CAAC;IAEV,MAAM,QAAQ,GAAmB,EAAE,CAAA;IACnC,MAAM,KAAK,GAAG,EAAE,CAAA;IAChB,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;QACjD,QAAQ,CAAC,IAAI,GAAG;YACd,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE;YACzC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE;SACrC,CAAA;IACH,CAAC;IACD,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;QAC/C,QAAQ,CAAC,EAAE,GAAG;YACZ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE;YACtC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE;SACrC,CAAA;IACH,CAAC;IACD,MAAM,YAAY,GAAG,kBAAkB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;IACzE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,MAAM,GAAG,GAAsB,EAAE,CAAA;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,YAAY,EAAE,CAAA;QAC7D,MAAM,QAAQ,GAAG,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;QAChF,GAAG,CAAC,IAAI,CAAC;YACP,QAAQ,EAAE,CAAC;YACX,WAAW,EAAE,EAAE,GAAG,CAAC;YACnB,aAAa,EAAE,IAAI;YACnB,qBAAqB,EAAE,uBAAuB;YAC9C,4BAA4B,EAAE,EAAE;YAChC,SAAS,EAAE,GAAG,QAAQ,YAAY;YAClC,iBAAiB,EAAE,GAAG,QAAQ,qBAAqB;YACnD,WAAW,EAAE,KAAK;YAClB,SAAS,EAAE,KAAK;YAChB,aAAa,EAAE,CAAC;YAChB,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC;gBAC3B,SAAS,EAAE,KAAK;gBAChB,SAAS,EAAE,GAAG;aACf,CAAC;YACF,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC;gBAC7B;oBACE,QAAQ;oBACR,QAAQ,EAAE,KAAK;oBACf,eAAe,EAAE,WAAW;oBAC5B,oBAAoB,EAAE,WAAW;oBACjC,GAAG,QAAQ;iBACZ;aACF,CAAC;SACH,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport { FastStats } from './stats'\nimport { ThrottleConfig, ThrottleRule } from '@vpalmisano/throttler'\nimport { Config } from './config'\nimport { Auth, google } from 'googleapis'\nimport { logger } from './utils'\nimport { sprintf } from 'sprintf-js'\nimport { PlotData, plotHtml } from './plot'\n\nconst log = logger('webrtcperf:scenarios')\n\nexport type StatsRow = Record<string, string | number>\n\n/**\n * It parses a CSV stats file and returns an array of objects representing each row.\n * @param filePath The path to the CSV stats file.\n * @returns An array of objects where each object represents a row in the CSV file with keys as column headers.\n */\nexport async function parseStatsFile(filePath: string) {\n log.debug(`parseStatsFile: ${filePath}`)\n const fileData = await fs.promises.readFile(filePath, 'utf-8')\n const lines = fileData.split('\\n')\n const headers = lines[0].split(',')\n const data = lines.slice(1).map(line =>\n line.split(',').reduce((acc, value, index) => {\n if (value !== '') {\n acc[headers[index]] = isNaN(Number(value)) ? value : Number(value)\n }\n return acc\n }, {} as StatsRow),\n )\n return data\n}\n\nexport type StatsSummary = {\n timestamp: number\n id: string\n scenario: string\n videoRecvBitratePerPixel: FastStats\n videoRecvFps: FastStats\n videoSentFps: FastStats\n}\n\n/**\n * It aggregates the stats summary from multiple test runs in a directory.\n * @param options.dirPath Directory path containing test run subdirectories. Default is 'logs'.\n * @param options.senderParticipantName Participant name of the sender. Default is 'Participant-000001'.\n * @param options.receiverParticipantName Participant name of the receiver. Default is 'Participant-000000'.\n * @param options.nameParser Function to parse test directory names. Default splits by '_' and extracts id and scenario.\n * @returns Array of aggregated stats including timestamp, id, scenario, videoRecvBitratePerPixel, videoRecvFps, and videoSentFps.\n */\nexport async function aggregateStatsSummary({\n dirPath = 'logs',\n senderParticipantName = 'Participant-000001',\n receiverParticipantName = 'Participant-000000',\n nameParser = (name: string) => {\n const [_, id, scenario] = name.split('_')\n return { id, scenario }\n },\n}) {\n log.debug(`aggregateStatsSummary: ${dirPath}`)\n const stats: StatsSummary[] = []\n const results = await fs.promises.readdir(dirPath)\n for (const test of results) {\n const filePath = path.join(dirPath, test, 'detailed-stats-summary.csv')\n if (!fs.existsSync(filePath)) continue\n const timestamp = fs.statSync(path.join(dirPath, test)).ctime.getTime()\n const data = await parseStatsFile(filePath)\n const { id, scenario } = nameParser(test)\n\n const aggregated = {\n timestamp,\n id,\n scenario,\n videoRecvBitratePerPixel: new FastStats(),\n videoRecvFps: new FastStats(),\n videoSentFps: new FastStats(),\n }\n data.forEach(v => {\n const { participantName, trackId } = v as { participantName: string; trackId: string }\n const metrics = v as Record<string, number>\n if (participantName === receiverParticipantName) {\n if (trackId?.endsWith('-v') && metrics.videoRecvFrames > 0) {\n const videoRecvBitratePerPixel =\n metrics.videoRecvBitrates / (metrics.videoRecvWidth * metrics.videoRecvHeight)\n if (!isNaN(videoRecvBitratePerPixel)) aggregated.videoRecvBitratePerPixel.push(videoRecvBitratePerPixel)\n if (!isNaN(metrics.videoRecvFps)) aggregated.videoRecvFps.push(metrics.videoRecvFps)\n }\n } else if (participantName === senderParticipantName) {\n if (trackId?.endsWith('-v') && metrics.videoSentFrames > 0) {\n if (!isNaN(metrics.videoSentFps)) aggregated.videoSentFps.push(metrics.videoSentFps)\n }\n }\n })\n stats.push(aggregated)\n }\n return stats.sort((a, b) => a.timestamp - b.timestamp)\n}\n\n/**\n * It uploads the aggregated stats to a Google Sheet.\n * A valid Google service account credentials file must be specified\n * in the `GOOGLE_CREDENTIALS_PATH` environment variable.\n * @param stats The aggregated stats to upload.\n * @param spreadsheetId The ID of the Google Spreadsheet.\n * @param table The name of the table (sheet) within the spreadsheet. Default is 'data'.\n */\nexport async function uploadStatsToGoogleSheet(stats: StatsSummary[], spreadsheetId: string, table = 'data') {\n log.debug(`uploadResultsToGoogleSheet spreadsheetId: ${spreadsheetId} table: ${table}`)\n if (!process.env.GOOGLE_CREDENTIALS_PATH) throw new Error('GOOGLE_CREDENTIALS_PATH environment variable is not set')\n if (!fs.existsSync(process.env.GOOGLE_CREDENTIALS_PATH))\n throw new Error(`Google credentials file not found: ${process.env.GOOGLE_CREDENTIALS_PATH}`)\n if (!stats.length) return\n const auth = new Auth.GoogleAuth({\n keyFile: process.env.GOOGLE_CREDENTIALS_PATH,\n scopes: ['https://www.googleapis.com/auth/spreadsheets'],\n })\n const sheets = google.sheets({ version: 'v4', auth })\n // Update headers.\n const headers = ['datetime', 'id', 'scenario', 'videoRecvBitratePerPixel', 'videoRecvFps']\n await sheets.spreadsheets.values.update({\n spreadsheetId,\n range: `${table}!A1:E1`,\n valueInputOption: 'USER_ENTERED',\n requestBody: { majorDimension: 'ROWS', values: [headers] },\n })\n // Append values.\n const values = [] as string[][]\n stats.forEach(s => {\n const { timestamp, id, scenario, videoRecvBitratePerPixel, videoRecvFps } = s\n if (!videoRecvBitratePerPixel.length) return\n const datetime = new Date(timestamp).toLocaleString('en-US', {\n timeZone: 'UTC',\n hourCycle: 'h23',\n })\n values.push([\n datetime,\n id,\n formatThrottleRule(parseThrottleRule(scenario), true),\n videoRecvBitratePerPixel.percentile(95).toFixed(3),\n videoRecvFps.percentile(95).toFixed(3),\n ])\n })\n if (values.length) {\n await sheets.spreadsheets.values.append({\n spreadsheetId,\n range: `${table}!A:E`,\n valueInputOption: 'USER_ENTERED',\n insertDataOption: 'INSERT_ROWS',\n requestBody: { majorDimension: 'ROWS', values },\n })\n }\n}\n\nexport type ThrottleDirection = 'up' | 'down' | 'bidi'\n\nexport function formatBitrate(bitrate: number | undefined, prefix = ' ', pad = true) {\n if (bitrate === undefined) return ''\n let suffix = 'Kbps'\n if (bitrate >= 1000) {\n bitrate /= 1000\n suffix = 'Mbps'\n }\n return `${prefix}${sprintf(`%${pad ? '5' : ''}.4g`, bitrate)}${suffix}`\n}\n\nexport function formatLoss(loss: number | undefined, prefix = ' ', pad = true) {\n return loss !== undefined ? `${prefix}${loss.toFixed(0).padStart(pad ? 2 : 0, ' ')}%` : ''\n}\n\nexport function formatDelay(delay: number | undefined, prefix = ' ', pad = true) {\n return delay !== undefined ? `${prefix}${delay.toFixed(0).padStart(pad ? 3 : 0, ' ')}ms` : ''\n}\n\nexport function formatThrottleRule(\n throttleRule: ThrottleRule & { direction: ThrottleDirection },\n human = false,\n pad = true,\n) {\n const { rate, loss, delay, direction } = throttleRule\n return human\n ? `${direction.padEnd(pad ? 4 : 0, ' ')}${formatBitrate(rate, ' ', pad)}${formatLoss(loss, ' ', pad)}${formatDelay(delay, ' ', pad)}`\n : `${direction}-r${rate}-l${loss}-d${delay}`\n}\n\nexport function parseThrottleRule(throttleDesc: string) {\n const match = throttleDesc.match(/(up|down|bidi)-r(\\d+)-l([\\d.]+)-d(\\d+)/)\n if (!match) throw new Error(`Invalid throttle description: ${throttleDesc}`)\n const direction = match[1] as ThrottleDirection\n const rate = parseInt(match[2])\n const loss = parseInt(match[3])\n const delay = parseInt(match[4])\n return { direction, rate, loss, delay }\n}\n\nexport async function plotStatsSummary(stats: StatsSummary[]) {\n const labels = new Set<string>()\n const data = {} as Record<string, Record<string, FastStats>>\n stats.forEach(s => {\n const { id, scenario, videoRecvBitratePerPixel } = s\n if (!videoRecvBitratePerPixel.length) return null\n if (!data[id]) {\n data[id] = {}\n }\n const scenarioFormatted = formatThrottleRule(parseThrottleRule(scenario), true, true)\n if (!data[id][scenarioFormatted]) {\n data[id][scenarioFormatted] = new FastStats()\n }\n labels.add(scenarioFormatted)\n data[id][scenarioFormatted].push(videoRecvBitratePerPixel.percentile(95))\n })\n const series: PlotData[] = []\n Object.entries(data)\n .sort((a, b) => a[0].localeCompare(b[0]))\n .forEach(([id, data]) => {\n const plotData: PlotData = { label: id, data: [] }\n Object.entries(data)\n .sort((a, b) => a[0].localeCompare(b[0]))\n .forEach(([scenario, stats]) => {\n plotData.data.push({\n x: scenario,\n y: stats.percentile(50),\n yMin: stats.percentile(5),\n yMax: stats.percentile(95),\n })\n })\n series.push(plotData)\n })\n await plotHtml(\n {\n type: 'barWithErrorBars',\n xLabel: 'Scenario',\n yLabel: 'Video Receive Bitrate per Pixel',\n labels: Array.from(labels).sort((a, b) => a.localeCompare(b)),\n },\n series,\n )\n}\n\n/**\n * It generates a test configuration with a scenario including 2 participants.\n * The first participant sends video and the second receives it.\n * Both participants send and receive audio.\n * The network conditions are applied according to the specified direction to the sender (`up`),\n * the receiver (`down`) or both (`bidi`).\n * The test is repeated the specified number of times.\n * The output is an array of partial configuration objects that can be used to run the tests\n * with the main application, after merging it with a configuration that includes\n * the destination url (mandatory) and other optional parameters.\n * @param id The unique identifier for the test scenario.\n * @param options.rate The target bandwidth in kbps.\n * @param options.loss The packet loss percentage.\n * @param options.delay The network delay in milliseconds.\n * @param options.direction The direction of the network throttling: 'up', 'down', or 'bidi'.\n * @param repeat The number of times to repeat the test scenario. Default is 1.\n * @returns An array of partial configuration objects for each test scenario.\n */\nexport async function twoParticipantsWithRateLossDelay(\n id: string,\n { rate, loss, delay, direction }: { rate: number; loss: number; delay: number; direction: ThrottleDirection },\n repeat = 1,\n) {\n const throttle: ThrottleConfig = {}\n const queue = 25\n if (direction === 'down' || direction === 'bidi') {\n throttle.down = [\n { rate: 20000, loss: 0, delay: 0, queue },\n { rate, loss, delay, queue, at: 30 },\n ]\n }\n if (direction === 'up' || direction === 'bidi') {\n throttle.up = [\n { rate: 20000, loss: 0, delay, queue },\n { rate, loss, delay, queue, at: 30 },\n ]\n }\n const throttleDesc = formatThrottleRule({ rate, loss, delay, direction })\n const now = Date.now()\n const ret: Partial<Config>[] = []\n for (let i = 0; i < repeat; i++) {\n const basePath = `logs/${now}-${i + 1}_${id}_${throttleDesc}`\n const sessions = direction === 'bidi' ? '0-1' : direction === 'down' ? '0' : '1'\n ret.push({\n sessions: 2,\n runDuration: 60 * 3,\n debuggingPort: 9000,\n prometheusPushgateway: 'http://localhost:9091',\n prometheusPushgatewayJobName: id,\n statsPath: `${basePath}/stats.csv`,\n detailedStatsPath: `${basePath}/detailed-stats.csv`,\n showPageLog: false,\n showStats: false,\n statsInterval: 5,\n scriptParams: JSON.stringify({\n enableMic: '0-1',\n enableCam: '1',\n }),\n throttleConfig: JSON.stringify([\n {\n sessions,\n protocol: 'udp',\n skipSourcePorts: '53,80,443',\n skipDestinationPorts: '53,80,443',\n ...throttle,\n },\n ]),\n })\n }\n return ret\n}\n"]}
|
package/build/src/server.js
CHANGED
|
@@ -254,7 +254,7 @@ class Server {
|
|
|
254
254
|
throw new Error(`Session not found: "${sessionId}"`);
|
|
255
255
|
}
|
|
256
256
|
const filePath = await session.pageScreenshot(pageId, format);
|
|
257
|
-
res.sendFile(path_1.default.resolve(filePath));
|
|
257
|
+
res.sendFile(path_1.default.resolve(filePath), { dotfiles: 'allow' });
|
|
258
258
|
}
|
|
259
259
|
catch (err) {
|
|
260
260
|
next(err);
|
|
@@ -378,7 +378,7 @@ class Server {
|
|
|
378
378
|
if (req.query.range && !req.headers.range) {
|
|
379
379
|
req.headers.range = `bytes=${req.query.range}`;
|
|
380
380
|
}
|
|
381
|
-
res.sendFile(path_1.default.resolve(this.pageLogPath));
|
|
381
|
+
res.sendFile(path_1.default.resolve(this.pageLogPath), { dotfiles: 'allow' });
|
|
382
382
|
}
|
|
383
383
|
/**
|
|
384
384
|
* GET /view/docker.log endpoint.
|
|
@@ -398,7 +398,7 @@ class Server {
|
|
|
398
398
|
if (req.query.range && !req.headers.range) {
|
|
399
399
|
req.headers.range = `bytes=${req.query.range}`;
|
|
400
400
|
}
|
|
401
|
-
res.sendFile(path_1.default.resolve(logPath));
|
|
401
|
+
res.sendFile(path_1.default.resolve(logPath), { dotfiles: 'allow' });
|
|
402
402
|
}
|
|
403
403
|
catch (err) {
|
|
404
404
|
next(err);
|
|
@@ -451,7 +451,7 @@ class Server {
|
|
|
451
451
|
if (req.query.range && !req.headers.range) {
|
|
452
452
|
req.headers.range = `bytes=${req.query.range}`;
|
|
453
453
|
}
|
|
454
|
-
res.sendFile(fpath);
|
|
454
|
+
res.sendFile(fpath, { dotfiles: 'allow' });
|
|
455
455
|
}
|
|
456
456
|
getDataArchive(req, res, next) {
|
|
457
457
|
log.debug(`GET /data`, req.query);
|
|
@@ -473,7 +473,7 @@ class Server {
|
|
|
473
473
|
if (req.query.range && !req.headers.range) {
|
|
474
474
|
req.headers.range = `bytes=${req.query.range}`;
|
|
475
475
|
}
|
|
476
|
-
res.sendFile(fpath);
|
|
476
|
+
res.sendFile(fpath, { dotfiles: 'allow' });
|
|
477
477
|
}
|
|
478
478
|
/**
|
|
479
479
|
* Starts a new {@link Session} instance.
|
package/build/src/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import compression from 'compression'\nimport { timingSafeEqual } from 'crypto'\nimport express, { json } from 'express'\nimport fs from 'fs'\nimport { Server as HttpServer, createServer } from 'http'\nimport { Server as HttpsServer, createServer as _createServer } from 'https'\nimport os from 'os'\nimport path from 'path'\nimport tar from 'tar-fs'\nimport { WebSocketServer } from 'ws'\nimport zlib from 'zlib'\nimport auth from 'basic-auth'\n\nimport { Config, loadConfig } from './config'\nimport { Session, SessionParams } from './session'\nimport { Stats } from './stats'\nimport { logger, runShellCommand, getDockerLogsPath } from './utils'\nimport { getSessionThrottleIndex } from '@vpalmisano/throttler'\nimport { MediaPath, prepareFakeMedia } from './media'\n\nconst log = logger('webrtcperf:server')\n\n/**\n * An HTTP server instance that allows to control the tool using a REST\n * interface. Moreover, it allows to aggregate stats data coming from multiple\n * running tool instances.\n */\nexport class Server {\n /** The server listening port. */\n readonly serverPort: number\n /** The basic auth secret. */\n readonly serverSecret: string\n /** If HTTPS protocol should be used. */\n readonly serverUseHttps: boolean\n /** An optional path that the HTTP server will expose with the /data endpoint. */\n serverData: string\n /** The file path that will be used to serve the \\`/view/page.log\\` requests. */\n pageLogPath: string\n /** The path that will be used to serve the \\`/cache\\` requests. */\n videoCachePath: string\n /** A {@link Stats} class instance. */\n stats: Stats\n\n private app: express.Express\n private server: HttpServer | HttpsServer | null = null\n private wss: WebSocketServer | null = null\n\n /**\n * Server instance.\n * All the HTTP endpoints are protected by basic authentication with user\n * `admin` and password {@link Server.serverSecret}.\n * @param serverPort The server listening port.\n * @param serverSecret The basic auth secret.\n * @param serverUseHttps If HTTPS protocol should be used.\n * @param serverData An optional path that the HTTP server will expose with the /data endpoint.\n * @param pageLogPath The file path that will be used to serve the \\`/view/page.log\\` requests.\n * @param videoCachePath The path that will be used to serve the \\`/cache\\` requests.\n * @param stats A {@link Stats} class instance.\n */\n constructor(\n {\n serverPort = 5000,\n serverSecret = 'secret',\n serverUseHttps = false,\n serverData = '',\n pageLogPath = '',\n videoCachePath = '',\n } = {},\n stats: Stats,\n ) {\n this.serverPort = serverPort\n this.serverSecret = serverSecret\n this.serverUseHttps = serverUseHttps\n this.serverData = serverData\n this.pageLogPath = pageLogPath\n this.videoCachePath = videoCachePath\n this.stats = stats\n //\n this.app = express()\n this.app.use(compression())\n this.app.use(\n json({\n limit: '10mb',\n }),\n )\n\n this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (req.query.auth === this.serverSecret) {\n return next()\n }\n const credentials = auth(req)\n if (!credentials || credentials.name !== 'admin' || credentials.pass !== this.serverSecret) {\n res.setHeader('WWW-Authenticate', 'Basic realm=\"Restricted Area\"')\n res.status(401).send('Unauthorized')\n return\n }\n next()\n })\n\n this.app.get('/', (_req, res) => {\n res.send('')\n })\n\n this.app.get('/stats', this.getStats.bind(this))\n this.app.get('/collected-stats', this.getCollectedStats.bind(this))\n this.app.get('/screenshot/:sessionId', this.getScreenshot.bind(this))\n this.app.put('/collected-stats', this.putCollectedStats.bind(this))\n this.app.put('/session', this.putSession.bind(this))\n this.app.put('/sessions', this.putSessions.bind(this))\n this.app.delete('/session', this.deleteSession.bind(this))\n this.app.delete('/sessions', this.deleteSessions.bind(this))\n this.app.get('/view/page.log', this.getPageLog.bind(this))\n this.app.get('/view/docker.log', this.getDockerLog.bind(this))\n this.app.get('/download/alert-rules', this.getAlertRules.bind(this))\n this.app.get('/download/stats', this.getStatsFile.bind(this))\n this.app.get('/download/detailed-stats', this.getDetailedStatsFile.bind(this))\n this.app.get('/empty-page', this.getEmptyPage.bind(this))\n if (this.serverData) {\n log.debug(`using serverData: ${this.serverData}`)\n fs.promises.mkdir(this.serverData, { recursive: true }).catch(err => {\n log.error(`mkdir ${this.serverData} error: ${err.message}`)\n })\n this.app.get('/data', this.getDataArchive.bind(this))\n this.app.get('/data/:path', this.getData.bind(this))\n }\n if (this.videoCachePath) {\n log.debug(`using videoCachePath: ${this.videoCachePath}`)\n fs.promises.mkdir(this.videoCachePath, { recursive: true }).catch(err => {\n log.error(`mkdir ${this.videoCachePath} error: ${err.message}`)\n })\n this.app.get('/cache/:path', this.getCache.bind(this))\n }\n\n this.app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {\n log.error(`request path=${req.path} error:`, err.stack)\n if (res.headersSent) {\n return next(err)\n }\n res.status(500).send(err.message)\n })\n }\n\n /*\n * onConnection\n * @param {Socket} socket\n */\n /* onConnection(socket) {\n log.debug('onConnection', socket);\n\n socket.on('disconnect', () => {\n log.debug('io socket disconnected');\n });\n\n socket.on('message', (msg) => {\n log.debug('message', msg);\n });\n } */\n\n /**\n * GET /stats endpoint.\n *\n * Returns a JSON array of the last statistics for each running Session.\n */\n private async getStats(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`GET /stats`)\n const stats = []\n try {\n for (const session of this.stats.sessions.values()) {\n stats.push(session.stats)\n }\n res.json(stats)\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * GET /download/stats endpoint.\n *\n * Returns the {@link Stats.statsWriter} file content.\n */\n private getStatsFile(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`/download/stats`, req.query)\n if (!this.stats.statsWriter) {\n return next(new Error('statsPath not set'))\n }\n res.download(this.stats.statsPath)\n }\n\n /**\n * GET /download/detailed-stats endpoint.\n *\n * Returns the {@link Stats.detailedStatsWriter} file content.\n */\n private getDetailedStatsFile(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`/download/detailed-stats`, req.query)\n if (!this.stats.detailedStatsWriter) {\n return next(new Error('detailedStatsPath not set'))\n }\n res.download(this.stats.detailedStatsPath)\n }\n\n /**\n * GET /collected-stats endpoint.\n *\n * Returns a JSON array of the last statistics collected from external running\n * tools.\n */\n private getCollectedStats(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`GET /collected-stats`)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stats: Record<string, any> = {}\n try {\n for (const [key, stat] of Object.entries(this.stats.collectedStats)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n stats[key] = (stat as any).data\n }\n res.json(stats)\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * GET /screenshot/<sessionID> endpoint.\n *\n * Returns the page screenshot running inside the {@link Session} identified\n * by `sessionID`.\n * Additional query params:\n * - `page`: the page number (starting from `0`) running inside the {@link Session}.\n * - `format`: the image format (`jpeg`, `png`, `webp`). Default: `webp`.\n */\n private async getScreenshot(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n const sessionId = parseInt(req.params.sessionId)\n const pageId = parseInt((req.query.page as string) || '0')\n const format = (req.query.format as string) || 'webp'\n log.debug(`GET /screenshot/${sessionId} page=${pageId} format=${format}`)\n try {\n const session = this.stats.sessions.get(sessionId)\n if (!session) {\n throw new Error(`Session not found: \"${sessionId}\"`)\n }\n const filePath = await session.pageScreenshot(pageId, format)\n res.sendFile(path.resolve(filePath))\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * PUT /collected-stats endpoint.\n *\n * Allows to inject {@link Stats} metrics coming from an external tool.\n */\n private putCollectedStats(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`PUT /collected-stats`)\n const { id, stats, config } = req.body\n try {\n this.stats.addExternalCollectedStats(id, stats, config)\n res.json({\n message: `Collected stats added`,\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * PUT /session endpoint.\n *\n * Starts a new {@link Session}.\n * The request body format will be parsed as a {@link SessionParams} object.\n */\n private async putSession(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`PUT /session`, req.body)\n try {\n const config = req.body as Config\n const id = this.stats.consumeSessionId(config.tabsPerSession)\n await this.startLocalSession(id, req.body)\n res.json({\n message: `Session created`,\n data: { id },\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * PUT /sessions endpoint.\n *\n * Starts multiple {@link Session} instances as specified into the\n * `body.sessions` value.\n * The request body will be parsed as a {@link SessionParams} object.\n */\n private async putSessions(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`PUT /sessions`, req.body)\n try {\n const { sessions, tabsPerSession } = req.body as Config\n const sessionsIds = []\n for (let i = 0; i < sessions; i++) {\n const id = this.stats.consumeSessionId(tabsPerSession)\n await this.startLocalSession(id, req.body)\n sessionsIds.push(id)\n }\n res.json({\n message: `${sessions} sessions created`,\n data: { ids: sessionsIds },\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * DELETE /session endpoint.\n *\n * Delete the {@link Session} instance identified by the `body.id` param.\n */\n private async deleteSession(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`DELETE /session`, req.body)\n try {\n const { id } = req.body\n await this.stopLocalSession(id)\n res.json({\n message: `Session deleted`,\n data: { id },\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * DELETE /sessions endpoint.\n *\n * Delete the {@link Session} instances specified by the `body.ids` array.\n */\n private async deleteSessions(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`DELETE /sessions`, req.body)\n try {\n const { ids } = req.body\n for (const id of ids) {\n await this.stopLocalSession(id)\n }\n res.json({\n message: `${ids.length} sessions deleted`,\n data: { ids },\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * GET /view/page.log endpoint.\n *\n * Returns the page log file content as specified in {@link Config} `pageLogPath`.\n */\n private getPageLog(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`GET /view/page.log`, req.query)\n if (!this.pageLogPath) {\n return next(new Error('pageLogPath not set'))\n }\n if (req.query.range && !req.headers.range) {\n req.headers.range = `bytes=${req.query.range}`\n }\n res.sendFile(path.resolve(this.pageLogPath))\n }\n\n /**\n * GET /view/docker.log endpoint.\n *\n * Returns the Docker logs related to the container running the tool.\n * It requires to run the Docker container with the following options:\n * ```\n --cidfile /tmp/docker.id\n -v /tmp/docker.id:/root/.webrtcperf/docker.id:ro\n -v /var/lib/docker:/var/lib/docker:ro\n * ```\n */\n private async getDockerLog(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`GET /view/docker.log`, req.query)\n try {\n const logPath = await getDockerLogsPath()\n if (req.query.range && !req.headers.range) {\n req.headers.range = `bytes=${req.query.range}`\n }\n res.sendFile(path.resolve(logPath))\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * GET /download/alert-rules endpoint.\n *\n * Downloads the alert rules report stored into the {@link Stats.alertRulesOutput}.\n */\n private getAlertRules(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`GET /download/alert-rules`, req.query)\n if (!this.stats.alertRulesOutput) {\n return next(new Error('Stats alertRulesOutput not set'))\n }\n res.download(this.stats.alertRulesOutput)\n }\n\n /**\n * GET /empty-page endpoint.\n *\n * Returns an empty HTML page. Useful for running tests with raw Javascript\n * content without any DOM rendering.\n */\n private getEmptyPage(req: express.Request, res: express.Response): void {\n log.debug(`GET /empty-page`, req.query)\n const title = req.query.title || 'EmptyPage'\n res.send(`<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${title}</title>\n</head>\n<body></body>\n</html>`)\n }\n\n /**\n * GET /data/* endpoint.\n *\n * Returns the file content relative to the {@link Config} `serverData` path.\n * If the requested path points to a directory, it returns the directory\n * content in tar.gz format.\n */\n private getData(req: express.Request, res: express.Response, next: express.NextFunction): void {\n const paramPath = path.normalize(req.params.path).replace(/^(\\.\\.(\\/|\\\\|$))+/, '')\n log.debug(`GET /data/${paramPath}`, req.query)\n const fpath = path.resolve(this.serverData, paramPath)\n if (!fs.existsSync(fpath)) {\n return next(new Error(`${paramPath} not found`))\n }\n if (req.query.range && !req.headers.range) {\n req.headers.range = `bytes=${req.query.range}`\n }\n res.sendFile(fpath)\n }\n\n private getDataArchive(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`GET /data`, req.query)\n const fpath = path.resolve(this.serverData)\n if (!fs.lstatSync(fpath).isDirectory()) {\n return next(new Error(`${fpath} is not a directory`))\n }\n res.header('Content-Disposition', `attachment; filename=\"${path.basename(fpath)}.tar.gz\"`)\n res.setHeader('content-type', 'application/gzip')\n tar.pack(fpath).pipe(zlib.createGzip()).pipe(res)\n }\n\n private getCache(req: express.Request, res: express.Response, next: express.NextFunction): void {\n const paramPath = path.normalize(req.params.path).replace(/^(\\.\\.(\\/|\\\\|$))+/, '')\n log.debug(`GET /cache/${paramPath}`, req.query)\n const fpath = path.resolve(this.videoCachePath, paramPath)\n if (!fs.existsSync(fpath)) {\n return next(new Error(`${paramPath} not found`))\n }\n if (req.query.range && !req.headers.range) {\n req.headers.range = `bytes=${req.query.range}`\n }\n res.sendFile(fpath)\n }\n\n /**\n * Starts a new {@link Session} instance.\n * @param id The session unique id.\n * @param config The session configuration.\n */\n private async startLocalSession(id: number, config: SessionParams): Promise<Session> {\n const configs = await loadConfig(undefined, config)\n const sessionConfig = configs[0]\n const throttleIndex = getSessionThrottleIndex(id)\n const spawnPeriod = 1000 / sessionConfig.spawnRate\n\n // Prepare fake video and audio.\n const mediaPaths: MediaPath[] = []\n if (sessionConfig.videoPath) {\n for (const videoPath of sessionConfig.videoPath.split(',')) {\n const ret = await prepareFakeMedia({ ...sessionConfig, videoPath })\n mediaPaths.push(ret)\n }\n }\n const mediaPath = mediaPaths.length ? mediaPaths[id % mediaPaths.length] : undefined\n\n const session = new Session({ ...sessionConfig, throttleIndex, spawnPeriod, mediaPath, id })\n session.once('stop', () => {\n console.warn(`Session ${id} stopped, reloading...`)\n setTimeout(this.startLocalSession.bind(this), spawnPeriod, id, config)\n })\n this.stats.addSession(session)\n try {\n await session.start()\n } catch (err) {\n this.stats.removeSession(session.id)\n throw err\n }\n return session\n }\n\n /**\n * Stops a new {@link Session} instance.\n * @param {number} id The session unique id.\n */\n private async stopLocalSession(id: number): Promise<void> {\n const session = this.stats.sessions.get(id)\n if (!session) {\n log.warn(`stopLocalSession session ${id} not found`)\n return\n }\n session.removeAllListeners()\n this.stats.removeSession(id)\n await session.stop()\n }\n\n /**\n * Starts the {@link Server} instance.\n */\n async start(): Promise<void> {\n log.debug('start')\n if (this.serverUseHttps) {\n const destDir = path.join(os.homedir(), '.webrtcperf/ssl')\n const keyPath = path.join(destDir, 'domain.key')\n const crtPath = path.join(destDir, 'domain.crt')\n if (!fs.existsSync(keyPath) || !fs.existsSync(crtPath)) {\n await runShellCommand(\n `mkdir -p ${destDir} && openssl req -newkey rsa:2048 -nodes -keyout ${keyPath} -x509 -days 365 -out ${crtPath} -subj \"/C=EU/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com\"`,\n )\n }\n this.server = _createServer(\n {\n key: fs.readFileSync(keyPath),\n cert: fs.readFileSync(crtPath),\n },\n this.app,\n )\n } else {\n this.server = createServer(this.app)\n }\n\n // WebSocket endpoint.\n const wss = new WebSocketServer({ noServer: true })\n wss.on('connection', (ws, request) => {\n try {\n const query = new URLSearchParams(request.url?.split('?')[1] || '')\n const action = query.get('action') || ''\n log.debug(`ws connection from ${request.socket.remoteAddress} action: ${action}`)\n switch (action) {\n case 'write-stream': {\n if (!this.serverData) {\n throw new Error('serverData option not set')\n }\n const filename = query.get('filename') || ''\n if (!filename) {\n throw new Error('filename not set')\n }\n const paramPath = path.normalize(filename).replace(/^(\\.\\.(\\/|\\\\|$))+/, '')\n\n log.debug(`ws write-stream ${paramPath}`)\n const fpath = path.resolve(this.serverData, paramPath)\n if (fs.existsSync(fpath)) {\n throw new Error(`file already exists: ${fpath}`)\n }\n const stream = fs.createWriteStream(fpath)\n\n let headerWritten = false\n let framesWritten = 0\n\n const close = async () => {\n stream.close()\n ws.close()\n\n try {\n if (!framesWritten) {\n await fs.promises.unlink(fpath)\n }\n } catch (err) {\n log.error(`ws write-stream close error: ${(err as Error).message}`)\n }\n }\n\n stream.on('error', (err: Error) => {\n log.error(`ws write-stream error: ${err.message}`)\n void close()\n })\n\n ws.on('error', (err: Error) => {\n log.error(`ws write-stream error: ${err.message}`)\n void close()\n })\n\n ws.on('close', () => {\n log.debug(`ws write-stream close`)\n void close()\n })\n\n ws.on('message', (data: Uint8Array) => {\n if (!data?.byteLength) return\n if (!headerWritten) {\n stream.write(data)\n headerWritten = true\n return\n }\n stream.write(data)\n framesWritten++\n })\n\n break\n }\n default:\n throw new Error(`invalid action: ${action}`)\n }\n } catch (err) {\n log.error(`ws connection error: ${(err as Error).message}`)\n ws.close()\n }\n })\n this.wss = wss\n\n this.server.on('upgrade', (request, socket, head) => {\n log.debug(`ws upgrade ${request.url}`)\n try {\n const query = new URLSearchParams(request.url?.split('?')[1] || '')\n const auth = query.get('auth')\n if (!auth || !timingSafeEqual(Buffer.from(auth), Buffer.from(this.serverSecret))) {\n throw new Error('invalid auth')\n }\n } catch (err) {\n log.error(`ws upgrade error: ${(err as Error).message}`)\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n\n wss.handleUpgrade(request, socket, head, ws => {\n wss.emit('connection', ws, request)\n })\n })\n\n this.server.listen(this.serverPort, () => {\n log.debug(`HTTPS server listening on port ${this.serverPort}`)\n })\n }\n\n /**\n * Stops the {@link Server} instance.\n */\n stop(): void {\n if (this.wss) {\n this.wss.close()\n this.wss = null\n }\n if (this.server) {\n log.debug('stop')\n this.server.close()\n this.server = null\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import compression from 'compression'\nimport { timingSafeEqual } from 'crypto'\nimport express, { json } from 'express'\nimport fs from 'fs'\nimport { Server as HttpServer, createServer } from 'http'\nimport { Server as HttpsServer, createServer as _createServer } from 'https'\nimport os from 'os'\nimport path from 'path'\nimport tar from 'tar-fs'\nimport { WebSocketServer } from 'ws'\nimport zlib from 'zlib'\nimport auth from 'basic-auth'\n\nimport { Config, loadConfig } from './config'\nimport { Session, SessionParams } from './session'\nimport { Stats } from './stats'\nimport { logger, runShellCommand, getDockerLogsPath } from './utils'\nimport { getSessionThrottleIndex } from '@vpalmisano/throttler'\nimport { MediaPath, prepareFakeMedia } from './media'\n\nconst log = logger('webrtcperf:server')\n\n/**\n * An HTTP server instance that allows to control the tool using a REST\n * interface. Moreover, it allows to aggregate stats data coming from multiple\n * running tool instances.\n */\nexport class Server {\n /** The server listening port. */\n readonly serverPort: number\n /** The basic auth secret. */\n readonly serverSecret: string\n /** If HTTPS protocol should be used. */\n readonly serverUseHttps: boolean\n /** An optional path that the HTTP server will expose with the /data endpoint. */\n serverData: string\n /** The file path that will be used to serve the \\`/view/page.log\\` requests. */\n pageLogPath: string\n /** The path that will be used to serve the \\`/cache\\` requests. */\n videoCachePath: string\n /** A {@link Stats} class instance. */\n stats: Stats\n\n private app: express.Express\n private server: HttpServer | HttpsServer | null = null\n private wss: WebSocketServer | null = null\n\n /**\n * Server instance.\n * All the HTTP endpoints are protected by basic authentication with user\n * `admin` and password {@link Server.serverSecret}.\n * @param serverPort The server listening port.\n * @param serverSecret The basic auth secret.\n * @param serverUseHttps If HTTPS protocol should be used.\n * @param serverData An optional path that the HTTP server will expose with the /data endpoint.\n * @param pageLogPath The file path that will be used to serve the \\`/view/page.log\\` requests.\n * @param videoCachePath The path that will be used to serve the \\`/cache\\` requests.\n * @param stats A {@link Stats} class instance.\n */\n constructor(\n {\n serverPort = 5000,\n serverSecret = 'secret',\n serverUseHttps = false,\n serverData = '',\n pageLogPath = '',\n videoCachePath = '',\n } = {},\n stats: Stats,\n ) {\n this.serverPort = serverPort\n this.serverSecret = serverSecret\n this.serverUseHttps = serverUseHttps\n this.serverData = serverData\n this.pageLogPath = pageLogPath\n this.videoCachePath = videoCachePath\n this.stats = stats\n //\n this.app = express()\n this.app.use(compression())\n this.app.use(\n json({\n limit: '10mb',\n }),\n )\n\n this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (req.query.auth === this.serverSecret) {\n return next()\n }\n const credentials = auth(req)\n if (!credentials || credentials.name !== 'admin' || credentials.pass !== this.serverSecret) {\n res.setHeader('WWW-Authenticate', 'Basic realm=\"Restricted Area\"')\n res.status(401).send('Unauthorized')\n return\n }\n next()\n })\n\n this.app.get('/', (_req, res) => {\n res.send('')\n })\n\n this.app.get('/stats', this.getStats.bind(this))\n this.app.get('/collected-stats', this.getCollectedStats.bind(this))\n this.app.get('/screenshot/:sessionId', this.getScreenshot.bind(this))\n this.app.put('/collected-stats', this.putCollectedStats.bind(this))\n this.app.put('/session', this.putSession.bind(this))\n this.app.put('/sessions', this.putSessions.bind(this))\n this.app.delete('/session', this.deleteSession.bind(this))\n this.app.delete('/sessions', this.deleteSessions.bind(this))\n this.app.get('/view/page.log', this.getPageLog.bind(this))\n this.app.get('/view/docker.log', this.getDockerLog.bind(this))\n this.app.get('/download/alert-rules', this.getAlertRules.bind(this))\n this.app.get('/download/stats', this.getStatsFile.bind(this))\n this.app.get('/download/detailed-stats', this.getDetailedStatsFile.bind(this))\n this.app.get('/empty-page', this.getEmptyPage.bind(this))\n if (this.serverData) {\n log.debug(`using serverData: ${this.serverData}`)\n fs.promises.mkdir(this.serverData, { recursive: true }).catch(err => {\n log.error(`mkdir ${this.serverData} error: ${err.message}`)\n })\n this.app.get('/data', this.getDataArchive.bind(this))\n this.app.get('/data/:path', this.getData.bind(this))\n }\n if (this.videoCachePath) {\n log.debug(`using videoCachePath: ${this.videoCachePath}`)\n fs.promises.mkdir(this.videoCachePath, { recursive: true }).catch(err => {\n log.error(`mkdir ${this.videoCachePath} error: ${err.message}`)\n })\n this.app.get('/cache/:path', this.getCache.bind(this))\n }\n\n this.app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {\n log.error(`request path=${req.path} error:`, err.stack)\n if (res.headersSent) {\n return next(err)\n }\n res.status(500).send(err.message)\n })\n }\n\n /*\n * onConnection\n * @param {Socket} socket\n */\n /* onConnection(socket) {\n log.debug('onConnection', socket);\n\n socket.on('disconnect', () => {\n log.debug('io socket disconnected');\n });\n\n socket.on('message', (msg) => {\n log.debug('message', msg);\n });\n } */\n\n /**\n * GET /stats endpoint.\n *\n * Returns a JSON array of the last statistics for each running Session.\n */\n private async getStats(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`GET /stats`)\n const stats = []\n try {\n for (const session of this.stats.sessions.values()) {\n stats.push(session.stats)\n }\n res.json(stats)\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * GET /download/stats endpoint.\n *\n * Returns the {@link Stats.statsWriter} file content.\n */\n private getStatsFile(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`/download/stats`, req.query)\n if (!this.stats.statsWriter) {\n return next(new Error('statsPath not set'))\n }\n res.download(this.stats.statsPath)\n }\n\n /**\n * GET /download/detailed-stats endpoint.\n *\n * Returns the {@link Stats.detailedStatsWriter} file content.\n */\n private getDetailedStatsFile(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`/download/detailed-stats`, req.query)\n if (!this.stats.detailedStatsWriter) {\n return next(new Error('detailedStatsPath not set'))\n }\n res.download(this.stats.detailedStatsPath)\n }\n\n /**\n * GET /collected-stats endpoint.\n *\n * Returns a JSON array of the last statistics collected from external running\n * tools.\n */\n private getCollectedStats(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`GET /collected-stats`)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stats: Record<string, any> = {}\n try {\n for (const [key, stat] of Object.entries(this.stats.collectedStats)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n stats[key] = (stat as any).data\n }\n res.json(stats)\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * GET /screenshot/<sessionID> endpoint.\n *\n * Returns the page screenshot running inside the {@link Session} identified\n * by `sessionID`.\n * Additional query params:\n * - `page`: the page number (starting from `0`) running inside the {@link Session}.\n * - `format`: the image format (`jpeg`, `png`, `webp`). Default: `webp`.\n */\n private async getScreenshot(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n const sessionId = parseInt(req.params.sessionId)\n const pageId = parseInt((req.query.page as string) || '0')\n const format = (req.query.format as string) || 'webp'\n log.debug(`GET /screenshot/${sessionId} page=${pageId} format=${format}`)\n try {\n const session = this.stats.sessions.get(sessionId)\n if (!session) {\n throw new Error(`Session not found: \"${sessionId}\"`)\n }\n const filePath = await session.pageScreenshot(pageId, format)\n res.sendFile(path.resolve(filePath), { dotfiles: 'allow' })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * PUT /collected-stats endpoint.\n *\n * Allows to inject {@link Stats} metrics coming from an external tool.\n */\n private putCollectedStats(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`PUT /collected-stats`)\n const { id, stats, config } = req.body\n try {\n this.stats.addExternalCollectedStats(id, stats, config)\n res.json({\n message: `Collected stats added`,\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * PUT /session endpoint.\n *\n * Starts a new {@link Session}.\n * The request body format will be parsed as a {@link SessionParams} object.\n */\n private async putSession(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`PUT /session`, req.body)\n try {\n const config = req.body as Config\n const id = this.stats.consumeSessionId(config.tabsPerSession)\n await this.startLocalSession(id, req.body)\n res.json({\n message: `Session created`,\n data: { id },\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * PUT /sessions endpoint.\n *\n * Starts multiple {@link Session} instances as specified into the\n * `body.sessions` value.\n * The request body will be parsed as a {@link SessionParams} object.\n */\n private async putSessions(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`PUT /sessions`, req.body)\n try {\n const { sessions, tabsPerSession } = req.body as Config\n const sessionsIds = []\n for (let i = 0; i < sessions; i++) {\n const id = this.stats.consumeSessionId(tabsPerSession)\n await this.startLocalSession(id, req.body)\n sessionsIds.push(id)\n }\n res.json({\n message: `${sessions} sessions created`,\n data: { ids: sessionsIds },\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * DELETE /session endpoint.\n *\n * Delete the {@link Session} instance identified by the `body.id` param.\n */\n private async deleteSession(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`DELETE /session`, req.body)\n try {\n const { id } = req.body\n await this.stopLocalSession(id)\n res.json({\n message: `Session deleted`,\n data: { id },\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * DELETE /sessions endpoint.\n *\n * Delete the {@link Session} instances specified by the `body.ids` array.\n */\n private async deleteSessions(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`DELETE /sessions`, req.body)\n try {\n const { ids } = req.body\n for (const id of ids) {\n await this.stopLocalSession(id)\n }\n res.json({\n message: `${ids.length} sessions deleted`,\n data: { ids },\n })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * GET /view/page.log endpoint.\n *\n * Returns the page log file content as specified in {@link Config} `pageLogPath`.\n */\n private getPageLog(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`GET /view/page.log`, req.query)\n if (!this.pageLogPath) {\n return next(new Error('pageLogPath not set'))\n }\n if (req.query.range && !req.headers.range) {\n req.headers.range = `bytes=${req.query.range}`\n }\n res.sendFile(path.resolve(this.pageLogPath), { dotfiles: 'allow' })\n }\n\n /**\n * GET /view/docker.log endpoint.\n *\n * Returns the Docker logs related to the container running the tool.\n * It requires to run the Docker container with the following options:\n * ```\n --cidfile /tmp/docker.id\n -v /tmp/docker.id:/root/.webrtcperf/docker.id:ro\n -v /var/lib/docker:/var/lib/docker:ro\n * ```\n */\n private async getDockerLog(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {\n log.debug(`GET /view/docker.log`, req.query)\n try {\n const logPath = await getDockerLogsPath()\n if (req.query.range && !req.headers.range) {\n req.headers.range = `bytes=${req.query.range}`\n }\n res.sendFile(path.resolve(logPath), { dotfiles: 'allow' })\n } catch (err) {\n next(err)\n }\n }\n\n /**\n * GET /download/alert-rules endpoint.\n *\n * Downloads the alert rules report stored into the {@link Stats.alertRulesOutput}.\n */\n private getAlertRules(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`GET /download/alert-rules`, req.query)\n if (!this.stats.alertRulesOutput) {\n return next(new Error('Stats alertRulesOutput not set'))\n }\n res.download(this.stats.alertRulesOutput)\n }\n\n /**\n * GET /empty-page endpoint.\n *\n * Returns an empty HTML page. Useful for running tests with raw Javascript\n * content without any DOM rendering.\n */\n private getEmptyPage(req: express.Request, res: express.Response): void {\n log.debug(`GET /empty-page`, req.query)\n const title = req.query.title || 'EmptyPage'\n res.send(`<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${title}</title>\n</head>\n<body></body>\n</html>`)\n }\n\n /**\n * GET /data/* endpoint.\n *\n * Returns the file content relative to the {@link Config} `serverData` path.\n * If the requested path points to a directory, it returns the directory\n * content in tar.gz format.\n */\n private getData(req: express.Request, res: express.Response, next: express.NextFunction): void {\n const paramPath = path.normalize(req.params.path).replace(/^(\\.\\.(\\/|\\\\|$))+/, '')\n log.debug(`GET /data/${paramPath}`, req.query)\n const fpath = path.resolve(this.serverData, paramPath)\n if (!fs.existsSync(fpath)) {\n return next(new Error(`${paramPath} not found`))\n }\n if (req.query.range && !req.headers.range) {\n req.headers.range = `bytes=${req.query.range}`\n }\n res.sendFile(fpath, { dotfiles: 'allow' })\n }\n\n private getDataArchive(req: express.Request, res: express.Response, next: express.NextFunction): void {\n log.debug(`GET /data`, req.query)\n const fpath = path.resolve(this.serverData)\n if (!fs.lstatSync(fpath).isDirectory()) {\n return next(new Error(`${fpath} is not a directory`))\n }\n res.header('Content-Disposition', `attachment; filename=\"${path.basename(fpath)}.tar.gz\"`)\n res.setHeader('content-type', 'application/gzip')\n tar.pack(fpath).pipe(zlib.createGzip()).pipe(res)\n }\n\n private getCache(req: express.Request, res: express.Response, next: express.NextFunction): void {\n const paramPath = path.normalize(req.params.path).replace(/^(\\.\\.(\\/|\\\\|$))+/, '')\n log.debug(`GET /cache/${paramPath}`, req.query)\n const fpath = path.resolve(this.videoCachePath, paramPath)\n if (!fs.existsSync(fpath)) {\n return next(new Error(`${paramPath} not found`))\n }\n if (req.query.range && !req.headers.range) {\n req.headers.range = `bytes=${req.query.range}`\n }\n res.sendFile(fpath, { dotfiles: 'allow' })\n }\n\n /**\n * Starts a new {@link Session} instance.\n * @param id The session unique id.\n * @param config The session configuration.\n */\n private async startLocalSession(id: number, config: SessionParams): Promise<Session> {\n const configs = await loadConfig(undefined, config)\n const sessionConfig = configs[0]\n const throttleIndex = getSessionThrottleIndex(id)\n const spawnPeriod = 1000 / sessionConfig.spawnRate\n\n // Prepare fake video and audio.\n const mediaPaths: MediaPath[] = []\n if (sessionConfig.videoPath) {\n for (const videoPath of sessionConfig.videoPath.split(',')) {\n const ret = await prepareFakeMedia({ ...sessionConfig, videoPath })\n mediaPaths.push(ret)\n }\n }\n const mediaPath = mediaPaths.length ? mediaPaths[id % mediaPaths.length] : undefined\n\n const session = new Session({ ...sessionConfig, throttleIndex, spawnPeriod, mediaPath, id })\n session.once('stop', () => {\n console.warn(`Session ${id} stopped, reloading...`)\n setTimeout(this.startLocalSession.bind(this), spawnPeriod, id, config)\n })\n this.stats.addSession(session)\n try {\n await session.start()\n } catch (err) {\n this.stats.removeSession(session.id)\n throw err\n }\n return session\n }\n\n /**\n * Stops a new {@link Session} instance.\n * @param {number} id The session unique id.\n */\n private async stopLocalSession(id: number): Promise<void> {\n const session = this.stats.sessions.get(id)\n if (!session) {\n log.warn(`stopLocalSession session ${id} not found`)\n return\n }\n session.removeAllListeners()\n this.stats.removeSession(id)\n await session.stop()\n }\n\n /**\n * Starts the {@link Server} instance.\n */\n async start(): Promise<void> {\n log.debug('start')\n if (this.serverUseHttps) {\n const destDir = path.join(os.homedir(), '.webrtcperf/ssl')\n const keyPath = path.join(destDir, 'domain.key')\n const crtPath = path.join(destDir, 'domain.crt')\n if (!fs.existsSync(keyPath) || !fs.existsSync(crtPath)) {\n await runShellCommand(\n `mkdir -p ${destDir} && openssl req -newkey rsa:2048 -nodes -keyout ${keyPath} -x509 -days 365 -out ${crtPath} -subj \"/C=EU/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com\"`,\n )\n }\n this.server = _createServer(\n {\n key: fs.readFileSync(keyPath),\n cert: fs.readFileSync(crtPath),\n },\n this.app,\n )\n } else {\n this.server = createServer(this.app)\n }\n\n // WebSocket endpoint.\n const wss = new WebSocketServer({ noServer: true })\n wss.on('connection', (ws, request) => {\n try {\n const query = new URLSearchParams(request.url?.split('?')[1] || '')\n const action = query.get('action') || ''\n log.debug(`ws connection from ${request.socket.remoteAddress} action: ${action}`)\n switch (action) {\n case 'write-stream': {\n if (!this.serverData) {\n throw new Error('serverData option not set')\n }\n const filename = query.get('filename') || ''\n if (!filename) {\n throw new Error('filename not set')\n }\n const paramPath = path.normalize(filename).replace(/^(\\.\\.(\\/|\\\\|$))+/, '')\n\n log.debug(`ws write-stream ${paramPath}`)\n const fpath = path.resolve(this.serverData, paramPath)\n if (fs.existsSync(fpath)) {\n throw new Error(`file already exists: ${fpath}`)\n }\n const stream = fs.createWriteStream(fpath)\n\n let headerWritten = false\n let framesWritten = 0\n\n const close = async () => {\n stream.close()\n ws.close()\n\n try {\n if (!framesWritten) {\n await fs.promises.unlink(fpath)\n }\n } catch (err) {\n log.error(`ws write-stream close error: ${(err as Error).message}`)\n }\n }\n\n stream.on('error', (err: Error) => {\n log.error(`ws write-stream error: ${err.message}`)\n void close()\n })\n\n ws.on('error', (err: Error) => {\n log.error(`ws write-stream error: ${err.message}`)\n void close()\n })\n\n ws.on('close', () => {\n log.debug(`ws write-stream close`)\n void close()\n })\n\n ws.on('message', (data: Uint8Array) => {\n if (!data?.byteLength) return\n if (!headerWritten) {\n stream.write(data)\n headerWritten = true\n return\n }\n stream.write(data)\n framesWritten++\n })\n\n break\n }\n default:\n throw new Error(`invalid action: ${action}`)\n }\n } catch (err) {\n log.error(`ws connection error: ${(err as Error).message}`)\n ws.close()\n }\n })\n this.wss = wss\n\n this.server.on('upgrade', (request, socket, head) => {\n log.debug(`ws upgrade ${request.url}`)\n try {\n const query = new URLSearchParams(request.url?.split('?')[1] || '')\n const auth = query.get('auth')\n if (!auth || !timingSafeEqual(Buffer.from(auth), Buffer.from(this.serverSecret))) {\n throw new Error('invalid auth')\n }\n } catch (err) {\n log.error(`ws upgrade error: ${(err as Error).message}`)\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n\n wss.handleUpgrade(request, socket, head, ws => {\n wss.emit('connection', ws, request)\n })\n })\n\n this.server.listen(this.serverPort, () => {\n log.debug(`HTTPS server listening on port ${this.serverPort}`)\n })\n }\n\n /**\n * Stops the {@link Server} instance.\n */\n stop(): void {\n if (this.wss) {\n this.wss.close()\n this.wss = null\n }\n if (this.server) {\n log.debug('stop')\n this.server.close()\n this.server = null\n }\n }\n}\n"]}
|