@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.
@@ -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"]}
@@ -226,5 +226,9 @@ export declare class Stats extends events.EventEmitter {
226
226
  * writeAlertRulesReport
227
227
  */
228
228
  writeAlertRulesReport(): Promise<void>;
229
+ /**
230
+ * Reset the stats.
231
+ */
232
+ resetStats(): void;
229
233
  stop(): Promise<void>;
230
234
  }
@@ -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.collectedStats = this.initCollectedStats();
1431
- this.externalCollectedStats.clear();
1438
+ this.resetStats();
1432
1439
  }
1433
1440
  }
1434
1441
  exports.Stats = Stats;