@vpalmisano/webrtcperf 4.6.2 → 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/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/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/stats.d.ts
CHANGED
package/build/src/stats.js
CHANGED
|
@@ -1397,6 +1397,14 @@ class Stats extends events.EventEmitter {
|
|
|
1397
1397
|
log.error(`writeAlertRulesReport error: ${err.stack}`);
|
|
1398
1398
|
}
|
|
1399
1399
|
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Reset the stats.
|
|
1402
|
+
*/
|
|
1403
|
+
resetStats() {
|
|
1404
|
+
log.debug('resetStats');
|
|
1405
|
+
this.collectedStats = this.initCollectedStats();
|
|
1406
|
+
this.externalCollectedStats.clear();
|
|
1407
|
+
}
|
|
1400
1408
|
async stop() {
|
|
1401
1409
|
if (!this.running)
|
|
1402
1410
|
return;
|
|
@@ -1427,8 +1435,7 @@ class Stats extends events.EventEmitter {
|
|
|
1427
1435
|
this.gateway = null;
|
|
1428
1436
|
this.metrics = {};
|
|
1429
1437
|
}
|
|
1430
|
-
this.
|
|
1431
|
-
this.externalCollectedStats.clear();
|
|
1438
|
+
this.resetStats();
|
|
1432
1439
|
}
|
|
1433
1440
|
}
|
|
1434
1441
|
exports.Stats = Stats;
|