@vpalmisano/webrtcperf 4.1.11 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +700 -235
- package/app.min.js +1 -1
- package/build/src/app.js +30 -8
- package/build/src/app.js.map +1 -1
- package/build/src/config.d.ts +1 -0
- package/build/src/config.js +115 -9
- package/build/src/config.js.map +1 -1
- package/build/src/session.js +3 -2
- package/build/src/session.js.map +1 -1
- package/build/src/stats.js +19 -18
- package/build/src/stats.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +30 -27
- package/src/app.ts +36 -9
- package/src/config.ts +118 -10
- package/src/session.ts +3 -1
- package/src/stats.ts +4 -2
package/src/app.ts
CHANGED
|
@@ -2,9 +2,8 @@ import { getSessionThrottleIndex, startThrottle, stopThrottle } from '@vpalmisan
|
|
|
2
2
|
import { paramCase } from 'change-case'
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import json5 from 'json5'
|
|
5
|
-
import wrap from 'word-wrap'
|
|
6
5
|
|
|
7
|
-
import { Config, getConfigDocs, loadConfig } from './config'
|
|
6
|
+
import { Config, getConfigDocs, loadConfig, loadConfigFromPrompt } from './config'
|
|
8
7
|
import { MediaPath, prepareFakeMedia } from './media'
|
|
9
8
|
import { Server } from './server'
|
|
10
9
|
import { Session } from './session'
|
|
@@ -23,22 +22,34 @@ import {
|
|
|
23
22
|
import { calculateVisqolScore } from './visqol'
|
|
24
23
|
import { calculateVmafScore, convertToIvf, prepareVideo } from './vmaf'
|
|
25
24
|
import path from 'path'
|
|
25
|
+
import { markedTerminal } from 'marked-terminal'
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
|
+
const { marked } = require('marked')
|
|
29
|
+
marked.use(markedTerminal({ reflowText: true, tab: 2 }))
|
|
26
30
|
|
|
27
31
|
const log = logger('webrtcperf')
|
|
28
32
|
|
|
29
33
|
function showHelpOrVersion(): void {
|
|
30
|
-
if (process.argv.
|
|
34
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
31
35
|
const docs = getConfigDocs()
|
|
32
|
-
let out =
|
|
36
|
+
let out = marked.parse(`**Webrtcperf parameters**
|
|
37
|
+
|
|
38
|
+
\`--version\` It shows the package version.
|
|
39
|
+
`)
|
|
33
40
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
41
|
Object.entries(docs).forEach(([name, value]: [string, any]) => {
|
|
35
|
-
out +=
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
out += marked.parse(
|
|
43
|
+
`
|
|
44
|
+
\`--${paramCase(name)}\`
|
|
45
|
+
${value.doc}
|
|
46
|
+
Default value: \`${value.default}\`
|
|
47
|
+
`,
|
|
48
|
+
)
|
|
38
49
|
})
|
|
39
50
|
console.log(out)
|
|
40
51
|
process.exit(0)
|
|
41
|
-
} else if (process.argv.
|
|
52
|
+
} else if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
42
53
|
const version = json5.parse(fs.readFileSync(resolvePackagePath('package.json')).toString()).version
|
|
43
54
|
console.log(version)
|
|
44
55
|
process.exit(0)
|
|
@@ -189,7 +200,23 @@ export async function setupApplication(config: Config): Promise<{ stats: Stats;
|
|
|
189
200
|
async function main(): Promise<void> {
|
|
190
201
|
showHelpOrVersion()
|
|
191
202
|
|
|
192
|
-
|
|
203
|
+
let config: Config
|
|
204
|
+
|
|
205
|
+
if (process.argv.slice(2).includes('--prompt')) {
|
|
206
|
+
const params = await loadConfigFromPrompt(
|
|
207
|
+
process.argv
|
|
208
|
+
.slice(2)
|
|
209
|
+
.filter(s => !['--prompt', '--dry-run'].includes(s))
|
|
210
|
+
.join(' '),
|
|
211
|
+
)
|
|
212
|
+
if (process.argv.slice(2).includes('--dry-run')) {
|
|
213
|
+
console.log(json5.stringify(params, null, 2))
|
|
214
|
+
process.exit(0)
|
|
215
|
+
}
|
|
216
|
+
config = await loadConfig(undefined, params)
|
|
217
|
+
} else {
|
|
218
|
+
config = await loadConfig(process.argv[2])
|
|
219
|
+
}
|
|
193
220
|
|
|
194
221
|
if (config.vmafPrepareVideo) {
|
|
195
222
|
await prepareVideo(config, true)
|
package/src/config.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import convict, { addFormats } from 'convict'
|
|
1
|
+
import convict, { addFormats, SchemaObj } from 'convict'
|
|
2
2
|
import { ipaddress, url } from 'convict-format-with-validator'
|
|
3
3
|
import { existsSync } from 'fs'
|
|
4
4
|
import os from 'os'
|
|
5
|
-
import { join } from 'path'
|
|
5
|
+
import path, { join } from 'path'
|
|
6
6
|
import json5 from 'json5'
|
|
7
7
|
import yaml from 'yaml'
|
|
8
8
|
import toml from 'toml'
|
|
@@ -110,7 +110,7 @@ The temporary files containing the raw video and audio will be stored at \
|
|
|
110
110
|
\`\${VIDEO_CACHE_PATH}/video.\${VIDEO_FORMAT}\` and \
|
|
111
111
|
\`\${VIDEO_CACHE_PATH}/audio.wav\`.`,
|
|
112
112
|
format: String,
|
|
113
|
-
default: 'https://github.com/vpalmisano/webrtcperf/releases/download/
|
|
113
|
+
default: 'https://github.com/vpalmisano/webrtcperf/releases/download/videos-1.0/kt.mp4',
|
|
114
114
|
env: 'VIDEO_PATH',
|
|
115
115
|
arg: 'video-path',
|
|
116
116
|
},
|
|
@@ -190,7 +190,51 @@ seconds.`,
|
|
|
190
190
|
arg: 'run-duration',
|
|
191
191
|
},
|
|
192
192
|
throttleConfig: {
|
|
193
|
-
doc: `A JSON5 string with a valid
|
|
193
|
+
doc: `A JSON5 string with a valid throttler configuration (https://github.com/vpalmisano/throttler). \
|
|
194
|
+
Example: \
|
|
195
|
+
|
|
196
|
+
\`\`\`javascript
|
|
197
|
+
[{
|
|
198
|
+
sessions: '0-1',
|
|
199
|
+
device: 'eth0',
|
|
200
|
+
protocol: 'udp',
|
|
201
|
+
skipSourcePorts: "443",
|
|
202
|
+
skipDestinationPorts: "443",
|
|
203
|
+
filter: "--sports 443 --dports 443",
|
|
204
|
+
match: 'nbyte("ababa" at 12 layer 1)',
|
|
205
|
+
capture: 'capture.pcap',
|
|
206
|
+
up: {
|
|
207
|
+
rate: 1000,
|
|
208
|
+
delay: 50,
|
|
209
|
+
loss: 5,
|
|
210
|
+
queue: 10,
|
|
211
|
+
},
|
|
212
|
+
down: [
|
|
213
|
+
{ rate: 2000, delay: 50, delayJitter: 10, delayJitterCorrelation: 25, loss: 2, lossBurst: 2, queue: 20 },
|
|
214
|
+
{ rate: 1000, delay: 50, loss: 2, queue: 20, at: 60 },
|
|
215
|
+
]
|
|
216
|
+
}]
|
|
217
|
+
\`\`\`
|
|
218
|
+
- The sessions field represents the sessions IDs range that will be affected by the rule, e.g.: "0-10", "2,4" or simply "2".
|
|
219
|
+
- The device, protocol, up, down fields are optional. When device is not set, the default route device will be used. If protocol is specified ('udp' or 'tcp'), \
|
|
220
|
+
only the packets with the specified protocol will be affected by the shaping rules.
|
|
221
|
+
- The capture field is optional and specifies the pcap file to save the captured packets.
|
|
222
|
+
- With skipSourcePorts and skipDestinationPorts you can specify a comma-separated list of ports that will not be affected by the shaping rules.
|
|
223
|
+
- The filter field is optional and specifies the additional IPTables filter to apply for filtering the packets.
|
|
224
|
+
- The match field is optional and specifies the additional match rule to apply for filtering the packets (https://man7.org/linux/man-pages/man8/tc-ematch.8.html).
|
|
225
|
+
- The up and down fields are optional and they specify the upstream and downstream shaping rules. The possible options for the up and down rules could be:
|
|
226
|
+
- rate: the shaping rate in Kbps;
|
|
227
|
+
- delay: the shaping delay in milliseconds;
|
|
228
|
+
- delayJitter: the shaping delay jitter in milliseconds;
|
|
229
|
+
- delayJitterCorrelation: the shaping delay jitter correlation in milliseconds;
|
|
230
|
+
- loss: the packet loss percentage;
|
|
231
|
+
- lossBurst: the packet loss burst percentage;
|
|
232
|
+
- queue: the shaping queue size in packets;
|
|
233
|
+
- at: the time in seconds when the shaping rule will be applied (default: 0).
|
|
234
|
+
The up and down rules can be specified as a single object or an array of objects.
|
|
235
|
+
When using an array of objects, specify a different "at" value for each of them, in order to apply a sequence of actions; please note that only the specified properties will override previous ones, so you can omit the values that you don't want to change. \
|
|
236
|
+
\
|
|
237
|
+
`,
|
|
194
238
|
format: String,
|
|
195
239
|
nullable: true,
|
|
196
240
|
default: '',
|
|
@@ -367,9 +411,9 @@ calculated using \`Date.now()\``,
|
|
|
367
411
|
arg: 'spawn-rate',
|
|
368
412
|
},
|
|
369
413
|
showPageLog: {
|
|
370
|
-
doc: `If \`true\`, the pages console logs will be shown on console.`,
|
|
414
|
+
doc: `If \`true\`, the pages console logs will be shown on console. Set to false to disable the page logs.`,
|
|
371
415
|
format: 'Boolean',
|
|
372
|
-
default:
|
|
416
|
+
default: false,
|
|
373
417
|
env: 'SHOW_PAGE_LOG',
|
|
374
418
|
arg: 'show-page-log',
|
|
375
419
|
},
|
|
@@ -401,7 +445,7 @@ on the console. Regexp string allowed.`,
|
|
|
401
445
|
userAgent: {
|
|
402
446
|
doc: `The user agent override.`,
|
|
403
447
|
format: String,
|
|
404
|
-
default:
|
|
448
|
+
default: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${puppeteer.PUPPETEER_REVISIONS.chrome} Safari/537.36`,
|
|
405
449
|
nullable: true,
|
|
406
450
|
env: 'USER_AGENT',
|
|
407
451
|
arg: 'user-agent',
|
|
@@ -412,7 +456,9 @@ If set, the files contents will be executed inside each opened tab page; \
|
|
|
412
456
|
the following global variables will be attached to the \`webrtcperf\` global object: \
|
|
413
457
|
\`WEBRTC_PERF_SESSION\` the session number (0-indexed); \
|
|
414
458
|
\`WEBRTC_PERF_TAB\` the tab number inside the same session (0-indexed); \
|
|
415
|
-
\`WEBRTC_PERF_INDEX\` the page absolute index (0-indexed).
|
|
459
|
+
\`WEBRTC_PERF_INDEX\` the page absolute index (0-indexed).
|
|
460
|
+
Suggested values:
|
|
461
|
+
- With meet.google.com: https://raw.githubusercontent.com/vpalmisano/webrtcperf/refs/heads/devel/examples/google-meet.js
|
|
416
462
|
`,
|
|
417
463
|
format: String,
|
|
418
464
|
default: '',
|
|
@@ -762,7 +808,7 @@ the reference and degraded versions.`,
|
|
|
762
808
|
vmafCrop: {
|
|
763
809
|
doc: `If set, the reference and degraded videos will be cropped using the specified configuration in JSON5 format. \
|
|
764
810
|
Crop configuration should be expressed using the ffmpeg crop filter syntax (https://ffmpeg.org/ffmpeg-filters.html#crop). \
|
|
765
|
-
E.g. \`{ "Participant-000001_recv-by_Participant-000000
|
|
811
|
+
E.g. \`{ "Participant-000001_recv-by_Participant-000000": { ref: { w: "iw-10", h: "ih-5" }, deg: { w: "200", h: "200" } } }\``,
|
|
766
812
|
format: String,
|
|
767
813
|
nullable: true,
|
|
768
814
|
default: '',
|
|
@@ -880,7 +926,12 @@ export async function loadConfig(filePath?: string, values?: any): Promise<Confi
|
|
|
880
926
|
configSchema.load(values)
|
|
881
927
|
} else if (existsSync(filePath)) {
|
|
882
928
|
log.debug(`Loading config from local file: ${filePath}`)
|
|
883
|
-
|
|
929
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.mjs')) {
|
|
930
|
+
const module = await import(/* webpackIgnore: true */ path.resolve(filePath))
|
|
931
|
+
configSchema.load(await module.default())
|
|
932
|
+
} else {
|
|
933
|
+
configSchema.loadFile(filePath)
|
|
934
|
+
}
|
|
884
935
|
}
|
|
885
936
|
} else if (values) {
|
|
886
937
|
log.debug('Loading config from values.')
|
|
@@ -896,3 +947,60 @@ export async function loadConfig(filePath?: string, values?: any): Promise<Confi
|
|
|
896
947
|
log.debug('Using config:', config)
|
|
897
948
|
return config
|
|
898
949
|
}
|
|
950
|
+
|
|
951
|
+
function getFunctionDeclaration() {
|
|
952
|
+
const properties: Record<string, { type: string; description: string; nullable?: boolean }> = {}
|
|
953
|
+
const required: string[] = []
|
|
954
|
+
const schema = configSchema.getSchema()
|
|
955
|
+
|
|
956
|
+
Object.entries(schema._cvtProperties).forEach(([name, value]) => {
|
|
957
|
+
const { format, doc, nullable } = value as SchemaObj
|
|
958
|
+
properties[name] = {
|
|
959
|
+
type: format as string,
|
|
960
|
+
description: doc as string,
|
|
961
|
+
nullable,
|
|
962
|
+
}
|
|
963
|
+
})
|
|
964
|
+
|
|
965
|
+
return {
|
|
966
|
+
name: 'webrtcperf',
|
|
967
|
+
description: 'Starts a webrtcperf test.',
|
|
968
|
+
parameters: {
|
|
969
|
+
type: 'object',
|
|
970
|
+
properties,
|
|
971
|
+
required,
|
|
972
|
+
},
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
977
|
+
const { GoogleGenAI } = require('@google/genai')
|
|
978
|
+
|
|
979
|
+
export async function loadConfigFromPrompt(prompt: string) {
|
|
980
|
+
log.debug(`loadConfigFromPrompt: "${prompt}"`)
|
|
981
|
+
if (!process.env.GEMINI_API_KEY) {
|
|
982
|
+
throw new Error('GEMINI_API_KEY environment variable is not set. Please set it to use the Google GenAI API.')
|
|
983
|
+
}
|
|
984
|
+
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY })
|
|
985
|
+
const response = await ai.models.generateContent({
|
|
986
|
+
model: 'gemini-2.5-flash',
|
|
987
|
+
contents: prompt,
|
|
988
|
+
config: {
|
|
989
|
+
tools: [
|
|
990
|
+
{
|
|
991
|
+
functionDeclarations: [getFunctionDeclaration()],
|
|
992
|
+
},
|
|
993
|
+
],
|
|
994
|
+
thinkingConfig: {
|
|
995
|
+
thinkingBudget: 0,
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
})
|
|
999
|
+
if (response.functionCalls && response.functionCalls.length > 0) {
|
|
1000
|
+
const functionCall = response.functionCalls[0]
|
|
1001
|
+
log.debug('Using function call:', functionCall.name, functionCall.args)
|
|
1002
|
+
return functionCall.args
|
|
1003
|
+
} else {
|
|
1004
|
+
throw new Error('No function call found in the response. Please check the prompt and try again.')
|
|
1005
|
+
}
|
|
1006
|
+
}
|
package/src/session.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getSessionThrottleValues, throttleLauncher } from '@vpalmisano/throttler'
|
|
2
2
|
import assert from 'assert'
|
|
3
3
|
import axios from 'axios'
|
|
4
|
-
import chalk from 'chalk'
|
|
5
4
|
import EventEmitter from 'events'
|
|
6
5
|
import fs from 'fs'
|
|
7
6
|
import JSON5 from 'json5'
|
|
@@ -50,6 +49,9 @@ import {
|
|
|
50
49
|
} from './utils'
|
|
51
50
|
import { MediaPath } from './media'
|
|
52
51
|
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
53
|
+
const { default: chalk } = require('chalk-template')
|
|
54
|
+
|
|
53
55
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
54
56
|
const NavigatorHardwareConcurrency = require('puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency')
|
|
55
57
|
|
package/src/stats.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
2
|
import * as events from 'events'
|
|
4
3
|
import { Stats as FastStats } from 'fast-stats'
|
|
5
4
|
import * as fs from 'fs'
|
|
@@ -20,6 +19,9 @@ export { FastStats }
|
|
|
20
19
|
|
|
21
20
|
const log = logger('webrtcperf:stats')
|
|
22
21
|
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
+
const { default: chalk } = require('chalk-template')
|
|
24
|
+
|
|
23
25
|
function calculateFailAmountPercentile(stat: FastStats, percentile = 95): number {
|
|
24
26
|
return Math.round(stat.percentile(percentile))
|
|
25
27
|
}
|
|
@@ -144,7 +146,7 @@ function formatStats(s: FastStats, forWriter = false): StatsData | string[] {
|
|
|
144
146
|
function sprintfStatsTitle(name: string): string {
|
|
145
147
|
return sprintf(chalk`-- {bold %(name)s} %(fill)s\n`, {
|
|
146
148
|
name,
|
|
147
|
-
fill: '-'.repeat(
|
|
149
|
+
fill: '-'.repeat(110 - name.length - 4),
|
|
148
150
|
})
|
|
149
151
|
}
|
|
150
152
|
|