@vpalmisano/webrtcperf 4.4.3 → 4.4.4

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/app.min.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- (()=>{"use strict";var e={72:e=>{e.exports=require("@google/genai")},423:e=>{e.exports=require("change-case")},699:e=>{e.exports=require("basic-auth")},857:e=>{e.exports=require("os")},981:e=>{e.exports=require("pidtree")},1021:e=>{e.exports=require("chart.js")},1046:e=>{e.exports=require("@puppeteer/browsers")},1410:e=>{e.exports=require("dockerode")},1707:function(e,t,s){var a,r=this&&this.__createBinding||(Object.create?function(e,t,s,a){void 0===a&&(a=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,a,r)}:function(e,t,s,a){void 0===a&&(a=s),e[a]=t[s]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||(a=function(e){return a=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},a(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s=a(e),o=0;o<s.length;o++)"default"!==s[o]&&r(t,e,s[o]);return i(t,e),t}),n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Stats=t.FastStats=void 0;const c=n(s(8938)),l=o(s(6261)),d=s(4040);Object.defineProperty(t,"FastStats",{enumerable:!0,get:function(){return d.Stats}});const u=o(s(9896)),h=o(s(8611)),p=o(s(5692)),f=n(s(5865)),g=o(s(6928)),m=o(s(4622)),v=s(4066),w=o(s(3106)),b=s(7564),y=s(6185),S=(0,y.logger)("webrtcperf:stats"),{default:$}=s(5072);function P(e,t=95){return Math.round(e.percentile(t))}class R{fname;columns;headerWritten=!1;constructor(e="stats.log",t){this.fname=e,this.columns=t}async push(e,t=!0){if(!this.headerWritten||!t){const e=["datetime",...this.columns].join(",")+"\n";await u.promises.mkdir(g.dirname(this.fname),{recursive:!0}),await u.promises.writeFile(this.fname,e),this.headerWritten=!0}const s=[Date.now(),...e].join(",")+"\n";return u.promises.appendFile(this.fname,s)}}function T(e,t=!1){return t?[(0,y.toPrecision)(e.length||0,0),(0,y.toPrecision)(e.sum||0),(0,y.toPrecision)(e.amean()||0),(0,y.toPrecision)(e.stddev()||0),(0,y.toPrecision)(e.percentile(5)||0),(0,y.toPrecision)(e.percentile(95)||0),(0,y.toPrecision)(e.min||0),(0,y.toPrecision)(e.max||0)]:{length:e.length||0,sum:e.sum||0,mean:e.amean()||0,stddev:e.stddev()||0,p5:e.percentile(5)||0,p95:e.percentile(95)||0,min:e.min||0,max:e.max||0}}function k(e){return(0,v.sprintf)($`-- {bold %(name)s} %(fill)s\n`,{name:e,fill:"-".repeat(110-e.length-4)})}function E(e,t,s=".2f",a="",r=1,i=!1){if(!t?.all.length)return"";r||(r=1);const o=T(t.all);return(0,v.sprintf)($`{red {bold %(name)\' 30s}}`+$` {bold %(length)\' 8d}`+(i?" ":$` {bold %(sum)\' 8${s}}`)+$` {bold %(mean)\' 8${s}}`+$` {bold %(stddev)\' 8${s}}`+$` {bold %(p5)\' 8${s}}`+$` {bold %(p95)\' 8${s}}`+$` {bold %(min)\' 8${s}}`+$` {bold %(max)\' 8${s}}%(unit)s\n`,{name:e,length:o.length,sum:o.sum*r,mean:o.mean*r,stddev:o.stddev*r,p5:o.p5*r,p95:o.p95*r,min:o.min*r,max:o.max*r,unit:a?$` {red {bold ${a}}}`:""})}const x=(e,t,s="",a=[],r)=>new m.Gauge({name:`wst_${t}${s&&"_"+s}`,help:`${t} ${s}`,labelNames:a,registers:[e],collect:r}),C=(e,t)=>t?100*Math.min(1,Math.abs(e-t)/t):100*Math.min(1,Math.abs(e));class _ extends l.EventEmitter{statsPath;detailedStatsPath;prometheusPushgateway;prometheusPushgatewayJobName;prometheusPushgatewayAuth;prometheusPushgatewayGzip;showStats;showPageLog;statsInterval;rtcStatsTimeout;customMetrics={};startTimestamp;enableDetailedStats;startTimestampString;sessions=new Map;nextSessionId;statsWriter;detailedStatsWriter;detailedStatsSummaryWriter;scheduler;alertRules=null;alertRulesOutput;alertRulesFailPercentile;pushStatsUrl;pushStatsId;serverSecret;alertRulesReport=new Map;gateway=null;elapsedTimeMetric=null;metrics={};alertTagsMetrics;customMetricsLabels;collectedStats;collectedStatsConfig={url:"",pages:0,startTime:0};externalCollectedStats=new Map;pushStatsInstance=null;detailedStatsSummary={};running=!1;constructor({statsPath:e,detailedStatsPath:t,prometheusPushgateway:s,prometheusPushgatewayJobName:a,prometheusPushgatewayAuth:r,prometheusPushgatewayGzip:i,showStats:o,showPageLog:n,statsInterval:l,rtcStatsTimeout:d,customMetrics:u,alertRules:g,alertRulesOutput:m,alertRulesFailPercentile:v,pushStatsUrl:b,pushStatsId:y,serverSecret:$,startSessionId:P,startTimestamp:R,enableDetailedStats:T,customMetricsLabels:k}){if(super(),this.statsPath=e,this.detailedStatsPath=t,this.prometheusPushgateway=s,this.prometheusPushgatewayJobName=a||"default",this.prometheusPushgatewayAuth=r||void 0,this.prometheusPushgatewayGzip=i,this.showStats=void 0===o||o,this.showPageLog=!!n,this.statsInterval=l||10,this.rtcStatsTimeout=Math.max(d,this.statsInterval),u.trim()&&(this.customMetrics=f.default.parse(u),S.debug(`using customMetrics: ${JSON.stringify(this.customMetrics,void 0,2)}`)),this.collectedStats=this.initCollectedStats(),this.sessions=new Map,this.nextSessionId=P,this.startTimestamp=R||Date.now(),this.startTimestampString=new Date(this.startTimestamp).toISOString(),this.enableDetailedStats=T,this.customMetricsLabels=k?k.split(",").reduce(((e,t)=>((t=t.trim())&&(e[t]=void 0),e)),{}):{},this.statsWriter=null,this.detailedStatsWriter=null,this.detailedStatsSummaryWriter=null,g.trim()&&(this.alertRules=f.default.parse(g),S.debug(`using alertRules: ${JSON.stringify(this.alertRules,void 0,2)}`)),this.alertRulesOutput=m,this.alertRulesFailPercentile=v,this.pushStatsUrl=b,this.pushStatsId=y,this.serverSecret=$,this.pushStatsUrl){const e=new h.Agent({keepAlive:!1}),t=new p.Agent({keepAlive:!1,rejectUnauthorized:!1});this.pushStatsInstance=c.default.create({httpAgent:e,httpsAgent:t,baseURL:this.pushStatsUrl,auth:{username:"admin",password:this.serverSecret},maxBodyLength:2e7,transformRequest:[...c.default.defaults.transformRequest,(e,t)=>t&&"string"==typeof e&&e.length>16384?(t["Content-Encoding"]="gzip",w.gzipSync(e)):e]})}}initCollectedStats(){return this.statsNames.reduce(((e,t)=>(e[t]={all:new d.Stats,byHost:{},byCodec:{},byParticipantAndTrack:{}},e)),{})}get statsNames(){return Object.keys(b.PageStatsNames).concat(Object.keys(b.RtcStatsMetricNames)).concat(Object.keys(this.customMetrics))}consumeSessionId(e=1){const t=this.nextSessionId;return this.nextSessionId+=e,t}addSession(e){if(S.debug(`addSession ${e.id}`),this.sessions.has(e.id))throw new Error(`session id ${e.id} already present`);e.once("stop",(e=>{S.debug(`Session ${e} stopped`),this.sessions.delete(e)})),this.sessions.set(e.id,e)}removeSession(e){S.debug(`removeSession ${e}`),this.sessions.delete(e)}setCustomMetricLabel(e,t){if(!(e in this.customMetricsLabels))throw new Error(`Unknown custom metric label: ${e}`);this.customMetricsLabels[e]=t}async start(){if(this.running)S.warn("already running");else{if(S.debug("start"),this.running=!0,this.statsPath){S.debug(`Logging stats into ${this.statsPath}`);const e=this.statsNames.reduce(((e,t)=>{return e.concat([`${s=t}_length`,`${s}_sum`,`${s}_mean`,`${s}_stdev`,`${s}_5p`,`${s}_95p`,`${s}_min`,`${s}_max`]);var s}),[]);this.statsWriter=new R(this.statsPath,e)}if(this.detailedStatsPath&&(S.debug(`Logging stats into ${this.statsPath}`),this.detailedStatsWriter=new R(this.detailedStatsPath,["participantName","trackId",...this.statsNames]),this.detailedStatsSummaryWriter=new R(this.detailedStatsPath.replace(/\.(.+)$/,"-summary.$1"),["participantName","trackId",...this.statsNames])),this.prometheusPushgateway){const e=new m.Registry,t=this.prometheusPushgateway.startsWith("https://")?new p.Agent({keepAlive:!0,keepAliveMsecs:6e4,maxSockets:5}):new h.Agent({keepAlive:!0,keepAliveMsecs:6e4,maxSockets:5});this.gateway=new m.Pushgateway(this.prometheusPushgateway,{timeout:5e3,auth:this.prometheusPushgatewayAuth,rejectUnauthorized:!1,agent:t,headers:this.prometheusPushgatewayGzip?{"Content-Encoding":"gzip"}:void 0},e),this.elapsedTimeMetric=x(e,"elapsedTime","",["datetime",...Object.keys(this.customMetricsLabels)],(()=>this.elapsedTimeMetric?.set({datetime:this.startTimestampString,...this.customMetricsLabels},(Date.now()-this.startTimestamp)/1e3))),this.statsNames.forEach((t=>{if(this.metrics[t]={length:x(e,t,"length",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),sum:x(e,t,"sum",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),mean:x(e,t,"mean",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),stddev:x(e,t,"stddev",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),p5:x(e,t,"p5",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),p95:x(e,t,"p95",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),min:x(e,t,"min",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),max:x(e,t,"max",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),alertRules:{}},!1!==this.enableDetailedStats&&(this.metrics[t].value=x(e,t,"",["participantName","trackId","datetime",...Object.keys(this.customMetricsLabels)])),this.alertRules&&this.alertRules[t]){const s=this.alertRules[t];for(const a of Object.keys(s)){const s=`alert_${t}_${a}`;this.metrics[t].alertRules[s]={report:x(e,s,"report",["rule","datetime",...Object.keys(this.customMetricsLabels)]),rule:x(e,s,"",["rule","datetime",...Object.keys(this.customMetricsLabels)]),mean:x(e,s,"mean",["rule","datetime",...Object.keys(this.customMetricsLabels)])}}}})),this.alertRules&&(this.alertTagsMetrics=x(e,"alert_report","",["datetime","tag",...Object.keys(this.customMetricsLabels)])),await this.deletePushgatewayStats()}this.scheduler=new y.Scheduler("stats",this.statsInterval,this.collectStats.bind(this)),this.scheduler.start()}}async deletePushgatewayStats(){if(this.gateway)try{const{resp:e,body:t}=await this.gateway.delete({jobName:this.prometheusPushgatewayJobName});t.length&&S.warn(`Pushgateway delete error ${e.statusCode}: ${t}`)}catch(e){S.error(`Pushgateway delete error: ${e.stack}`)}}async collectStats(e){if(!this.running)return;if(!this.sessions.size&&!this.externalCollectedStats.size)return;this.collectedStatsConfig.pages=0,this.collectedStatsConfig.startTime=this.startTimestamp;const t={};Object.entries(this.collectedStats).forEach((([e,s])=>{s.all.reset(),Object.values(s.byHost).forEach((e=>e.reset())),Object.values(s.byCodec).forEach((e=>e.reset())),t[e]=new Set(Object.keys(s.byParticipantAndTrack)),s.byParticipantAndTrack={}}));for(const[e,s]of this.sessions.entries()){this.collectedStatsConfig.url=`${(0,y.hideAuth)(s.url)}?${s.urlQuery}`,this.collectedStatsConfig.pages+=s.pages.size||0;const a=await s.updateStats();for(const[s,r]of Object.entries(a)){if(void 0===r)return;try{const a=this.collectedStats[s],i=t[s];if("number"==typeof r&&isFinite(r))a.all.push(r);else for(const[t,s]of Object.entries(r))if("number"==typeof s&&isFinite(s)){a.all.push(s);const{trackId:r,hostName:o,participantName:n}=(0,b.parseRtStatKey)(t);let c=a.byHost[o];if(c||(c=a.byHost[o]=new d.Stats),c.push(s),(0,y.enabledForSession)(e,this.enableDetailedStats)&&n){const e=`${n}:${r||""}`;a.byParticipantAndTrack[e]=s,i.delete(e)}}else if("string"==typeof s){a.all.push(1);let e=a.byCodec[s];e||(e=a.byCodec[s]=new d.Stats),e.push(1)}}catch(e){S.error(`session getStats name: ${s} error: ${e.stack}`,e)}}}for(const[s,a]of this.externalCollectedStats.entries()){const{addedTime:r,externalStats:i,config:o}=a;e-r>1e3*this.rtcStatsTimeout?(S.debug(`remove externalCollectedStats from ${s}`),this.externalCollectedStats.delete(s)):(S.debug(`add external stats from ${s}`),o.url&&(this.collectedStatsConfig.url=o.url),o.pages&&(this.collectedStatsConfig.pages+=o.pages),this.statsNames.forEach((e=>{const s=i[e];if(!s)return;const a=this.collectedStats[e],r=t[e];a.all.push(s.all),Object.entries(s.byHost).forEach((([e,t])=>{a.byHost[e]||(a.byHost[e]=new d.Stats),a.byHost[e].push(t)})),Object.entries(s.byCodec).forEach((([e,t])=>{a.byCodec[e]||(a.byCodec[e]=new d.Stats),a.byCodec[e].push(t)})),Object.entries(s.byParticipantAndTrack).forEach((([e,t])=>{a.byParticipantAndTrack[e]=t,r.delete(e)}))})))}if(this.emit("stats",this.collectedStats),this.pushStatsInstance){const e={};for(const[t,s]of Object.entries(this.collectedStats))e[t]={all:s.all.data,byHost:{},byCodec:{},byParticipantAndTrack:{}},Object.entries(s.byHost).forEach((([s,a])=>{e[t].byHost[s]=a.data})),Object.entries(s.byCodec).forEach((([s,a])=>{e[t].byCodec[s]=a.data})),Object.entries(s.byParticipantAndTrack).forEach((([s,a])=>{e[t].byParticipantAndTrack[s]=a}));try{const t=await this.pushStatsInstance.put("/collected-stats",{id:this.pushStatsId,stats:e,config:this.collectedStatsConfig});S.debug(`pushStats message=${t.data.message}`)}catch(e){S.error(`pushStats error: ${e.stack}`)}}this.checkAlertRules(),this.consoleShowStats(),await Promise.allSettled([this.writeStats(),this.writeDetailedStats(),this.sendToPushGateway(t),this.writeAlertRulesReport()])}async writeStats(){if(!this.statsWriter)return;const e=this.statsNames.reduce(((e,t)=>e.concat(T(this.collectedStats[t].all,!0))),[]);await this.statsWriter.push(e)}async writeDetailedStats(){if(!this.detailedStatsWriter)return;const e=new Map;Object.entries(this.collectedStats).forEach((([t,s])=>{Object.entries(s.byParticipantAndTrack).forEach((([s,a])=>{let r=e.get(s);r||(r={},e.set(s,r)),r[t]=a}))}));for(const[t,s]of e.entries()){const[e,a]=t.split(":",2);this.detailedStatsSummary[t]||(this.detailedStatsSummary[t]={});const r=this.detailedStatsSummary[t],i=[e,a];for(const e of this.statsNames){i.push(void 0!==s[e]?(0,y.toPrecision)(s[e],6):""),r[e]||(r[e]=new d.Stats({store_data:!1}));const t=r[e];void 0!==s[e]&&t.push(s[e])}await this.detailedStatsWriter.push(i)}}async writeDetailedStatsSummary(){if(!this.detailedStatsSummaryWriter)return;let e=!1;for(const t of Object.keys(this.detailedStatsSummary)){const s=this.detailedStatsSummary[t],[a,r]=t.split(":",2),i=[a,r];for(const e of this.statsNames){const t=s[e];i.push(t?.length>0?(0,y.toPrecision)(t.amean(),6):"")}await this.detailedStatsSummaryWriter.push(i,e),e=!0}}addExternalCollectedStats(e,t,s){S.debug(`addExternalCollectedStats from ${e}`);const a=Date.now();this.externalCollectedStats.set(e,{addedTime:a,externalStats:t,config:s})}consoleShowStats(){if(!this.showStats)return;const e=this.collectedStats;let t=k((new Date).toUTCString())+(0,v.sprintf)($`{bold %(name)\' 30s} {bold %(length)\' 8s} {bold %(sum)\' 8s} {bold %(mean)\' 8s} {bold %(stddev)\' 8s} {bold %(p5)\' 8s} {bold %(p95)\' 8s} {bold %(min)\' 8s} {bold %(max)\' 8s}\n`,{name:"name",length:"count",sum:"sum",mean:"mean",stddev:"stddev",p5:"5p",p95:"95p",min:"min",max:"max"})+E("System CPU",e.usedCpu,".2f","%",void 0,!0)+E("System GPU",e.usedGpu,".2f","%",void 0,!0)+E("System Memory",e.usedMemory,".2f","%",void 0,!0)+E("CPU/page",e.cpu,".2f","%")+E("Memory/page",e.memory,".2f","MB")+E("Pages",e.pages,"d","")+E("Errors",e.errors,"d","")+E("Warnings",e.warnings,"d","")+E("Peer Connections",e.peerConnections,"d","")+E("audioSubscribeDelay",e.audioSubscribeDelay,"d","ms",void 0,!0)+E("videoSubscribeDelay",e.videoSubscribeDelay,"d","ms",void 0,!0)+k("Inbound audio")+E("received",e.audioBytesReceived,".2f","MB",1e-6)+E("rate",e.audioRecvBitrates,".2f","Kbps",.001)+E("lost",e.audioRecvPacketsLost,".2f","%",void 0,!0)+E("jitter",e.audioRecvJitter,".2f","s",void 0,!0)+E("avgJitterBufferDelay",e.audioRecvAvgJitterBufferDelay,".2f","ms",1e3,!0)+k("Inbound video")+E("received",e.videoRecvBytes,".2f","MB",1e-6)+E("decoded",e.videoFramesDecoded,"d","frames")+E("rate",e.videoRecvBitrates,".2f","Kbps",.001)+E("lost",e.videoRecvPacketsLost,".2f","%",void 0,!0)+E("jitter",e.videoRecvJitter,".2f","s",void 0,!0)+E("avgJitterBufferDelay",e.videoRecvAvgJitterBufferDelay,".2f","ms",1e3,!0)+E("width",e.videoRecvWidth,"d","px",void 0,!0)+E("height",e.videoRecvHeight,"d","px",void 0,!0)+E("fps",e.videoRecvFps,"d","fps",void 0,!0)+E("firCountSent",e.firCountSent,"d","",void 0,!0)+E("pliCountSent",e.pliCountSent,"d","",void 0,!0)+k("Outbound audio")+E("sent",e.audioBytesSent,".2f","MB",1e-6)+E("retransmitted",e.audioRetransmittedBytesSent,".2f","MB",1e-6)+E("rate",e.audioSentBitrates,".2f","Kbps",.001)+E("lost",e.audioSentPacketsLost,".2f","%",void 0,!0)+E("roundTripTime",e.audioSentRoundTripTime,".3f","s",void 0,!0)+k("Outbound video")+E("sent",e.videoSentBytes,".2f","MB",1e-6)+E("retransmitted",e.videoSentRetransmittedBytes,".2f","MB",1e-6)+E("rate",e.videoSentBitrates,".2f","Kbps",.001)+E("lost",e.videoSentPacketsLost,".2f","%",void 0,!0)+E("roundTripTime",e.videoSentRoundTripTime,".3f","s",void 0,!0)+E("qualityLimitResolutionChanges",e.videoQualityLimitationResolutionChanges,"d","")+E("qualityLimitationCpu",e.videoQualityLimitationCpu,"d","%")+E("qualityLimitationBandwidth",e.videoQualityLimitationBandwidth,"d","%")+E("sentActiveSpatialLayers",e.videoSentActiveSpatialLayers,"d","layers",void 0,!0)+E("sentMaxBitrate",e.videoSentMaxBitrate,".2f","Kbps",.001)+E("width",e.videoSentWidth,"d","px",void 0,!0)+E("height",e.videoSentHeight,"d","px",void 0,!0)+E("fps",e.videoSentFps,"d","fps",void 0,!0)+E("firCountReceived",e.videoFirCountReceived,"d","",void 0,!0)+E("pliCountReceived",e.videoPliCountReceived,"d","",void 0,!0);if(this.alertRules){const e=this.formatAlertRulesReport();e.length&&(t+=k("Alert rules report"),t+=e)}this.showPageLog||console.clear(),console.log(t)}async sendToPushGateway(e){if(!this.gateway||!this.running)return;const t=(Date.now()-this.startTimestamp)/1e3,s=this.startTimestampString;Object.entries(this.metrics).forEach((([a,r])=>{if(!this.collectedStats[a])return;const i=(e,t,a)=>{const i={host:t,codec:a,datetime:s,...this.customMetricsLabels},{length:o,sum:n,mean:c,stddev:l,p5:d,p95:u,min:h,max:p}=T(e);r.length.set(i,o),r.sum.set(i,n),r.mean.set(i,c),r.stddev.set(i,l),r.p5.set(i,d),r.p95.set(i,u),r.min.set(i,h),r.max.set(i,p)};i(this.collectedStats[a].all,"all","all"),Object.entries(this.collectedStats[a].byHost).forEach((([e,t])=>{i(t,e,"all")})),Object.entries(this.collectedStats[a].byCodec).forEach((([e,t])=>{i(t,"all",e)})),r.value&&Object.entries(this.collectedStats[a].byParticipantAndTrack).forEach((([e,t])=>{const[a,i]=e.split(":",2);r.value?.set({participantName:a,trackId:i,datetime:s,...this.customMetricsLabels},t)}));const o=e[a];if(o)for(const e of o){const[t,a]=e.split(":",2);r.value?.remove({participantName:t,trackId:a,datetime:s,...this.customMetricsLabels})}if(this.alertRules&&this.alertRules[a]){const e=this.alertRules[a];for(let[r,i]of Object.entries(e))if("tags"!==r){Array.isArray(i)||(i=[i]);for(const e of i){if(void 0!==e.$after&&t<e.$after)continue;const i=`alert_${a}_${r}`,o=this.metrics[a].alertRules[i],n=void 0!==e.$before&&t>e.$before,c=this.getAlertRuleDesc(r,e),l=this.alertRulesReport.get(a);if(l){const e=l.get(c);if(e){const t={rule:c,datetime:s,...this.customMetricsLabels};n?(o.report.remove(t),o.mean.remove(t)):(o.report.set(t,e.failAmountPercentile),o.mean.set(t,e.valueStats.amean()))}}if(void 0!==e.$eq){const t={rule:`${a} ${r} =`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$eq)}if(void 0!==e.$lt){const t={rule:`${a} ${r} <`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$lt)}if(void 0!==e.$lte){const t={rule:`${a} ${r} <=`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$lte)}if(void 0!==e.$gt){const t={rule:`${a} ${r} >`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$gt)}if(void 0!==e.$gte){const t={rule:`${a} ${r} >=`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$gte)}}}}}));const a=this.getAlertRulesTags();if(a&&this.alertTagsMetrics)for(const[e,t]of a.entries())this.alertTagsMetrics.set({datetime:s,tag:e,...this.customMetricsLabels},P(t,this.alertRulesFailPercentile));try{const{resp:e,body:t}=await this.gateway.push({jobName:this.prometheusPushgatewayJobName});t.length&&S.warn(`Pushgateway error ${e.statusCode}: ${t}`)}catch(e){S.error(`Pushgateway push error: ${e.stack}`)}}getAlertRuleDesc(e,t){const s=[];void 0!==t.$eq&&s.push(`= ${t.$eq}`),void 0!==t.$gt&&s.push(`> ${t.$gt}`),void 0!==t.$gte&&s.push(`>= ${t.$gte}`),void 0!==t.$lt&&s.push(`< ${t.$lt}`),void 0!==t.$lte&&s.push(`<= ${t.$lte}`);let a=`${e} ${s.join(" and ")}`;return void 0!==t.$after&&(a+=` after ${t.$after}s`),void 0!==t.$before&&(a+=` before ${t.$before}s`),a}checkAlertRules(){if(!this.alertRules||!this.running)return;const e=Date.now(),t=(e-this.startTimestamp)/1e3;for(const[s,a]of Object.entries(this.alertRules)){if(!this.collectedStats[s])continue;let r=this.alertRulesFailPercentile;const i=T(this.collectedStats[s].all);for(let[o,n]of Object.entries(a)){if(["tags","failPercentile"].includes(o)){"failPercentile"===o&&(r=n);continue}Array.isArray(n)||(n=[n]);let a=t;for(const c of n){if(void 0!==c.$after&&t<c.$after||void 0!==c.$before&&t>c.$before)continue;void 0!==c.$after&&(a-=c.$after);const n=i[o];if(!isFinite(n))continue;const l=this.getAlertRuleDesc(o,c);let d=!1,u=0;void 0!==c.$skip_lt&&n<c.$skip_lt||void 0!==c.$skip_lte&&n<=c.$skip_lte||void 0!==c.$skip_gt&&n>c.$skip_gt||void 0!==c.$skip_gte&&n>=c.$skip_gte||(void 0!==c.$eq?n!==c.$eq&&(d=!0,u=C(n,c.$eq)):(void 0!==c.$lt?n>=c.$lt&&(d=!0,u=C(n,c.$lt)):void 0!==c.$lte&&n>c.$lte&&(d=!0,u=C(n,c.$lte)),d||(void 0!==c.$gt?n<=c.$gt&&(d=!0,u=C(n,c.$gt)):void 0!==c.$gte&&n<c.$gte&&(d=!0,u=C(n,c.$gte)))),this.updateRulesReport(s,n,l,d,u,e,a,r))}}}}updateRulesReport(e,t,s,a,r,i,o,n){a&&S.debug(`updateRulesReport ${e}.${s} failed: ${a} checkValue: ${t} failAmount: ${r} elapsedSeconds: ${o}`);let c=this.alertRulesReport.get(e);c||(c=new Map,this.alertRulesReport.set(e,c));let l=c.get(s);l||(l={totalFails:0,totalFailsTime:0,totalFailsTimePerc:0,lastFailed:0,valueStats:new d.Stats,failAmountStats:new d.Stats,failAmountPercentile:0},c.set(s,l)),a?(l.totalFails+=1,l.lastFailed&&(l.totalFailsTime+=(i-l.lastFailed)/1e3),l.lastFailed=i):l.lastFailed=0,l.totalFailsTimePerc=Math.round(100*l.totalFailsTime/o),l.valueStats.push(t),l.failAmountStats.push(r),l.failAmountPercentile=P(l.failAmountStats,n)}getAlertRulesTags(){if(!this.alertRules)return;const e=new Map;for(const[t,s]of this.alertRulesReport.entries()){const a=this.alertRules[t].tags||[];for(const t of a)e.has(t)||e.set(t,new d.Stats);for(const t of s.values()){const{failAmountPercentile:s}=t;for(const t of a){const a=e.get(t);a&&a.push(s)}}}return e}formatAlertRulesReport(e=null){if(!this.alertRulesReport||!this.alertRules)return"";const t=this.getAlertRulesTags();if("json"===e){const e={tags:{},reports:{}};for(const[t,s]of this.alertRulesReport.entries())for(const[a,r]of s.entries()){const{totalFails:s,totalFailsTime:i,valueStats:o,totalFailsTimePerc:n,failAmountStats:c,failAmountPercentile:l}=r;s&&(e.reports[`${t} ${a}`]={totalFails:s,totalFailsTime:Math.round(i),valueAverage:o.amean(),totalFailsTimePerc:n,failAmount:l,count:c.length})}for(const[s,a]of t.entries())e.tags[s]=P(a,this.alertRulesFailPercentile);return JSON.stringify(e,null,2)}let s="",a=20;for(const[e,t]of this.alertRulesReport.entries())for(const[s,r]of t.entries()){const{totalFails:t,totalFailsTimePerc:i}=r;if(t&&i>0){const t=`${e} ${s}`;a=Math.max(a,t.length)}}s+=e?(0,v.sprintf)(`| %(check)-${a}s | %(total)-10s | %(totalFailsTime)-15s | %(totalFailsTimePerc)-15s | %(failAmount)-15s |\n`,{check:"Condition",total:"Fails",totalFailsTime:"Fail time (s)",totalFailsTimePerc:"Fail time (%)",failAmount:"Fail amount %"}):(0,v.sprintf)($`{bold %(check)-${a}s} {bold %(total)-10s} {bold %(totalFailsTime)-15s} {bold %(totalFailsTimePerc)-15s} {bold %(failAmount)-15s}\n`,{check:"Condition",total:"Fails",totalFailsTime:"Fail time (s)",totalFailsTimePerc:"Fail time (%)",failAmount:"Fail amount %"});for(const[t,r]of this.alertRulesReport.entries())for(const[i,o]of r.entries()){const{totalFails:r,totalFailsTime:n,failAmountPercentile:c,totalFailsTimePerc:l}=o;r&&l>0&&(s+=e?(0,v.sprintf)(`| %(check)-${a}s | %(totalFails)-10s | %(totalFailsTime)-15s | %(totalFailsTimePerc)-15s | %(failAmountPercentile)-15s |\n`,{check:`${t} ${i}`,totalFails:r,totalFailsTime:Math.round(n),totalFailsTimePerc:l,failAmountPercentile:c}):(0,v.sprintf)($`{red {bold %(check)-${a}s}} {bold %(totalFails)-10s} {bold %(totalFailsTime)-15s} {bold %(totalFailsTimePerc)-15s} {bold %(failAmountPercentile)-15s}\n`,{check:`${t} ${i}`,totalFails:r,totalFailsTime:Math.round(n),totalFailsTimePerc:l,failAmountPercentile:c}))}e?(s+=(0,v.sprintf)("%(fill)s\n",{fill:"-".repeat(a+15+7)}),s+=(0,v.sprintf)(`| %(name)-${a}s | %(failPerc)-15s |\n`,{name:"Tag",failPerc:"Fail %"})):(s+=(0,v.sprintf)("%(fill)s\n",{fill:"-".repeat(a+15)}),s+=(0,v.sprintf)($`{bold %(name)-${a}s} {bold %(failPerc)-15s}\n`,{name:"Tag",failPerc:"Fail %"}));for(const[r,i]of t.entries()){const t=P(i,this.alertRulesFailPercentile);if(e)s+=(0,v.sprintf)(`| %(tag)-${a}s | %(failPerc)-15s |\n`,{tag:r,failPerc:t});else{const e=t<5?"green":t<25?"yellowBright":t<50?"yellow":"red";s+=(0,v.sprintf)($`{${e} {bold %(tag)-${a}s %(failPerc)-15s}}\n`,{tag:r,failPerc:t})}}return s}async writeAlertRulesReport(){if(this.alertRules&&this.alertRulesOutput&&this.running){S.debug(`writeAlertRulesReport writing in ${this.alertRulesOutput}`);try{const e=this.alertRulesOutput.split(".").slice(-1)[0],t=this.formatAlertRulesReport(e);if(!t.length)return;let s;if("log"===e){const e=t.split("\n").filter((e=>e.length)),a=`Alert rules report (${(new Date).toISOString()})`;s=(0,v.sprintf)("-- %(name)s %(fill)s\n",{name:a,fill:"-".repeat(Math.max(4,e[0].length-a.length-4))}),s+=t,s+=(0,v.sprintf)("%(fill)s\n",{fill:"-".repeat(e[e.length-1].length)})}else s=t;await u.promises.mkdir(g.dirname(this.alertRulesOutput),{recursive:!0}),await u.promises.writeFile(this.alertRulesOutput,s)}catch(e){S.error(`writeAlertRulesReport error: ${e.stack}`)}}}async stop(){if(this.running){this.running=!1,S.debug("stop"),this.scheduler&&(await this.scheduler.stop(),this.scheduler=void 0);for(const e of this.sessions.values())try{e.removeAllListeners(),await e.stop()}catch(e){S.error(`session stop error: ${e.stack}`)}this.sessions.clear(),await this.writeDetailedStatsSummary(),this.statsWriter=null,this.detailedStatsWriter=null,this.detailedStatsSummaryWriter=null,this.detailedStatsSummary={},this.gateway&&(await this.deletePushgatewayStats(),this.gateway=null,this.metrics={}),this.collectedStats=this.initCollectedStats(),this.externalCollectedStats.clear()}}}t.Stats=_},1712:function(e,t,s){e=s.nmd(e);var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.calculateVisqolScore=c;const r=s(6185),i=a(s(9896)),o=a(s(6928)),n=(0,r.logger)("webrtcperf:visqol");async function c(e){const{visqolPath:t,visqolKeepSourceFiles:s}=e;n.debug("calculateVisqolScore",{visqolPath:t,visqolKeepSourceFiles:s});const a=new Set,c=new Set,l=await(0,r.getFiles)(t,"");for(const e of l){if(!e.endsWith(".wav")&&!e.endsWith(".f32le.raw"))continue;const t=o.default.basename(e).includes("_send_"),n=o.default.basename(e).includes("_recv_");if(!n&&!t)continue;let l=e;e.endsWith(".f32le.raw")&&(l=o.default.join(o.default.dirname(e),o.default.basename(e).replace(".f32le.raw",".wav")),await(0,r.runShellCommand)(`ffmpeg -hide_banner -loglevel info -f f32le -ar 48000 -ac 1 -i ${e} -ac 1 ${l}`),s||i.default.unlinkSync(e)),t?a.add(l):n&&c.add(l)}for(const e of a.values())for(const t of c.values()){n.info(`Calculating score ${e} -> ${t}`);try{await(0,r.runShellCommand)(`/usr/bin/visqol --reference_file ${e} --degraded_file ${t} --similarity_to_quality_model /usr/share/visqol/model/tcdaudio14_aacvopus_coresv_svrnsim_n.68_g.01_c1.model --results_csv ${o.default.dirname(e)}/visqol.csv`)}catch(e){n.error("Error calculating score:",e.stack)}}}s.c[s.s]===e&&(async()=>{await c({visqolPath:process.argv[2],visqolKeepSourceFiles:!0})})().catch((e=>console.error(e)))},1845:e=>{e.exports=require("node-os-utils")},1859:function(e,t,s){e=s.nmd(e);var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Application=void 0;const r=s(7191),i=s(423),o=a(s(9896)),n=a(s(5865)),c=s(7028),l=s(2680),d=s(6997),u=s(6976),h=s(1707),p=s(6185),f=s(1712),g=s(5812),m=a(s(6928)),v=s(6259),w=s(6261),b=s(6958),{marked:y}=s(3840);y.use((0,v.markedTerminal)({reflowText:!0,tab:2}));const S=(0,p.logger)("webrtcperf");class $ extends w.EventEmitter{config;stats;server;mediaPaths=[];constructor(e){super(),e.startTimestamp||(e.startTimestamp=Date.now()),this.config=e,this.stats=new h.Stats(e),e.serverPort&&(this.server=new d.Server(e,this.stats))}async start(){S.debug(`start (runDuration: ${this.config.runDuration})`),await this.stats.start(),this.server&&await this.server.start();const e=this.config;if(e.vmafPrepareVideo&&await(0,g.prepareVideo)(e,!0),e.vmafProcessVideo&&await(0,g.convertToIvf)(e.vmafProcessVideo,e.vmafVideoCrop,e.vmafKeepSourceFiles,e.vmafSkipDuplicated),e.sessions>0){if(e.videoPath&&!this.mediaPaths.length)for(const t of e.videoPath.split(",")){const s=await(0,l.prepareFakeMedia)({...e,videoPath:t});this.mediaPaths.push(s)}e.throttleConfig&&await(0,r.startThrottle)(e.throttleConfig),e.chromiumUrl||e.chromiumPath||await(0,p.checkChromeExecutable)(),e.randomAudioPeriod&&(0,p.startRandomActivateAudio)(this.stats.sessions,e.randomAudioPeriod,e.randomAudioProbability,e.randomAudioRange);const t=1e3/e.spawnRate;S.debug(`Starting ${e.sessions} sessions (spawnPeriod: ${t}ms)`);const s=Date.now();for(let s=0;s<e.sessions;s+=1){const a=this.stats.consumeSessionId(e.tabsPerSession);await this.startSession(a,t),s<e.sessions-1&&await(0,p.sleep)(t)}const a=Math.round((Date.now()-s)/1e3),i=e.sessions*e.tabsPerSession/a;S.debug(`${e.sessions*e.tabsPerSession} pages started in ${a}s (${i.toFixed(2)}/s)`)}(e.runDuration||e.vmafPath||e.visqolPath)&&setTimeout((()=>this.stop()),1e3*e.runDuration)}async startSession(e,t){S.debug(`startSession ${e}`);const s=(0,r.getSessionThrottleIndex)(e),a=this.mediaPaths.length?this.mediaPaths[e%this.mediaPaths.length]:void 0,i=new u.Session({...this.config,mediaPath:a,spawnPeriod:t,id:e,throttleIndex:s});i.once("stop",(()=>{console.warn(`Session ${e} stopped, reloading...`),setTimeout((()=>this.startSession(e,t)),t)})),this.stats.addSession(i),await i.start()}async postTest(){if(S.debug("postTest"),this.config.vmafPath){console.log("Calculating VMAF score...");try{await(0,g.calculateVmafScore)(this.config)}catch(e){S.error(`vmaf score error: ${e.stack}`)}}if(this.config.visqolPath){console.log("Calculating Visqol score...");try{await(0,f.calculateVisqolScore)(this.config)}catch(e){S.error(`visqol score error: ${e.stack}`)}}}async stop(e=!1){if(S.debug(`stop (canceled: ${e})`),(0,p.stopRandomActivateAudio)(),await this.stats.stop(),this.config.throttleConfig&&await(0,r.stopThrottle)(),(0,p.stopTimers)(),await this.postTest(),this.config.pageLogPath)try{const e=await(0,p.getDockerLogsPath)(),t=m.default.dirname(this.config.pageLogPath);await o.default.promises.cp(e,m.default.resolve(t,"docker.log"))}catch(e){S.debug(`docker logs not found: ${e.message}`)}this.server?.stop(),this.emit("stop",e)}}t.Application=$,s.c[s.s]===e&&async function(){!function(){if(process.argv.includes("--help")||process.argv.includes("-h")){const e=(0,c.getConfigDocs)();let t=y.parse("**Webrtcperf parameters**\n\n`--version` It shows the package version.\n");Object.entries(e).forEach((([e,s])=>{t+=y.parse(`\n\`--${(0,i.paramCase)(e)}\`\n${s.doc}\nDefault value: \`${s.default}\`\n`)})),console.log(t),process.exit(0)}else if(process.argv.includes("--version")||process.argv.includes("-v")){const e=n.default.parse(o.default.readFileSync((0,p.resolvePackagePath)("package.json")).toString()).version;console.log(e),process.exit(0)}}();const e=process.argv.slice(2);if(e.includes("--docker")){try{await(0,b.runWithDocker)(e)}catch(e){S.error(`runWithDocker error: ${e.stack}`),process.exit(1)}process.exit(0)}let t,s;if(e.includes("--prompt")){const s=await(0,c.loadConfigFromPrompt)(e.filter((e=>!["--prompt","--dry-run"].includes(e))).join(" "));process.argv.slice(2).includes("--dry-run")&&(console.log(n.default.stringify(s,null,2)),process.exit(0)),t=await(0,c.loadConfig)(void 0,s)}else t=await(0,c.loadConfig)(process.argv[2]);if(!t.length)throw new Error("No configuration found");const a=()=>{const e=t.splice(0,1)[0];return s=new $(e),s.once("stop",(e=>{!e&&t.length?(S.info(`Application stopped, running next (${t.length} left)...`),a()):process.exit(0)})),s.start()},r=async()=>{console.log("Exiting..."),await s.stop(!0)};(0,p.registerExitHandler)((()=>r())),await a(),process.stdin&&process.stdin.setRawMode&&(console.log("Press [q] to quit or [x] to exit immediately"),process.stdin.setRawMode(!0),process.stdin.resume(),process.stdin.on("data",(async e=>{if(S.debug("[stdin]",e[0]),e[0]==="q".charCodeAt(0))try{await r()}catch(e){S.error(`stop error: ${e.stack}`),process.exit(1)}else e[0]==="x".charCodeAt(0)&&process.exit(1)})))}().catch((e=>{console.error(e),process.exit(-1)}))},2011:e=>{e.exports=require("node-cache")},2115:e=>{e.exports=require("yaml")},2250:e=>{e.exports=require("dns")},2305:e=>{e.exports=require("form-data")},2365:e=>{e.exports=require("tar-fs")},2613:e=>{e.exports=require("assert")},2680:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.prepareFakeMedia=async function({videoPath:e,videoWidth:t,videoHeight:s,videoFramerate:n,videoSeek:c,videoDuration:l,videoCacheRaw:d,videoCachePath:u,videoFormat:h,useFakeMedia:p}){if(i.debug("prepareFakeMedia",{videoPath:e,videoWidth:t,videoHeight:s,videoFramerate:n,videoSeek:c,videoDuration:l,videoCacheRaw:d,videoCachePath:u,videoFormat:h,useFakeMedia:p}),!e)throw new Error("empty video path");e.startsWith("http")||e.startsWith("generate:")||(0,a.existsSync)(e)||(i.warn(`video not found: ${e}, using default test video`),e=o);await a.promises.mkdir(u,{recursive:!0});const f=(0,r.sha256)(e),g=`${u}/${f}_${t}x${s}_${n}fps.${h}`,m=`${u}/${f}.wav`,v=p?"":`${u}/${f}_${t}x${s}_${n}fps.mp4`,w=p?"":`${u}/${f}.m4a`;if(!(0,a.existsSync)(g)||!(0,a.existsSync)(m)||v&&!(0,a.existsSync)(v)||w&&!(0,a.existsSync)(w)||!d){i.info(`Converting ${e} to ${g}, ${m}${v?`, ${v}`:""}${w?`, ${w}`:""}`);const o=`${u}/${f}_${t}x${s}_${n}fps.tmp.${h}`,d=`${u}/${f}.tmp.wav`,b=p?"":`${u}/${f}_${t}x${s}_${n}fps.tmp.mp4`,y=p?"":`${u}/${f}.tmp.m4a`;try{let i=`-i "${e}"`;const u="-map 0:v",h=e.startsWith("generate:")?"-map 1:a":"-map 0:a";"generate:null"===e?i=`-f lavfi -i color=size=${t}x${s}:rate=${n}:color=black -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000`:"generate:test"===e&&(i=`-f lavfi -i testsrc=size=${t}x${s}:rate=${n} -pix_fmt yuv420p -f lavfi -i sine=frequency=220:beep_factor=4:sample_rate=48000`),await(0,r.runShellCommand)(`ffmpeg -loglevel warning -y -threads 0 ${i} -s ${t}:${s} -r ${n} -ss ${c} -t ${l} -shortest -af apad ${u} ${o} ${h} -ar 48000 ${d}`+(b?` ${u} -c:v libx264 -crf 10 -f mp4 -movflags faststart ${b} ${h} -c:a aac -ar 48000 -b:a 192k -f mp4 -movflags faststart ${y}`:"")),await a.promises.rename(o,g),await a.promises.rename(d,m),b&&await a.promises.rename(b,v),y&&await a.promises.rename(y,w)}catch(e){throw i.error(`Error converting video: ${e.stack}`),a.promises.unlink(o).catch((e=>i.debug(e.message))),a.promises.unlink(d).catch((e=>i.debug(e.message))),b&&a.promises.unlink(b).catch((e=>i.debug(e.message))),e}}return{video:g,audio:m,mp4:v,m4a:w}};const a=s(9896),r=s(6185),i=(0,r.logger)("webrtcperf:media"),o="https://github.com/vpalmisano/webrtcperf/releases/download/v2.0.4/video.mp4"},3106:e=>{e.exports=require("zlib")},3200:e=>{e.exports=require("child_process")},3840:e=>{e.exports=require("marked")},3940:e=>{e.exports=require("debug-level")},4040:e=>{e.exports=require("fast-stats")},4066:e=>{e.exports=require("sprintf-js")},4412:e=>{e.exports=require("pidusage/lib/ps")},4622:e=>{e.exports=require("prom-client")},4908:e=>{e.exports=require("toml")},4950:e=>{e.exports=require("convict")},5072:e=>{e.exports=require("chalk-template")},5086:e=>{e.exports=require("ws")},5692:e=>{e.exports=require("https")},5727:e=>{e.exports=require("skia-canvas")},5812:function(e,t,s){e=s.nmd(e);var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.parseVideo=h,t.prepareVideo=async function({vmafPrepareVideo:e,vmafVideoCrop:t,videoWidth:s,videoHeight:a,videoFramerate:n,videoDuration:l},p=!0){const[f,g]=e.split(","),m=o.default.join(o.default.dirname(f),`${g}_send.mp4`);if(r.default.existsSync(m))throw new Error(`Output file ${m} already exists`);const{width:v,height:w,frameRate:b}=await h(f);d.info(`prepareVideo ${f} ${v}x${w}@${b} -> ${m} ${t&&`crop: ${t}`}`);const y=Math.round((a||w)/18),S=Math.round(1.2*y),P=t?$(i.default.parse(t),0,","):"";await(0,c.runShellCommand)(`ffmpeg -hide_banner -loglevel warning -threads ${Math.min(u,16)} ${l?`-t ${l}`:""} -stream_loop -1 -i ${f} -filter_complex "[0:v]scale=w=${s||v}:h=${a||w},fps=${n||b},${P}drawbox=x=0:y=0:w=iw:h=${S}:color=black:t=fill,drawtext=fontfile=/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf:text='${g||0}-%{eif\\:t*1000\\:u}':fontcolor=white:fontsize=${y}:x=(w-text_w)/2:y=(${S}-text_h)/2[out]" -map [out] -fps_mode vfr -c:v libx264 -crf 10 -an -f mp4 -movflags +faststart ${m}`,!0),p||await r.default.promises.unlink(f)},t.convertToIvf=p,t.recognizeFrames=f,t.fixIvfFrames=m,t.fixIvfFiles=v,t.runVmaf=b,t.calculateVmafScore=R;const r=a(s(9896)),i=a(s(5865)),o=a(s(6928)),n=a(s(857)),c=s(6185),l=s(1707),d=(0,c.logger)("webrtcperf:vmaf"),u=n.default.cpus().length;async function h(e){let t=0,s=0,a=0;return await(0,c.ffprobe)(e,"video","frame=pts,width,height,duration_time","",(e=>{const r=parseInt(e.width),i=parseInt(e.height);r>t&&(t=r),i>s&&(s=i);const o=parseFloat(e.duration_time);return o&&(a=Math.max(Math.round(1/o),a)),c.FFProbeProcess.Skip})),{width:t,height:s,frameRate:a}}async function p(e,t,s=!0,a=!1){const{width:o,height:n,frameRate:l}=await h(e),p=e.replace(/\.[^.]+$/,".ivf.raw");d.debug(`convertToIvf ${e} ${o}x${n}@${l} -> ${p} crop:`,t);const f=t?`-vf '${$(i.default.parse(t))}'`:"";await(0,c.runShellCommand)(`ffmpeg -y -hide_banner -y -loglevel warning -i ${e} -map 0:v -c:v vp8 -quality best -cpu-used 0 -crf 1 -b:v 20M -qmin 1 -qmax 10 -g 1 -threads ${Math.min(u,16)} ${f} -an -f ivf ${p}`,!0),s||await r.default.promises.unlink(e),await m(p,!1,a)}async function f(e,t=!1,s=!1){const{width:a,height:r,frameRate:i}=await h(e),n=o.default.basename(e),l=new Map;let u=0,p=0,f=0,g=0,m=0,v="";const w=/(?<name>[0-9]{1,6})-(?<time>[0-9]{1,13})/;if(await(0,c.ffprobe)(e,"video","frame=pts,frame_tags=lavfi.ocr.text,lavfi.ocr.confidence","scale=w=1280:h=-1:flags=bicubic,crop=w=min(iw\\,ih):h=max((ih/15)\\,32):x=(iw-ow)/2:y=0:exact=1,ocr=whitelist=0123456789-",(e=>{const a=parseInt(e.pts);if(l.has(a)&&l.get(a)||!i)u++;else{const r=parseFloat(e.tag_lavfi_ocr_confidence?.trim()||"0"),o=w.exec(e.tag_lavfi_ocr_text?.trim()||"");if(r>0&&o){const{name:e,time:t}=o.groups;v=`Participant-${e.padStart(6,"0")}`;const c=parseInt(t),u=Math.round(i*c/1e3);s&&d.debug(`recognized frame ${n} confidence=${r} pts=${a} name=${e} time=${t} recognized=${u}`),l.set(a,u),g||(g=u/i),m=u/i}else t&&l.set(a,0),p++}return c.FFProbeProcess.Skip})),t){const e=Array.from(l.keys()).sort(((e,t)=>e-t));for(const[t,s]of e.entries()){if(!l.get(s)&&t){const a=l.get(e[t-1]);a?(l.set(s,a+s-e[t-1]),f++):l.delete(s)}}}return d.info(`recognizeFrames ${n} ${a}x${r}@${i} "${v}" frames: ${l.size} skipped: ${u} recovered: ${f} failed: ${p} ts: ${g.toFixed(2)}-${m.toFixed(2)} (${(m-g).toFixed(2)})`),{width:a,height:r,frameRate:i,frames:l,participantDisplayName:v}}async function g(e,t=!1){const{width:s,height:a}=await h(e),i=o.default.basename(e),n=await r.default.promises.open(e,"r"),c=new ArrayBuffer(32),l=new DataView(c);if(32!==(await n.read(l,0,32,0)).bytesRead)throw await n.close(),new Error("Invalid IVF file");const u=l.getUint32(16,!0)/l.getUint32(20,!0);let p="",g=0;const m=new DataView(new ArrayBuffer(12));let v=0,w=32,b=0;const y=new Map;let S=0,$=0;do{if(b=(await n.read(m,0,m.byteLength,w)).bytesRead,12!==b)break;const e=m.getUint32(0,!0),t=Number(m.getBigUint64(4,!0));y.has(t)?(d.debug(`IVF file ${i}: pts ${t} already present, skipping`),g++):(y.set(t,{pts:t,index:v,position:w,size:e+12}),v++,S||(S=t/u),$=t/u),w+=e+12}while(12===b);if(await n.close(),d.debug(`parseIvf ${i}: ${s}x${a}@${u} frames: ${y.size} skipped: ${g} ts: ${S.toFixed(2)}-${$.toFixed(2)} (${($-S).toFixed(2)}s)`),t){const{frames:t,participantDisplayName:s}=await f(e);p=s;for(const[e,s]of y.entries()){const a=t.get(e);a&&(s.recognizedPts=a)}}return{width:s,height:a,frameRate:u,frames:y,participantDisplayName:p}}async function m(e,t=!0,s=!1){const a=o.default.basename(e);d.debug(`fixIvfFrames ${a} keepSourceFile=${t} skipDuplicated=${s}`);const i=o.default.dirname(e);if(!a.endsWith(".ivf.raw"))throw new Error(`fixIvfFrames ${a}: invalid file extension, expected ".ivf.raw"`);const{width:n,height:c,frames:l,participantDisplayName:u}=await g(e,!0);if(!u)throw new Error(`fixIvfFrames ${a}: no participant name found`);if(!l.size)throw new Error(`fixIvfFrames ${a}: no frames found`);d.debug(`fixIvfFrames ${a} width=${n} height=${c} (${l.size} frames)`);const h=await r.default.promises.open(e,"r"),p=o.default.basename(e).split("_");if(!p[1].startsWith("send")&&!p[1].startsWith("recv"))throw new Error(`fixIvfFrames ${a}: invalid file name, expected "<name>_send" or "<name>_recv"`);const f=o.default.join(i,p[1].startsWith("send")?`${u}.ivf`:`${u}_recv-by_${p[0]}.ivf`),m=await r.default.promises.open(f,"w"),v=new DataView(new ArrayBuffer(32));await h.read(v,0,v.byteLength,0);let w=32,b=0;const y=Array.from(l.keys()).filter((e=>l.get(e)?.recognizedPts)).sort(((e,t)=>e===t?(l.get(e)?.recognizedPts||0)-(l.get(t)?.recognizedPts||0):e-t));for(const[e,t]of y.entries()){const r=l.get(t);if(!r||!r.recognizedPts){d.warn(`fixIvfFrames ${a}: pts ${t} not found, skipping`);continue}const i=l.get(y[e-1]),o=l.get(y[e+1]);if(o?.recognizedPts&&(o?.recognizedPts||0)<r.recognizedPts)continue;if(i?.recognizedPts&&r.recognizedPts===i.recognizedPts){if(d.debug(`${a}: duplicate recognized frame pts=${t}:${r.recognizedPts} prev=${i.pts}:${i.recognizedPts} next=${o?.pts}:${o?.recognizedPts} (${s?"skipped":"fixed"})`),s)continue;r.recognizedPts+=r.pts-i.pts}const n=new DataView(new ArrayBuffer(r.size));await h.read(n,0,r.size,r.position),n.setBigUint64(4,BigInt(r.recognizedPts),!0),await m.write(new Uint8Array(n.buffer),0,n.byteLength,w),w+=n.byteLength,b++}return v.setUint16(12,n,!0),v.setUint16(14,c,!0),v.setUint32(24,b,!0),await m.write(new Uint8Array(v.buffer),0,v.byteLength,0),await h.close(),await m.close(),t||await r.default.promises.unlink(e),{participantDisplayName:u,outFilePath:f}}async function v(e,t=!0,s=!1){d.debug(`fixIvfFiles ${e} keepSourceFiles=${t} skipDuplicated=${s}`);const a=new Map,r=new Map,i=(e,t)=>{t.includes("_recv-by_")?(r.has(e)||r.set(e,[]),r.get(e)?.push(t)):a.set(e,t)},n=await(0,c.getFiles)(e,".ivf");if(n.length){d.debug(`using existing ${n.length} ivf files`);for(const e of n)try{i(o.default.basename(e).replace(".ivf","").split("_")[0],e)}catch(e){d.error(`fixIvfFrames error: ${e.stack}`)}}const l=await(0,c.getFiles)(e,".ivf.raw");if(l.length){d.debug(`processing ${l.length} raw ivf files`);const e=await(0,c.chunkedPromiseAll)(l,(async e=>{try{const{participantDisplayName:a,outFilePath:r}=await m(e,t,s);return{participantDisplayName:a,outFilePath:r}}catch(e){d.error(`fixIvfFrames error: ${e.stack}`)}}),Math.ceil(u/4));for(const t of e){if(!t)continue;const{participantDisplayName:e,outFilePath:s}=t;i(e,s)}}return{reference:a,degraded:r}}async function w(e,t){const s=e.replace(".ivf",".filtered.ivf"),a=await r.default.promises.open(e,"r"),i=await r.default.promises.open(s,"w"),o=new DataView(new ArrayBuffer(32));await a.read(o,0,o.byteLength,0);let n=32,c=0;for(const e of t.values()){const t=new DataView(new ArrayBuffer(e.size));await a.read(t,0,e.size,e.position),await i.write(new Uint8Array(t.buffer),0,t.byteLength,n),n+=t.byteLength,c++}return o.setUint32(24,c,!0),await i.write(new Uint8Array(o.buffer),0,o.byteLength,0),await a.close(),await i.close(),s}async function b(e,t,s,a={},i=!1){const n=o.default.dirname(t),l=o.default.basename(t.replace(/\.[^.]+$/,"")),h=a[l],p={ref:S(h?.ref),deg:S(h?.deg)};d.info("runVmaf",{referencePath:e,degradedPath:t,preview:s,crop:p}),await r.default.promises.mkdir(o.default.join(n,l),{recursive:!0});const f=o.default.join(n,l,"vmaf-log.json"),m=o.default.join(n,l,"psnr.log"),v=o.default.join(n,l,"comparison.mp4"),b=o.default.basename(e).replace(".ivf",""),R=o.default.basename(t).replace(".ivf","").split("_recv-by_")[1],{width:T,height:k,frameRate:E,frames:x}=await g(e,!1),{width:C,height:_,frameRate:A,frames:F}=await g(t,!1),I=i?"(ih/15)":"";if(I&&(p.ref.h=`${p.ref.h}-${I}`,p.ref.y=`${p.ref.y}+${I}`,p.deg.h=`${p.deg.h}-${I}`,p.deg.y=`${p.deg.y}+${I}`),E!==A)throw new Error(`runVmaf: frame rates do not match: ref=${E} deg=${A}`);const D=[],L=[];let O=0,M=0;for(const[e,t]of x.entries()){const s=F.get(e);s&&(D.push(t),L.push(s),O||(O=e),M=e)}const B=(M-O)/E;e=await w(e,D),t=await w(t,L),d.debug(`common frames: ${D.length} ref: ${x.size} deg: ${F.size} duration: ${B}s`,{crop:p});const U=`ffmpeg -hide_banner -loglevel warning -y -threads ${Math.min(u,16)} -i ${t} -i ${e} `;let N=0;const j=C/_;T/k>j&&(N=Math.round(j*k));const H=`[0:v]scale=w=-1:h=${k}:flags=bicubic,${N?`crop=w=${N}:x=(iw-${N})/2,`:""}${$(p.deg,0,",")}${P(["deg_vmaf","deg_psnr",s?"deg_preview":""])};[1:v]scale=w=-1:h=${k}:flags=bicubic,${$(p.ref,0,",")}${P(["ref_vmaf","ref_psnr",s?"ref_preview":""])};[deg_vmaf][ref_vmaf]libvmaf=model='path=/usr/share/model/vmaf_v0.6.1.json':log_fmt=json:log_path=${f}:n_subsample=1:n_threads=${u}:shortest=1[vmaf];[deg_psnr][ref_psnr]psnr=stats_file=${m}[psnr]`,q=s?`${U} -filter_complex "${H};[ref_preview][deg_preview]hstack[stacked]" -map [vmaf] -f null - -map [psnr] -f null - -map [stacked] -fps_mode vfr -c:v libx264 -crf 10 -f mp4 -movflags +faststart ${v} `:`${U} -filter_complex "${H}" -map [vmaf] -f null - -map [psnr] -f null - `;d.debug("runVmaf",q);try{const{stdout:e,stderr:t}=await(0,c.runShellCommand)(q),s=JSON.parse(await r.default.promises.readFile(f,"utf-8"));d.debug("runVmaf",{stdout:e,stderr:t});const a={sender:b,receiver:R,...s.pooled_metrics.vmaf};d.info(`VMAF metrics ${f}:`,a);try{await y(f)}catch(e){d.error(`writeGraph error: ${e.stack}`)}return a}finally{await r.default.promises.unlink(t),await r.default.promises.unlink(e)}}async function y(e){const{CategoryScale:t,Chart:a,LinearScale:i,LineController:n,LineElement:c,PointElement:d,Legend:u,Title:h}=s(1021),{Canvas:p}=s(5727),f=JSON.parse(await r.default.promises.readFile(e,"utf-8")),{min:g,max:m,mean:v}=f.pooled_metrics.vmaf,w=e.replace(".json",".png"),b=Math.ceil(f.frames.length/500),y=new l.FastStats,S=f.frames.reduce(((e,t)=>(t.frameNum%b==0?e.push({x:t.frameNum,y:t.metrics.vmaf,count:1}):(e[e.length-1].y+=t.metrics.vmaf,e[e.length-1].count++),y.push(t.metrics.vmaf),e)),[]).map((e=>({x:e.x,y:e.y/e.count})));a.register([t,n,c,i,d,u,h]);const $=new p(1280,720),P=new a($,{type:"line",data:{labels:S.map((e=>e.x)),datasets:[{label:`VMAF score (min: ${g.toFixed(2)}, max: ${m.toFixed(2)}, mean: ${v.toFixed(2)}, P5: ${y.percentile(5).toFixed(2)})`,data:S.map((e=>e.y)),fill:!1,borderColor:"rgb(0, 0, 0)",borderWidth:1,pointRadius:0}]},options:{plugins:{title:{display:!0,text:o.default.basename(o.default.dirname(e)).replace(/_/g," ")}},scales:{y:{min:0,max:100}}}});await $.saveAs(w,{format:"png",matte:"white"}),P.destroy()}const S=e=>({w:e?.w??"iw",h:e?.h??"ih",x:e?.x??"0",y:e?.y??"0"}),$=(e,t=0,s="")=>{const{w:a,h:r,x:i,y:o}=e;return i||a||i||o?`crop=w=${a}:h=${r}:x=${i}:y=${o}:exact=${t}${s}`:""},P=(e,t="")=>{const s=e.filter((e=>!!e)).map((e=>`[${e}]`)).join("");return s?`split=${e.length}${s}${t}`:""};async function R(e){const{vmafPath:t,vmafPreview:s,vmafKeepIntermediateFiles:a,vmafKeepSourceFiles:n,vmafCrop:c,vmafSkipDuplicated:l}=e;if(!r.default.existsSync(e.vmafPath))throw new Error(`VMAF path ${e.vmafPath} does not exist`);d.debug(`calculateVmafScore referencePath=${t}`);const{reference:u,degraded:h}=await v(t,n,l),p=c?i.default.parse(c):void 0,f=[];for(const e of u.keys()){const t=u.get(e);if(t){for(const i of h.get(e)??[])try{const e=await b(t,i,s,p);f.push(e)}catch(e){d.error(`runVmaf error: ${e.message}`)}finally{a||await r.default.promises.unlink(i)}a||await r.default.promises.unlink(t)}}return await r.default.promises.writeFile(o.default.join(t,"vmaf.json"),JSON.stringify(f,void 0,2)),f}s.c[s.s]===e&&(async()=>{switch(process.argv[2]){case"convert":await p(process.argv[3],process.argv[4],!1);break;case"parse":{const{frames:e}=await g(process.argv[3],!0);console.log(e);break}case"fix":await m(process.argv[3],!0);break;case"analyze":console.log(JSON.stringify(await(0,c.analyzeColors)(process.argv[3]),null,2));break;case"graph":await y(process.argv[3]);break;case"vmaf":await R({vmafPath:process.argv[3],vmafPreview:!0,vmafKeepIntermediateFiles:!0,vmafKeepSourceFiles:!0,vmafCrop:i.default.stringify({"Participant-000001_recv-by_Participant-000000":{ref:{w:"",h:"",x:"",y:""},deg:{w:"",h:"",x:"",y:""}}})});break;default:throw new Error(`Invalid command: ${process.argv[2]}`)}})().catch((e=>console.error(e))).finally((()=>process.exit(0)))},5865:e=>{e.exports=require("json5")},6185:function(e,t,s){var a,r=this&&this.__createBinding||(Object.create?function(e,t,s,a){void 0===a&&(a=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,a,r)}:function(e,t,s,a){void 0===a&&(a=s),e[a]=t[s]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||(a=function(e){return a=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},a(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s=a(e),o=0;o<s.length;o++)"default"!==s[o]&&r(t,e,s[o]);return i(t,e),t}),n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.FFProbeProcess=t.PeerConnectionExternal=t.Scheduler=t.LoggerInterface=t.Log=void 0,t.logger=T,t.resolvePackagePath=function(e){if("__nexe"in process)return k.debug("resolvePackagePath (nexe)",e),e;{const t=S.default.normalize(S.default.join(S.default.dirname(__filename),e));return k.debug("resolvePackagePath (webpack)",t),t}},t.sha256=function(e){return(0,u.createHash)("sha256").update(e).digest("hex")},t.getProcessStats=async function(e=0,t=!1){const s=e||process.pid;let a=E.get(s);if(a)return a;const r=await(0,P.default)(s);a=r?{cpu:r.cpu,memory:r.memory/1e6}:{cpu:0,memory:0};if(t)try{let e=x.get(s);if(e?.length||(e=await(0,$.default)(s),e?.length&&x.set(s,e)),e?.length){const t=await(0,P.default)(e);for(const s of e)t[s]&&(a.cpu+=t[s].cpu,a.memory+=t[s].memory/1e6)}}catch(e){k.error(`getProcessStats children error: ${e.stack}`)}return E.set(s,a),a},t.getSocketStats=async function(e){const t={recvBytes:0,sendBytes:0};try{const{stdout:s}=await G(`ss -nOHpti | { grep pid=${e} || true; }`);for(const{groups:e}of s.matchAll(/bytes_sent:(?<sendBytes>\d+).+bytes_received:(?<recvBytes>\d+)/g)){if(!e)continue;const s=parseInt(e.recvBytes),a=parseInt(e.sendBytes);t.recvBytes+=s,t.sendBytes+=a}}catch(e){k.error(`socketStats error: ${e.stack}`)}return t},t.getSystemStats=function(){A||F();return C.get("default")},t.startUpdateSystemStats=F,t.stopTimers=I,t.sleep=D,t.startRandomActivateAudio=function(e,t,s,a){if(O)return;O=!0,M(e,t,s,a)},t.stopRandomActivateAudio=function(){O=!1,L&&clearTimeout(L)},t.randomActivateAudio=M,t.downloadUrl=async function(e,t,s,a,r=6e4){k.debug(`downloadUrl url=${e} ${s}`);const i=t?.split(":");let o=null;s&&(await f.default.promises.mkdir((0,S.dirname)(s),{recursive:!0}),o=(0,f.createWriteStream)(s));const n=await(0,d.default)({method:"get",url:e,auth:i?{username:i[0],password:i[1]}:void 0,headers:a?{Range:`bytes=${a}`}:void 0,timeout:r,onDownloadProgress:t=>{k.debug(`downloadUrl fileUrl=${e} progress=${t.progress||t.bytes}`)},httpsAgent:new g.Agent({rejectUnauthorized:!1}),responseType:o?"stream":"text"});if(o)return new Promise(((e,t)=>{if(!o)return;n.data.pipe(o);let s=null;o.once("error",(e=>{s=e,o&&o.close(),t(e)})),o.once("close",(()=>{s||e()}))}));{const e=n.headers["content-type"];let t=0,s=0,a=0;if(n.headers["content-range"]){const r=n.headers["content-range"].split("/");k.debug(`downloadUrl ${n.data.length} bytes, contentType=${e}, contentRange=${r}`);const i=r[0].split("-");a=parseInt(r[1]),2===i.length?(t=parseInt(i[0]),s=parseInt(i[1])):r[0].startsWith("-")?s=parseInt(i[0]):r[0].endsWith("-")&&(t=parseInt(i[0]),s=a)}return{data:n.data,start:t,end:s,total:a,contentType:e}}},t.uploadUrl=async function(e,t,s){k.debug(`uploadUrl ${e} to ${t}`);const a=s?.split(":"),r=new p.default;r.append("file",f.default.createReadStream(e));return(await(0,d.default)({method:"post",url:t,auth:a?{username:a[0],password:a[1]}:void 0,headers:r.getHeaders(),timeout:36e5,httpsAgent:new g.Agent({rejectUnauthorized:!1}),responseType:"text",data:r})).data},t.hideAuth=function(e){if(!e)return e;return e.replace(B,"$1")},t.registerExitHandler=function(e){U.add(e)},t.unregisterExitHandler=function(e){U.delete(e)},t.runExitHandlersNow=H,t.checkChromeExecutable=W,t.clampMinMax=V,t.runShellCommand=G,t.resolveIP=async function(e,t=36e5){if(!e)return"";if("private"===m.parse(e).range())return e;const s=Date.now(),a=z.get(e);if(!a||s-a.timestamp>t){return await Promise.race([D(1e3),h.promises.reverse(e).then((a=>a.length?(k.debug(`resolveIP ${e} -> ${a.join(", ")}`),z.set(e,{host:a[0],timestamp:s+10*t}),a[0]):(z.set(e,{host:e,timestamp:s}),e))).catch((t=>{k.debug(`resolveIP error: ${t.stack}`),z.set(e,{host:e,timestamp:s})}))])||e}return a?.host||e},t.stripColors=function(e){return e.replace(/\x1B[[(?);]{0,2}(;?\d)*./g,"")},t.systemGpuStats=Q,t.toTitleCase=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},t.getFiles=async function e(t,s){const a=await f.default.promises.readdir(t,{withFileTypes:!0}),r=await Promise.all(a.map((a=>{const r=S.default.resolve(t,a.name);return a.isDirectory()?e(r,s):r})));return Array.prototype.concat(...r).filter((e=>e.endsWith(s)))},t.toPrecision=function(e,t=3){return(Math.round(e*10**t)/10**t).toFixed(t)},t.getDefaultNetworkInterface=X,t.checkNetworkInterface=async function(e){await G(`ip route | grep -q "dev ${e}"`)},t.portForwarder=async function(e,t){t||(t=await X());const s=new AbortController;return Object.entries((0,y.networkInterfaces)()).forEach((([a,r])=>{if(("0.0.0.0"===t||a===t)&&r)for(const t of r){if(t.internal||"127.0.0.1"===t.address||"IPv4"!==t.family)continue;const r=`portForwarder on ${a} (${t.address}:${e})`,i=v.default.createServer((t=>{const s=v.default.createConnection({host:"127.0.0.1",port:e});t.once("error",(e=>{k.error(`${r} error: ${e.stack}`),s.destroy()})),s.once("error",(e=>{k.error(`${r} error: ${e.stack}`),t.destroy()})),t.pipe(s),s.pipe(t)})).listen({port:e,host:t.address,signal:s.signal});i.on("listening",(()=>{k.debug(`${r} listening`)})),i.once("error",(e=>{k.error(`${r} error: ${e.stack}`)}))}})),()=>{k.debug(`portForwarder on port ${e} stop`),s.abort()}},t.pageScreenshot=async function(e,t,s=1920,a=1024,r="body",i,o){k.debug(`pageScreenshot ${e} -> ${t}`),await f.default.promises.mkdir(S.default.dirname(t),{recursive:!0});let n=process.env.CHROMIUM_PATH;n&&f.default.existsSync(n)||(n=await W());const c=await R.default.launch({headless:!0,executablePath:n,defaultViewport:{width:s,height:a,deviceScaleFactor:1,isMobile:!1,hasTouch:!1,isLandscape:!1},args:["--no-sandbox","--disable-setuid-sandbox"]}),l=await c.newPage();i&&await l.setExtraHTTPHeaders(i);o&&await l.evaluateOnNewDocument((e=>{document.addEventListener("DOMContentLoaded",(()=>{const t=document.createElement("style");t.setAttribute("id","webrtcperf-extra-style"),t.setAttribute("type","text/css"),t.innerHTML=e,document.head.appendChild(t)}))}),o);await l.goto(e,{waitUntil:["domcontentloaded","networkidle0"],timeout:6e4});try{const e=await l.waitForSelector(r,{visible:!0,timeout:15e3});if(!e)throw new Error(`pageScreenshot selector "${r}" not found`);const s=t;await e.screenshot({path:s})}catch(e){k.error(`pageScreenshot error: ${e.message}`)}finally{await l.close(),await c.close()}},t.enabledForSession=Z,t.increaseKey=function(e,t,s){if(void 0===s||!isFinite(s))return;void 0===e[t]&&(e[t]=0);e[t]+=s},t.chunkedPromiseAll=async function(e,t,s=1){const a=Array(e.length);for(let r=0;r<e.length;r+=s)await Promise.allSettled(e.slice(r,r+s).map((async(e,s)=>{const i=await t(e,r+s);void 0!==i&&(a[r+s]=i)})));return a},t.maybeNumber=function(e){const t=parseFloat(e);return isNaN(t)?e:t},t.ffprobe=te,t.buildIvfHeader=function(e=1920,t=1080,s=30,a="MJPG"){const r=Buffer.alloc(32);return r.write("DKIF",0,"utf8"),r.writeUint16LE(0,4),r.writeUint16LE(32,6),r.write(a,8,"utf8"),r.writeUint16LE(e,12),r.writeUint16LE(t,14),r.writeUint32LE(s,16),r.writeUint32LE(1,20),r.writeUint32LE(0,24),r.writeUint32LE(0,28),r},t.ffmpeg=async function(e="video",t){const a=1e4+Math.floor(1e4*Math.random()),r=`exec ffmpeg -hide_banner -loglevel warning ${e} zmq:tcp://127.0.0.1:${a}`;k.debug(`${r}`);let i="";const o=new(s(6584).Subscriber),n=(0,l.spawn)(r,{shell:!0,stdio:["ignore","ignore","pipe"]});n.stderr.on("data",(e=>{i+=e})),n.once("error",(e=>{throw o.close(),e})),n.once("close",(e=>{if(o.close(),0!==e)throw new Error(`${r} failed with code ${e}: ${i}`)})),o.connect(`tcp://127.0.0.1:${a}`),o.subscribe("");for await(const[e]of o)await t(e);o.close()},t.analyzeColors=async function(e){let t=0,s=0,a=0,r=0,i=0,o=0;return await te(e,"video","frame=lavfi.signalstats.YAVG,lavfi.signalstats.UAVG,lavfi.signalstats.VAVG,lavfi.signalstats.SATAVG,lavfi.signalstats.HUEAVG","signalstats",(e=>(t+=parseFloat(e.tag_lavfi_signalstats_YAVG),s+=parseFloat(e.tag_lavfi_signalstats_UAVG),a+=parseFloat(e.tag_lavfi_signalstats_VAVG),r+=parseFloat(e.tag_lavfi_signalstats_SATAVG),i+=parseFloat(e.tag_lavfi_signalstats_HUEAVG),o++,ee.Skip))),{YAvg:t/o,UAvg:s/o,VAvg:a/o,SatAvg:r/o,HueAvg:i/o}},t.waitStopProcess=async function(e,t=5e3){k.debug(`waitStopProcess pid: ${e} timeout: ${t}`);const s=Date.now();for(;Date.now()-s<t;)try{process.kill(e,0),await D(Math.max(t/10,200))}catch{return!0}k.warn(`waitStopProcess pid: ${e} timeout`);try{process.kill(e,"SIGKILL")}catch{return!0}return!1},t.getDockerLogsPath=async function(){const e=await f.default.promises.readFile(`${y.default.homedir()}/.webrtcperf/docker.id`,"utf-8"),t=`/var/lib/docker/containers/${e}/${e}-json.log`;if(!f.default.existsSync(t))throw new Error(`docker logs path ${t} not found`);return t};const c=s(1046),l=s(3200),d=n(s(8938)),u=s(6982),h=o(s(2250)),p=n(s(2305)),f=o(s(9896)),g=s(5692),m=o(s(6247)),v=n(s(9278)),w=n(s(2011)),b=o(s(1845)),y=o(s(857)),S=o(s(6928)),$=n(s(981)),P=n(s(7648)),R=n(s(8034));s(4412);function T(e,s={}){return new t.Log(e,{splitLine:!1,...s})}t.Log=s(3940).Log;t.LoggerInterface=class{name;logInit(e){this.name&&e.unshift(`[${this.name}]`)}debug(...e){this.logInit(e),k.debug(...e)}info(...e){this.logInit(e),k.info(...e)}warn(...e){this.logInit(e),k.warn(...e)}error(...e){this.logInit(e),k.error(...e)}log(...e){this.logInit(e),k.log(...e)}};const k=T("webrtcperf:utils");const E=new w.default({stdTTL:5,checkperiod:10}),x=new w.default({stdTTL:15,checkperiod:15});const C=new w.default({stdTTL:30,checkperiod:60});async function _(){const[e,t,s]=await Promise.all([b.cpu.free(1e4),b.mem.info(),Q()]),a={usedCpu:100-e,usedMemory:100-t.freeMemPercentage,usedGpu:s.gpu,usedGpuMemory:s.mem};C.set("default",a)}let A=null;function F(){A||(A=setInterval(_,5e3))}function I(){A&&(clearInterval(A),A=null)}function D(e){return new Promise((t=>setTimeout((()=>t()),e)))}let L=null,O=!1;async function M(e,t,s,a){if(t&&O)try{let t=[];for(const s of e.values()){const e=[...s.pages.values()];Z(s.id,a)&&(t=t.concat(e))}for(const[e,s]of t.entries()){if(!s)continue;let a=0;try{a=await s.evaluate((()=>webrtcperf.getActiveAudioTracks().length))}catch(e){k.error(`randomActivateAudio error: ${e.stack}`)}a||(t[e]=null)}const r=t.filter((e=>!!e)),i=Math.floor(Math.random()*r.length),o=Math.round(100*Math.random())<=s;for(const[e,t]of r.entries())try{e===i?(k.debug(`Changing audio in page ${e+1}/${r.length} (enable: ${o})`),await t.evaluate((async e=>{"undefined"!=typeof publisherSetMuted?await publisherSetMuted(!e):webrtcperf.getActiveAudioTracks().forEach((t=>{t.enabled=e}))}),o)):await t.evaluate((async()=>{"undefined"!=typeof publisherSetMuted?await publisherSetMuted(!0):webrtcperf.getActiveAudioTracks().forEach((e=>{e.enabled=!1}))}))}catch(t){k.error(`randomActivateAudio in page ${e+1}/${r.length} error: ${t.stack}`)}}catch(e){k.error(`randomActivateAudio error: ${e.stack}`)}finally{if(O){const r=t*(1+Math.random());L&&clearTimeout(L),L=setTimeout(M,1e3*r,e,t,s,a)}}}const B=new RegExp("(http[s]{0,1}://)(.+?:.+?@)","g");const U=new Set;const N=async e=>{let t=0;for(const s of U.values()){const a=`${t+1}/${U.size}`;k.debug(`running exitHandler ${a}`);try{await s(e),k.debug(` exitHandler ${a} done`)}catch(e){k.error(`exitHandler ${a} error: ${e}`)}t++}U.clear()};let j=null;async function H(e){j||(j=N(e)),await j,I()}const q=["beforeExit","uncaughtException","unhandledRejection","SIGHUP","SIGINT","SIGQUIT","SIGILL","SIGTRAP","SIGABRT","SIGBUS","SIGFPE","SIGUSR1","SIGSEGV","SIGUSR2","SIGTERM"];async function W(){const{loadConfig:e}=s(7028),t=(await e())[0],a=S.default.join(y.default.homedir(),".webrtcperf/chrome"),r=e=>e.split(".").slice(0,3).join("."),i=(await(0,c.getInstalledBrowsers)({cacheDir:a})).map((e=>r(e.buildId))),o=c.Browser.CHROME;i.sort((0,c.getVersionComparator)(o)),k.debug(`Available chrome versions: ${i}`);const n=t.chromiumVersion;if(!n)throw new Error("Chromium version not set");if(!i.includes(r(n))){k.info(`Downloading chrome ${n}...`);let e=0;await(0,c.install)({browser:o,buildId:n,cacheDir:a,downloadProgressCallback:(t,s)=>{const a=Math.round(100*t/s);a-e>1&&(e=a,k.info(` ${e}%`))}}),k.info(`Downloading chrome ${n} done.`)}return(0,c.computeExecutablePath)({browser:o,cacheDir:a,buildId:n})}function V(e,t,s){return Math.max(Math.min(e,s),t)}async function G(e,t=!1,s=1048576,{provideStdin:a=!1,returnStdout:r=!0,returnStderr:i=!0}={}){return t&&k.debug(`runShellCommand cmd: ${e}`),new Promise(((o,n)=>{const c=(0,l.spawn)(e,{shell:!0,stdio:[a?"inherit":"ignore",r?"pipe":"inherit",i?"pipe":"inherit"],detached:!0});let d="",u="";r&&c.stdout?.on("data",(e=>{s&&d.length>s&&(d=d.slice(e.length)),d+=e})),i&&c.stderr?.on("data",(e=>{s&&u.length>s&&(u=u.slice(e.length)),u+=e})),c.once("error",(e=>n(e))),c.once("close",(s=>{0!==s?n(new Error(`runShellCommand cmd: ${e} failed with code ${s}: ${u}`)):(t&&k.debug(`runShellCommand cmd: ${e} done`,{stdout:d,stderr:u}),o({stdout:d,stderr:u}))}))}))}process.setMaxListeners(process.getMaxListeners()+q.length),q.forEach((e=>process.once(e,(async e=>{e instanceof Error?k.error(`Exit on error: ${e.stack||e.message}`):k.debug(`Exit on signal: ${e}`),await H(e),process.exit(0)}))));const z=new Map;const J=f.default.existsSync("/usr/bin/nvidia-smi")&&f.default.existsSync("/dev/dri"),K="darwin"===process.platform&&f.default.existsSync("/usr/sbin/ioreg");async function Q(){try{if(J){const{stdout:e}=await G("nvidia-smi --query-gpu=utilization.gpu,utilization.memory --format=csv"),t=e.split("\n")[1].trim(),[s,a]=t.split(",").map((e=>parseFloat(e.replace(" %",""))));return{gpu:s,mem:a}}if(K){const{stdout:e}=await G('ioreg -r -d 1 -w 0 -c IOAccelerator | grep PerformanceStatistics\\"'),t=JSON.parse(e.trim().split(" = ")[1].replace(/=/g,":"));return{gpu:t["Device Utilization %"]||t["GPU Activity(%)"]||0,mem:0}}}catch(e){k.debug(`systemGpuStats error: ${e.stack}`)}return{gpu:0,mem:0}}t.Scheduler=class{name;interval;callback;verbose;running=!1;last=0;errorSum=0;statsTimeoutId;constructor(e,t,s,a=!1){this.name=e,this.interval=1e3*t,this.callback=s,this.verbose=a,k.debug(`[${this.name}-scheduler] constructor interval=${this.interval}ms`)}start(){k.debug(`[${this.name}-scheduler] start`),this.running=!0,this.scheduleNext()}async stop(){k.debug(`[${this.name}-scheduler] stop`),this.running=!1,this.statsTimeoutId&&clearTimeout(this.statsTimeoutId);try{await this.callback(Date.now())}catch(e){k.error(`[${this.name}-scheduler] stop callback error: ${e.stack}`,e)}}scheduleNext(){if(!this.running)return;const e=Date.now();this.last&&(this.errorSum+=V(e-this.last-this.interval,-this.interval,this.interval),this.verbose&&k.debug(`[${this.name}-scheduler] last=${e-this.last}ms drift=${this.errorSum}ms`)),this.last=e,this.statsTimeoutId=setTimeout((async()=>{try{const e=Date.now();await this.callback(e);const t=Date.now()-e;t>this.interval?k.warn(`[${this.name}-scheduler] callback elapsed=${t}ms > ${this.interval}ms`):this.verbose&&k.debug(`[${this.name}-scheduler] callback elapsed=${t}ms`)}catch(e){k.error(`[${this.name}-scheduler] callback error: ${e.stack}`,e)}finally{this.scheduleNext()}}),this.interval-this.errorSum/2)}};class Y{id;process;static cache=new Map;constructor(e){this.process=(0,l.spawn)("sleep",["600"]),this.id=this.process.pid||-1,k.debug(`PeerConnectionExternal contructor: ${this.id}`,e),Y.cache.set(this.id,this),this.process.stdout.on("data",(e=>{k.debug(`PeerConnectionExternal stdout: ${e}`)})),this.process.stderr.on("data",(e=>{k.debug(`PeerConnectionExternal stderr: ${e}`)})),this.process.on("close",(e=>{k.debug(`PeerConnectionExternal process exited with code ${e}`),Y.cache.delete(this.id)}))}static get(e){return Y.cache.get(e)}async createOffer(e){return k.debug("PeerConnectionExternal createOffer",{options:e}),{}}setLocalDescription(e){k.debug("PeerConnectionExternal setLocalDescription",e)}setRemoteDescription(e){k.debug("PeerConnectionExternal setRemoteDescription",e)}}async function X(){const{stdout:e}=await G("ip route | awk '/default/ {print $5; exit}' | tr -d ''");return e.trim()}function Z(e,t){if(!0===t||"true"===t)return!0;if(!1===t||"false"===t||void 0===t)return!1;if("string"==typeof t){if(t.includes("-")){const[s,a]=t.split("-").map((e=>parseInt(e)));return!(isFinite(s)&&e<s)&&!(isFinite(a)&&e>a)}return t.split(",").map((e=>e.trim())).filter((e=>e.length)).map((e=>parseInt(e))).includes(e)}return e===t}var ee;async function te(e,t="video",s="",a="",r){const i=`exec ffprobe -loglevel error ${"video"===t?"-select_streams v":""} -show_frames -print_format compact ${s?`-show_entries ${s}`:""} -f lavfi -i '${"video"===t?"":"a"}movie=${e}${a?`,${a}`:""}'`,o=[];let n="",c=!1;return new Promise(((e,t)=>{const s=(0,l.spawn)(i,{shell:!0,stdio:["ignore","pipe","pipe"]});s.stdout.on("data",(e=>{if(c)return;const t=e.toString().split("|").reduce(((e,t)=>{const[s,a]=t.split("=");return a&&!s.startsWith("side_datum")&&(e[s.replace(/[:.]/g,"_")]=a),e}),{});if(r){const e=r(t);e===ee.Skip||(e===ee.Stop?(c=!0,s.kill("SIGINT")):o.push(e))}else o.push(t)})),s.stderr.on("data",(e=>{n+=e})),s.once("error",(e=>t(e))),s.once("close",(s=>{0===s||c?e(o):t(new Error(`${i} failed with code ${s}: ${n}`))}))}))}t.PeerConnectionExternal=Y,function(e){e.Skip="skip",e.Stop="stop"}(ee||(t.FFProbeProcess=ee={}))},6247:e=>{e.exports=require("ipaddr.js")},6259:e=>{e.exports=require("marked-terminal")},6261:e=>{e.exports=require("events")},6584:e=>{e.exports=require("zeromq")},6928:e=>{e.exports=require("path")},6958:function(e,t,s){var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.runWithDocker=async function(e){const t=new i.default,s=e.filter((e=>"--docker"!==e))[0];if(!s)throw new Error("No configuration file specified");const a=r.default.basename(s),d=(await(0,n.loadConfig)(s))[0],u=Date.now(),h=r.default.resolve(r.default.dirname(s),"logs",`${u}`);await c.default.promises.mkdir(h,{recursive:!0});const p=[`${r.default.resolve(s)}:/config/${a}:ro`,"/dev/shm:/dev/shm",`${h}:/data`,"/tmp/webrtcperf-cache:/root/.webrtcperf"];if(d.scriptPath){const e=r.default.basename(d.scriptPath);p.push(`${r.default.resolve(d.scriptPath)}:/scripts/${e}:ro`)}process.env.DEBUG_SRC&&p.push(`${(0,o.resolvePackagePath)("app.min.js")}:/app/app.min.js:ro`);const f={},g={};if(d.debuggingPort)for(let e=0;e<d.sessions;e++){const t=`${d.debuggingPort+e}/tcp`;f[t]=[{HostPort:`${d.debuggingPort+e}`}],g[t]={}}const m=[`DEBUG_LEVEL=${process.env.DEBUG_LEVEL||"info"}`,"SHOW_PAGE_LOG=false","SHOW_STATS=false","SERVER_PORT=5000","SERVER_USE_HTTPS=true","SERVER_DATA=/data",`START_TIMESTAMP=${u}`,"STATS_PATH=/data/stats.csv","PAGE_LOG_PATH=/data/page.log","DETAILED_STATS_PATH=/data/detailed-stats.csv"];if(d.scriptPath){const e=r.default.basename(d.scriptPath);m.push(`SCRIPT_PATH=/scripts/${e}`)}d.debuggingPort&&m.push(`DEBUGGING_PORT=${d.debuggingPort}`);d.prometheusPushgateway.startsWith("http://localhost")&&m.push("PROMETHEUS_PUSHGATEWAY=http://pushgateway:9091");const v={Image:"ghcr.io/vpalmisano/webrtcperf:devel",name:"webrtcperf",Cmd:[`/config/${a}`],HostConfig:{Binds:p,PortBindings:f,CapAdd:["NET_ADMIN"],NetworkMode:d.prometheusPushgateway.startsWith("http://localhost")?"prometheus-stack_default":"bridge"},Env:m,AttachStdin:!0,AttachStdout:!0,AttachStderr:!0,Tty:!0,OpenStdin:!0,StdinOnce:!0,ExposedPorts:g};try{process.env.DEBUG_SRC||(l.info("Pulling latest webrtcperf image..."),await t.pull("ghcr.io/vpalmisano/webrtcperf:devel"));try{const e=await t.getContainer("webrtcperf");await e.remove({force:!0})}catch(e){}const e=await t.createContainer(v);await e.start();const s=await e.attach({stream:!0,stdin:!0,stdout:!0,stderr:!0});process.stdin.pipe(s),s.pipe(process.stdout),await new Promise((t=>{e.wait(((e,s)=>{e&&l.error("Error waiting for container:",s,e.stack),t(s)}))})),await e.remove()}catch(e){throw l.error("Docker operation failed:",e),e}};const r=a(s(6928)),i=a(s(1410)),o=s(6185),n=s(7028),c=a(s(9896)),l=(0,o.logger)("webrtcperf:docker")},6976:function(e,t,s){var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Session=void 0;const r=s(7191),i=a(s(2613)),o=a(s(8938)),n=a(s(6261)),c=a(s(9896)),l=a(s(5865)),d=s(9026),u=a(s(2011)),h=a(s(857)),p=a(s(6928)),f=a(s(8034)),g=s(8219),m=s(3106),v=s(7564),w=s(1707),b=s(6185),{default:y}=s(5072),S=s(9993),$=(0,b.logger)("webrtcperf:session"),P={error:"red",warn:"yellow",info:"cyan",log:"grey",debug:"white",requestfailed:"magenta"};class R extends n.default{chromiumUrl;chromiumPath;chromiumFieldTrials;windowWidth;windowHeight;deviceScaleFactor;display;mediaPath;videoWidth;videoHeight;videoFramerate;useFakeMedia;enableGpu;enableBrowserLogging;startTimestamp;sessions;tabsPerSession;spawnPeriod;statsInterval;disabledVideoCodecs;localStorage;sessionStorage;clearCookies;scriptPath;showPageLog;pageLogFilter;pageLogPath;userAgent;evaluateAfter;exposedFunctions;scriptParams;blockedUrls;extraHeaders;responseModifiers={};downloadResponses=[];extraCSS;cookies=[];overridePermissions=[];hardwareConcurrency;debuggingPort;debuggingAddress;randomAudioPeriod;maxVideoDecoders;maxVideoDecodersRange;incognito;serverPort;serverSecret;serverUseHttps;emulateCpuThrottling;running=!1;browser;context;stopPortForwarder;id;throttleIndex;useBrowserThrottling;url;urlQuery;customUrlHandler;customUrlHandlerFn;stats={};pages=new Map;httpResourcesStats=new Map;pagesMetrics=new Map;pageWarnings=0;pageErrors=0;screensharePage;static jsonFetchCache=new u.default({stdTTL:30,checkperiod:15});constructor({chromiumUrl:e,chromiumPath:t,chromiumFieldTrials:s,windowWidth:a,windowHeight:r,deviceScaleFactor:o,display:n,url:c,urlQuery:d,customUrlHandler:u,customUrlHandlerFn:h,mediaPath:p,videoWidth:f,videoHeight:m,videoFramerate:v,useFakeMedia:w,enableGpu:y,enableBrowserLogging:S,startTimestamp:P,sessions:R,tabsPerSession:T,spawnPeriod:k,statsInterval:E,disabledVideoCodecs:x,localStorage:C,sessionStorage:_,clearCookies:A,scriptPath:F,showPageLog:I,pageLogFilter:D,pageLogPath:L,userAgent:O,id:M,throttleIndex:B,useBrowserThrottling:U,evaluateAfter:N,exposedFunctions:j,scriptParams:H,blockedUrls:q,extraHeaders:W,responseModifiers:V,downloadResponses:G,extraCSS:z,cookies:J,overridePermissions:K,hardwareConcurrency:Q,debuggingPort:Y,debuggingAddress:X,randomAudioPeriod:Z,maxVideoDecoders:ee,maxVideoDecodersRange:te,incognito:se,serverPort:ae,serverSecret:re,serverUseHttps:ie,emulateCpuThrottling:oe}){if(super(),$.debug("constructor",{id:M}),this.id=M,this.chromiumUrl=e,this.chromiumPath=t||void 0,this.chromiumFieldTrials=s||void 0,this.windowWidth=a||1920,this.windowHeight=r||1080,this.deviceScaleFactor=o||1,this.debuggingPort=Y||0,this.debuggingAddress=X||"",this.display=n,this.url=c,this.urlQuery=d,!this.urlQuery&&c.includes("?")){const e=c.split("?",2);this.url=e[0],this.urlQuery=e[1]}if(this.customUrlHandler=u,this.customUrlHandlerFn=h,this.mediaPath=p,this.videoWidth=f,this.videoHeight=m,this.videoFramerate=v,this.useFakeMedia=w,this.enableGpu=y,this.enableBrowserLogging=(0,b.enabledForSession)(this.id,S),this.startTimestamp=P||Date.now(),this.sessions=R||1,this.tabsPerSession=T||1,(0,i.default)(this.tabsPerSession>=1,"tabsPerSession should be >= 1"),this.spawnPeriod=k||1e3,this.statsInterval=E||10,this.disabledVideoCodecs=x?x.split(",").map((e=>e.trim())).filter((e=>e.length)):[],C)try{this.localStorage=l.default.parse(C)}catch(e){$.error(`error parsing localStorage: ${e.stack}`),this.localStorage=null}if(_)try{this.sessionStorage=l.default.parse(_)}catch(e){$.error(`error parsing sessionStorage: ${e.stack}`),this.sessionStorage=null}if(this.clearCookies=A,this.scriptPath=F,this.showPageLog=I,this.pageLogFilter=D,this.pageLogPath=L,this.userAgent=O,this.randomAudioPeriod=Z,this.maxVideoDecoders=ee,this.maxVideoDecodersRange=te,this.incognito=se,this.serverPort=ae,this.serverSecret=re,this.serverUseHttps=ie,this.emulateCpuThrottling=oe,this.throttleIndex=B,this.useBrowserThrottling=U,this.evaluateAfter=N||[],this.exposedFunctions=j||{},H)try{this.scriptParams=l.default.parse(H)}catch(e){throw $.error(`error parsing scriptParams '${H}': ${e.stack}`),e}else this.scriptParams={};if(this.blockedUrls=(q||"").split(",").map((e=>e.trim())).filter((e=>e.length)),this.blockedUrls.push("ingest.sentry.io"),W)try{this.extraHeaders=l.default.parse(W)}catch(e){$.error(`error parsing extraHeaders: ${e.stack}`),this.extraHeaders=void 0}else this.extraHeaders=void 0;if(V)try{const e=l.default.parse(V);Object.entries(e).forEach((([e,t])=>{if(!Array.isArray(t))throw new Error(`responseModifiers replacements should be an array of { search, replace, body, headers } objects: ${t}`);this.responseModifiers[e]=t.map((({search:e,regexp:t,replace:s,file:a,headers:r})=>({search:t?new RegExp(t,"g"):e,replace:s,file:a,headers:r})))}))}catch(e){throw new Error(`error parsing responseModifiers "${V}": ${e.stack}`)}if(G)try{const e=l.default.parse(G);if(!Array.isArray(e))throw new Error(`downloadResponses should be an array: ${G}`);e.forEach((({urlPattern:e,output:t,append:s})=>{this.downloadResponses.push({urlPattern:(0,g.getUrlPatternRegExp)(e),output:t,append:s})}))}catch(e){throw new Error(`error parsing downloadResponses "${G}": ${e.stack}`)}if(this.extraCSS=z,J)try{this.cookies=l.default.parse(J)}catch(e){$.error(`error parsing cookies: ${e.stack}`)}K&&(this.overridePermissions=K.split(",").map((e=>e.trim())).filter((e=>e.length))),this.hardwareConcurrency=Q}getBrowserArgs(e){const t=["--no-sandbox","--no-zygote","--ignore-certificate-errors","--no-user-gesture-required","--autoplay-policy=no-user-gesture-required","--disable-infobars","--no-default-browser-check","--allow-running-insecure-content",`--unsafely-treat-insecure-origin-as-secure=http://${new URL(this.url||"http://localhost").host}`,"--disable-web-security","--disable-features=IsolateOrigins,Translate,CalculateNativeWinOcclusion","--disable-background-timer-throttling","--disable-backgrounding-occluded-windows","--disable-renderer-backgrounding","--disable-site-isolation-trials","--enable-usermedia-screen-capturing","--allow-http-screen-capture",`--remote-debugging-port=${this.debuggingPort?this.debuggingPort+this.id:0}`,"--enable-features=VaapiVideoDecoder,VaapiVideoEncoder,VaapiVideoDecodeLinuxGL,ElementCapture",`--window-size=${this.windowWidth},${this.windowHeight}`];let s=this.chromiumFieldTrials||"";if(this.enableBrowserLogging&&this.pageLogPath){const a=p.default.dirname(this.pageLogPath),r=p.default.resolve(a,`webrtc-event-logging-${this.id}`);c.default.mkdirSync(r,{recursive:!0}),t.push("--enable-logging","--vmodule=*/webrtc/*=5","--v=0",`--webrtc-event-logging=${r}`),s="WebRTC-RtcEventLogNewFormat/Disabled/"+s,e.CHROME_LOG_FILE=p.default.resolve(a,`chrome-${this.id}.log`)}return-1!==this.maxVideoDecoders&&(0,b.enabledForSession)(this.id,this.maxVideoDecodersRange)&&(s=`WebRTC-MaxVideoDecoders/${this.maxVideoDecoders}/`+s),s.length&&t.push(`--force-fieldtrials=${s}`),this.mediaPath&&(this.useFakeMedia?($.debug(`${this.id} using chromium as fake media source`),t.push("--use-fake-ui-for-media-stream","--use-fake-device-for-media-stream=display-media-type=browser,fps=30",`--use-file-for-fake-video-capture=${this.mediaPath.video}`,`--use-file-for-fake-audio-capture=${this.mediaPath.audio}`)):($.debug(`${this.id} using ${this.mediaPath} as fake media source`),t.push("--auto-accept-camera-and-microphone-capture","--auto-select-tab-capture-source-by-title=webrtcperf-screenshare","--mute-audio"))),this.enableGpu?(t.push("--ignore-gpu-blocklist","--enable-gpu-rasterization","--enable-zero-copy","--disable-gpu-sandbox","--enable-vulkan"),"egl"===this.enableGpu?t.push("--use-gl=egl"):t.push("--use-gl=angle","--use-angle=vulkan")):t.push("--disable-3d-apis","--disable-site-isolation-trials"),t}async start(){if(!this.running)if(this.running=!0,this.browser)$.warn(`${this.id} start: already running`);else{if($.debug(`${this.id} start`),this.chromiumUrl)try{this.browser=await f.default.connect({browserURL:this.chromiumUrl,defaultViewport:{width:this.windowWidth,height:this.windowHeight,deviceScaleFactor:this.deviceScaleFactor,isMobile:!1,hasTouch:!1,isLandscape:!0}})}catch(e){return $.error(`${this.id} browser connect error: ${e.stack}`),this.stop()}else{let e=this.chromiumPath;e&&c.default.existsSync(e)||(e=await(0,b.checkChromeExecutable)(),$.debug(`using executablePath=${e}`)),this.throttleIndex>-1&&"linux"===h.default.platform()&&(e=await(0,r.throttleLauncher)(e,this.throttleIndex));const t={...process.env};this.display?t.DISPLAY=this.display:delete t.DISPLAY;const s=this.getBrowserArgs(t),a=["--disable-dev-shm-usage","--remote-debugging-port","--enable-automation","--window-size"];$.debug(`[session ${this.id}] Using args:\n ${s.join("\n ")}`),$.debug(`[session ${this.id}] Default args:\n ${f.default.defaultArgs().join("\n ")}`);try{this.browser=await f.default.launch({browser:"chrome",headless:!this.display,executablePath:e,handleSIGINT:!1,env:t,defaultViewport:{width:this.windowWidth,height:this.windowHeight,deviceScaleFactor:this.deviceScaleFactor,isMobile:!1,hasTouch:!1,isLandscape:!1},ignoreDefaultArgs:a,args:s});const r=await this.browser.version();$.debug(`[session ${this.id}] Using chrome version: ${r}`)}catch(e){return $.error(`[session ${this.id}] Browser launch error: ${e.stack}`),this.stop()}}(0,i.default)(this.browser,"BrowserNotCreated"),this.debuggingPort&&"127.0.0.1"!==this.debuggingAddress&&(this.stopPortForwarder=await(0,b.portForwarder)(this.debuggingPort+this.id,this.debuggingAddress)),this.browser.once("disconnected",(()=>($.debug("browser disconnected"),this.stop())));for(let e=0;e<this.tabsPerSession;e++)this.openPage(e).catch((e=>$.error(`openPage error: ${e.stack}`))),e<this.tabsPerSession-1&&await(0,b.sleep)(this.spawnPeriod)}}setupPageCmd(e,t,s){let a=`webrtcperf = {};\nwebrtcperf.config = {\n START_TIMESTAMP: ${this.startTimestamp},\n WEBRTC_PERF_URL: "${(0,b.hideAuth)(s)}",\n WEBRTC_PERF_SESSION: ${this.id},\n WEBRTC_PERF_TAB_INDEX: ${t},\n WEBRTC_PERF_INDEX: ${e},\n STATS_INTERVAL: ${this.statsInterval},\n VIDEO_WIDTH: ${this.videoWidth},\n VIDEO_HEIGHT: ${this.videoHeight},\n VIDEO_FRAMERATE: ${this.videoFramerate},\n};\ntry {\n webrtcperf.params = JSON.parse('${JSON.stringify(this.scriptParams)}' || '{}');\n} catch (err) {\n console.error('[webrtcperf] Error parsing scriptParams:', err);\n webrtcperf.params = {};\n};\n `;return this.serverPort&&(a+=`webrtcperf.config.SAVE_MEDIA_URL = "ws${this.serverUseHttps?"s":""}://localhost:${this.serverPort}/?auth=${this.serverSecret}&action=write-stream";\n `,this.mediaPath?.mp4&&!this.useFakeMedia&&(a+=`webrtcperf.config.VIDEO_URL = "http${this.serverUseHttps?"s":""}://localhost:${this.serverPort}/cache/${p.default.basename(this.mediaPath.mp4)}?auth=${this.serverSecret}";\nwebrtcperf.config.AUDIO_URL = "http${this.serverUseHttps?"s":""}://localhost:${this.serverPort}/cache/${p.default.basename(this.mediaPath.m4a)}?auth=${this.serverSecret}";\n `)),this.disabledVideoCodecs.length&&($.debug("Using disabledVideoCodecs:",this.disabledVideoCodecs),a+=`webrtcperf.config.GET_CAPABILITIES_DISABLED_VIDEO_CODECS = JSON.parse('${JSON.stringify(this.disabledVideoCodecs)}');\n`),a}async openPage(e){if(!this.browser)return;const t=this.id+e;let s,a=this.url;if(!a){if(this.customUrlHandler&&!this.customUrlHandlerFn){const e=p.default.resolve(process.cwd(),this.customUrlHandler);if(!c.default.existsSync(e))throw new Error(`Custom url handler script not found: "${e}"`);this.customUrlHandlerFn=(await import(e)).default}if(!this.customUrlHandlerFn)throw new Error("Custom url handler function not set");a=await this.customUrlHandlerFn({id:this.id,sessions:this.sessions,tabIndex:e,tabsPerSession:this.tabsPerSession,index:t,pid:process.pid,env:{...process.env},params:this.scriptParams}),$.debug(`customUrlHandlerFn: ${a}`)}if(!a)throw new Error("Page URL not set");this.urlQuery&&(a+=`?${this.urlQuery.replace(/\$s/g,String(this.id)).replace(/\$S/g,String(this.sessions)).replace(/\$t/g,String(e)).replace(/\$T/g,String(this.tabsPerSession)).replace(/\$i/g,String(t)).replace(/\$p/g,String(process.pid))}`),$.debug(`opening page ${t} (session: ${this.id} tab: ${e}): ${(0,b.hideAuth)(a)}`),this.incognito?this.context=await this.browser.createBrowserContext():this.context=this.browser.defaultBrowserContext(),this.overridePermissions.length&&await this.context.overridePermissions(new URL(a).origin,this.overridePermissions);const i=await this.getNewPage(e),n=i._client();await i.setBypassCSP(!0),this.userAgent&&await i.setUserAgent(this.userAgent),await Promise.all(Object.keys(this.exposedFunctions).map((async e=>await i.exposeFunction(e,((...t)=>this.exposedFunctions[e](...t))))));let l=this.setupPageCmd(t,e,a);if(this.localStorage&&($.debug("Using localStorage:",this.localStorage),Object.entries(this.localStorage).map((([e,t])=>{l+=`window.localStorage.setItem('${e}', ${JSON.stringify(t)});\n`}))),this.sessionStorage&&($.debug("Using sessionStorage:",this.sessionStorage),Object.entries(this.sessionStorage).map((([e,t])=>{l+=`window.sessionStorage.setItem('${e}', ${JSON.stringify(t)});\n`}))),l+=`\nObject.defineProperty(window.screen, 'width', { value: ${this.windowWidth}, writable: false });\nObject.defineProperty(window.screen, 'height', { value: ${this.windowHeight}, writable: false });\nObject.defineProperty(window.screen, 'availWidth', { value: ${this.windowWidth}, writable: false });\nObject.defineProperty(window.screen, 'availHeight', { value: ${this.windowHeight}, writable: false });\nObject.defineProperty(window.screen.orientation, 'type', { value: 'landscape-primary', writable: false });\n `,$.debug("init command:",l),await i.evaluateOnNewDocument(l),this.clearCookies)try{await n.send("Network.clearBrowserCookies")}catch(e){$.error(`clearCookies error: ${e.stack}`)}i.on("dialog",(async e=>{$.debug(`page ${t+1} dialog ${e.type()}: ${e.message()}`);try{await e.accept()}catch(e){$.debug(`dialog accept error: ${e.message}`)}try{await e.dismiss()}catch(e){$.debug(`dialog dismiss error: ${e.message}`)}})),i.once("close",(()=>{$.debug(`page ${t+1} closed`),this.pages.delete(t),this.httpResourcesStats.delete(t),this.pagesMetrics.delete(t),s&&(s.close().catch((e=>{$.error(`saveFile close error: ${e.stack}`)})),s=void 0),this.browser&&this.running&&setTimeout((()=>this.openPage(t).catch((e=>$.error(`openPage after close error: ${e.stack}`)))),1e3)}));let u=!0;await n.send("Network.setBypassServiceWorker",{bypass:!0});const f=new g.RequestInterceptionManager(n,{onError:e=>{$.error("Request interception error:",e)}}),v=[];this.blockedUrls.forEach((e=>{v.push({urlPattern:e,modifyRequest:()=>({errorReason:"BlockedByClient"})})})),this.extraHeaders&&Object.entries(this.extraHeaders).forEach((([e,t])=>{const s=Object.entries(t).map((([e,t])=>({name:e,value:t})));v.push({urlPattern:e,modifyRequest:({event:e})=>($.debug(`adding extraHeaders in: ${e.request.url}`,s),{headers:s})})})),Object.entries(this.responseModifiers).forEach((([e,t])=>{v.push({urlPattern:e,modifyResponse:async({event:e,body:s})=>{const a=e.responseHeaders||[];for(const{search:r,replace:i,file:o,headers:n}of t)if(r&&i?($.debug(`using responseModifiers in: ${e.request.url}: ${r.toString()} => ${i}`),s=s?.replace(r,i)):o&&($.debug(`using responseModifiers in: ${e.request.url}: ${o}`),s=await c.default.promises.readFile(o,"utf8")),n)for(const[e,t]of Object.entries(n))a.push({name:e,value:t});return{body:s,responseHeaders:a}}})})),await f.intercept(...v),this.downloadResponses.length&&i.on("response",(async e=>{if(!e.ok())return;const t=e.url();for(const{urlPattern:s,output:a,append:r}of this.downloadResponses)if(s.test(t))try{const s=await e.buffer();if(s.byteLength>0)if(r){const e=a.replaceAll("${id}",this.id.toString());c.default.existsSync(p.default.dirname(e))||await c.default.promises.mkdir(p.default.dirname(a),{recursive:!0}),$.debug(`appending response body ${s.byteLength} to: ${e}`),await c.default.promises.appendFile(e,s)}else{c.default.existsSync(a)||await c.default.promises.mkdir(a,{recursive:!0});const e=p.default.join(a,`${p.default.basename(new URL(t).pathname)}`);$.debug(`saving response body ${s.byteLength} to: ${e}`),await c.default.promises.writeFile(e,s)}}catch(e){$.error(`downloadResponses error: ${e.stack}`)}}));if(await i.exposeFunction("setRequestInterception",(async e=>{if(e!==u){$.debug(`setRequestInterception to ${e}`);try{e?await f.enable():await f.disable(),u=e}catch(e){$.error(`setRequestInterception error: ${e.stack}`)}}})),await i.exposeFunction("jsonFetch",(async(e,t="",s=0)=>{if(t){const e=R.jsonFetchCache.get(t);if(e)return e}try{e.validStatuses&&(e.validateStatus=t=>e.validStatuses.includes(t));const{status:a,data:r,headers:i}=await(0,o.default)(e);if("stream"===e.responseType){if(e.downloadPath&&!c.default.existsSync(e.downloadPath)){$.debug(`jsonFetch saving file to: ${e.downloadPath}`,i["content-disposition"]),await c.default.promises.mkdir(p.default.dirname(e.downloadPath),{recursive:!0});const t=c.default.createWriteStream(e.downloadPath);await new Promise(((e,s)=>{t.on("error",(e=>s(e))),t.on("close",(()=>e())),r.pipe(t)}))}return t&&R.jsonFetchCache.set(t,{status:a},s),{status:a,headers:i}}return t&&R.jsonFetchCache.set(t,{status:a,data:r},s),{status:a,headers:i,data:r}}catch(e){const t=e.message;return $.warn(`jsonFetch error: ${t}`),{status:500,error:t}}})),await i.exposeFunction("readLocalFile",((e,t)=>(e=p.default.resolve(process.cwd(),e),c.default.promises.readFile(e,t)))),this.pageLogPath){const e=p.default.dirname(this.pageLogPath);await i.exposeFunction("webrtcperf_writeFile",((t,s,a=!1)=>{const r=p.default.resolve(e,t);return a?c.default.promises.appendFile(r,s):c.default.promises.writeFile(r,s)}))}await i.exposeFunction("webrtcperf_emulateCpuThrottling",(e=>i.emulateCPUThrottling(e))),this.emulateCpuThrottling&&($.debug(`emulateCpuThrottling: ${this.emulateCpuThrottling}`),await i.emulateCPUThrottling(this.emulateCpuThrottling)),await i.exposeFunction("keypressText",(async(e,t,s=20)=>{await i.type(e,t,{delay:s})})),await i.exposeFunction("mouseClick",(async(e,t=0,s=0)=>{await i.click(e,{offset:{x:t,y:s}})}));const w=new d.LoremIpsum({sentencesPerParagraph:{max:4,min:1},wordsPerSentence:{max:16,min:2}});if(await i.exposeFunction("loremIpsum",((e=1)=>w.generateSentences(e))),await i.exposeFunction("keypressRandomText",(async(e,t=1,s="",a="",r=0)=>{const o=s+w.generateSentences(t)+a,n=await i.frames();for(const t of n){const s=await t.$(e);s&&(await s.focus(),await t.type(e,o,{delay:r}))}})),await i.exposeFunction("uploadFileFromUrl",(async(e,t)=>{const s=(0,b.sha256)(e)+"."+e.split(".").slice(-1)[0],a=p.default.join(h.default.homedir(),".webrtcperf/uploads",s);c.default.existsSync(a)||await(0,b.downloadUrl)(e,void 0,a),$.debug(`uploadFileFromUrl: ${a}`);const r=await i.frames();for(const e of r){const s=await e.$(t);if(s){await s.uploadFile(a);break}}})),this.extraCSS){$.debug(`Add extraCSS: ${this.extraCSS}`);try{await i.evaluateOnNewDocument((e=>{document.addEventListener("DOMContentLoaded",(()=>{const t=document.createElement("style");t.setAttribute("id","webrtcperf-extra-style"),t.setAttribute("type","text/css"),t.innerHTML=e,document.head.appendChild(t)}))}),this.extraCSS.replace(/important/g,"!important"))}catch(e){$.error(`Add extraCSS error: ${e.stack}`)}}if(this.cookies)try{await i.setCookie(...this.cookies)}catch(e){$.error(`Set cookies error: ${e.stack}`)}if(this.pageLogPath)try{await c.default.promises.mkdir(p.default.dirname(this.pageLogPath),{recursive:!0}),s=await c.default.promises.open(this.pageLogPath,"a")}catch(e){$.error(`error opening page log file: ${this.pageLogPath}: ${e.stack}`)}if(await i.exposeFunction("webrtcperf_serializedConsoleLog",(async(e,a)=>{if(this.showPageLog||s)try{await this.onPageMessage(t,e,a,s)}catch(e){$.error(`serializedConsoleLog error: ${e.stack}`)}})),(this.showPageLog||s)&&(i.on("pageerror",(async e=>{const a=`pageerror: ${e?.message?.message||e?.message||e} - ${e?.message?.stack||e?.stack}`;await this.onPageMessage(t,"error",a,s)})),i.on("requestfailed",(async e=>{const a=(e.failure()?.errorText||"").trim();if("net::ERR_ABORTED"===a)return;const r=`${e.method()} ${e.url()}: ${a}`;await this.onPageMessage(t,"requestfailed",r,s)}))),await i.exposeFunction("webrtcperf_startFakeScreenshare",(async()=>{if(!this.browser)return;let s=i;this.useFakeMedia||this.screensharePage||(s=this.screensharePage=await this.browser.newPage(),await this.screensharePage.evaluateOnNewDocument(this.setupPageCmd(t,e,"about:blank")),await this.screensharePage.evaluateOnNewDocument(c.default.readFileSync((0,b.resolvePackagePath)("node_modules/@vpalmisano/webrtcperf-js/dist/webrtcperf.js"),"utf8")),await s.exposeFunction("webrtcperf_keypressText",(async(e,t,a=20)=>{await s.type(e,t,{delay:a})})),await s.exposeFunction("webrtcperf_keyPress",(async e=>{await s.keyboard.press(e)})),await s.goto(`http${this.serverUseHttps?"s":""}://localhost:${this.serverPort}/empty-page?auth=${this.serverSecret}&title=webrtcperf-screenshare`)),await s.evaluate((()=>webrtcperf.startFakeScreenshare()))})),await i.exposeFunction("webrtcperf_stopFakeScreenshare",(async()=>{!this.useFakeMedia&&this.screensharePage?(await this.screensharePage.close(),this.screensharePage=void 0):await i.evaluate((()=>webrtcperf.stopFakeScreenshare()))})),await i.exposeFunction("webrtcperf_reload",(()=>i.reload())),this.setupPageNetworkStats(n,t),this.hardwareConcurrency){const e=S({hardwareConcurrency:this.hardwareConcurrency});await e.onPageCreated(i)}this.throttleIndex>-1&&("linux"!==process.platform||this.useBrowserThrottling)&&($.debug("Using internal network throttling"),await n.send("Network.emulateNetworkConditions",{offline:!1,uploadThroughput:125e5,downloadThroughput:125e5,latency:0,packetLoss:0,packetQueueLength:0}));{const e=(0,b.resolvePackagePath)("node_modules/@vpalmisano/webrtcperf-js/dist/webrtcperf.js");if(!c.default.existsSync(e))throw new Error(`@vpalmisano/webrtcperf-js script not found: ${e}`);$.debug(`loading @vpalmisano/webrtcperf-js script from: ${e}`),await i.evaluateOnNewDocument(c.default.readFileSync(e,"utf8"))}if(this.scriptPath)if(this.scriptPath.startsWith("base64:gzip:")){const e=Buffer.from(this.scriptPath.replace("base64:gzip:",""),"base64"),t=(0,m.gunzipSync)(e).toString();$.debug(`loading script from ${t.length} bytes`),await i.evaluateOnNewDocument(t)}else for(const e of this.scriptPath.split(","))if(e.trim())if(e.startsWith("http")){$.debug(`loading custom script from url: ${e}`);const t=await(0,b.downloadUrl)(e);if(!t?.data)throw new Error(`Failed to download script from: ${e}`);await i.evaluateOnNewDocument(t.data)}else{if(!c.default.existsSync(e)){$.warn(`custom script not found: ${e}`);continue}$.debug(`loading custom script from file: ${e}`),await i.evaluateOnNewDocument(c.default.readFileSync(e,"utf8"))}$.debug(`Page ${t+1} "${a}" loading`);const y=Date.now();try{await i.goto(a,{waitUntil:"domcontentloaded",timeout:6e4})}catch(e){return $.error(`Page ${t+1} "${a}" load error: ${e.stack}`),void await i.close()}this.pages.set(t,i),this.throttleIndex>-1&&("linux"!==process.platform||this.useBrowserThrottling)&&(await this.applyNetworkThrottling(n),r.throttleNotifier.on("change",(async()=>{await this.applyNetworkThrottling(n)}))),$.debug(`Page ${t+1} "${a}" loaded in ${(Date.now()-y)/1e3}s`);for(let e=0;e<this.evaluateAfter.length;e++)await i.evaluate(this.evaluateAfter[e].pageFunction,...this.evaluateAfter[e].args)}setupPageNetworkStats(e,t){const s={sentBytes:0,recvBytes:0,recvLatency:new w.FastStats({store_data:!1}),wsSentBytes:0,wsRecvBytes:0,wsRecvLatency:new w.FastStats({store_data:!1})};this.httpResourcesStats.set(t,s);const a=new Map;e.on("Network.requestWillBeSent",(e=>{if(e.request.url.startsWith("data:"))return;const{requestId:t,request:r,timestamp:i}=e,o=r.postDataEntries?.reduce(((e,t)=>e+(t.bytes?.length||0)),0);o&&(s.sentBytes+=o),a.set(t,{url:r.url,timestamp:i})})),e.on("Network.responseReceived",(e=>{if(!a.get(e.requestId))return;const{response:t}=e;t.fromDiskCache?a.delete(e.requestId):s.recvBytes+=t.encodedDataLength})),e.on("Network.dataReceived",(e=>{a.get(e.requestId)&&(s.recvBytes+=e.encodedDataLength)})),e.on("Network.loadingFinished",(e=>{const t=a.get(e.requestId);if(!t)return;a.delete(e.requestId);const{timestamp:r}=e;s.recvLatency.push(r-t.timestamp)})),e.on("Network.webSocketCreated",(e=>{a.set(e.requestId,{url:e.url,timestamp:Date.now()})})),e.on("Network.webSocketHandshakeResponseReceived",(e=>{const t=a.get(e.requestId);t&&(a.delete(e.requestId),s.wsRecvLatency.push((Date.now()-t.timestamp)/1e3))})),e.on("Network.webSocketFrameSent",(e=>{s.wsSentBytes+=e.response.payloadData.length})),e.on("Network.webSocketFrameReceived",(e=>{s.wsRecvBytes+=e.response.payloadData.length}))}async applyNetworkThrottling(e){const t=(0,r.getSessionThrottleValues)(this.throttleIndex,"up"),s=(0,r.getSessionThrottleValues)(this.throttleIndex,"down"),a={offline:!1,uploadThroughput:t.rate||-1,downloadThroughput:s.rate||-1,latency:Math.max(t.delay||0,s.delay||0),packetLoss:Math.max(t.loss||0,s.loss||0),packetQueueLength:Math.max(t.queue||0,s.queue||0)};$.debug(`Apply internal network throttling: ${JSON.stringify(a)}`),await e.send("Network.emulateNetworkConditions",{...a,uploadThroughput:-1!==a.uploadThroughput?a.uploadThroughput/8:-1,downloadThroughput:-1!==a.downloadThroughput?a.downloadThroughput/8:-1})}async getNewPage(e){return $.debug(`getNewPage ${e}`),(0,i.default)(this.context,"NoBrowserContextCreated"),await this.context.newPage()}async onPageMessage(e,t,s,a){if(s.endsWith("net::ERR_BLOCKED_BY_CLIENT.Inspector"))return;if(this.blockedUrls.some((e=>("requestfailed"===t||-1!==s.search("FetchError"))&&-1!==s.search(e))))return;const r=P[t]||"grey",i=this.pageLogFilter?new RegExp(this.pageLogFilter,"ig"):null;if(!i||s.match(i)){const i=["error","warning"].includes(t),o=s.startsWith("[webrtcperf");a&&(!i&&!o&&s.length>1024&&(s=s.slice(0,1024)+`... +${s.length-1024} bytes`),await a.write(`${(new Date).toISOString()} [page ${e}] (${t}) ${s}\n`)),this.showPageLog&&(!i&&!o&&s.length>256&&(s=s.slice(0,256)+`... +${s.length-256} bytes`),console.log(y`{bold [page ${e}]} {${r} (${t}) ${s}}`)),"error"===t?this.pageErrors+=1:"warn"===t&&(this.pageWarnings+=1)}}async updateStats(){if(!this.browser)return this.stats={},this.stats;const e={};try{const t=await(0,b.getProcessStats)();Object.assign(e,{nodeCpu:t.cpu,nodeMemory:t.memory})}catch(e){$.error(`node getProcessStats error: ${e.stack}`)}try{const t=(0,b.getSystemStats)();t&&(e.usedCpu=t.usedCpu,e.usedMemory=t.usedMemory,e.usedGpu=t.usedGpu,e.usedCpu>80&&$.warn(`High system CPU usage: ${e.usedCpu.toFixed(2)}%`),e.usedMemory>80&&$.warn(`High system memory usage: ${e.usedMemory.toFixed(2)}%`))}catch(e){$.error(`node getSystemStats error: ${e.stack}`)}const t=this.browser.process();if(t)try{const s=await(0,b.getProcessStats)(t.pid,!0);Object.assign(e,s)}catch(e){$.error(`getProcessStats error: ${e.stack}`)}const s={},a={},i={},o={},n={},c={},l={},d={},u={},h={},p={},f={},g={},m={},w={},y={},S={},P={},R={},T={},k={},E={},x={},C={},_={},A={},F={},I={},D={},L={},O={},M={},B={},U={},N={},j={},H={},q={},W={},V={};return await Promise.allSettled([...this.pages.entries()].map((async([t,G])=>{try{const{peerConnectionStats:z,audioEndToEndDelay:J,videoEndToEndDelay:K,cpuPressure:Q,videoStats:Y,customMetrics:X}=await G.evaluate((async()=>({peerConnectionStats:await webrtcperf.collectPeerConnectionStats(),audioEndToEndDelay:webrtcperf.collectAudioEndToEndStats(),videoEndToEndDelay:webrtcperf.collectVideoEndToEndStats(),cpuPressure:webrtcperf.collectCpuPressure(),videoStats:webrtcperf.collectVideoStats(),customMetrics:"collectCustomMetrics"in window?collectCustomMetrics():null}))),{participantName:Z}=z,ee=this.httpResourcesStats.get(t);if(!z.signalingHost&&z.stats.length){const e=Object.values(z.stats[0]);e.length&&(z.signalingHost=await(0,b.resolveIP)(e[0].remoteAddress))}const{stats:te,activePeerConnections:se,signalingHost:ae}=z,re=(0,v.rtcStatKey)({hostName:ae,participantName:Z}),ie=(0,v.rtcStatKey)({pageIndex:t,hostName:ae,participantName:Z});(0,b.increaseKey)(s,re,1),(0,b.increaseKey)(a,ie,se),(0,b.increaseKey)(i,ie,z.peerConnectionConnectionTime),(0,b.increaseKey)(o,ie,z.peerConnectionDisconnectionTime),(0,b.increaseKey)(n,ie,z.peerConnectionsCreated),(0,b.increaseKey)(c,ie,z.peerConnectionsClosed),(0,b.increaseKey)(l,ie,z.peerConnectionsConnected),(0,b.increaseKey)(d,ie,z.peerConnectionsDisconnected),(0,b.increaseKey)(u,ie,z.peerConnectionsFailed),(0,b.increaseKey)(h,ie,z.peerConnectionsDelay),J&&(p[ie]=J.delay,f[ie]=J.startFrameDelay),K&&(g[ie]=K.videoDelay,w[ie]=K.videoStartFrameDelay,m[ie]=K.screenDelay,y[ie]=K.screenStartFrameDelay),ee&&(ee.sentBytes>0&&(S[ie]=ee.sentBytes),ee.recvBytes>0&&(P[ie]=ee.recvBytes),ee.recvLatency.length&&(R[ie]=ee.recvLatency.amean()),ee.wsSentBytes>0&&(T[ie]=ee.wsSentBytes),ee.wsRecvBytes>0&&(k[ie]=ee.wsRecvBytes),ee.wsRecvLatency.length&&(E[ie]=ee.wsRecvLatency.amean())),void 0!==Q&&(_[ie]=Q),Y&&(A[ie]=Y.width,F[ie]=Y.height,I[ie]=Y.bufferedTime,D[ie]=Y.playingTime,L[ie]=Y.bufferingTime,O[ie]=Y.bufferingEvents);for(const s of te)for(const[a,r]of Object.entries(s))try{(0,v.updateRtcStats)(e,t,a,r,ae,Z)}catch(e){$.error(`updateRtcStats error for ${a}: ${e.stack}`,e)}if(X)for(const[e,t]of Object.entries(X))V[e]||(V[e]={}),V[e][ie]=t;x[ie]=e.cpu/this.tabsPerSession,C[ie]=e.memory/this.tabsPerSession;const oe=(0,r.getSessionThrottleValues)(this.throttleIndex,"up");M[ie]=oe.rate||0,B[ie]=oe.delay||0,U[ie]=oe.loss||0,N[ie]=oe.queue||0;const ne=(0,r.getSessionThrottleValues)(this.throttleIndex,"down");j[ie]=ne.rate||0,H[ie]=ne.delay||0,q[ie]=ne.loss||0,W[ie]=ne.queue||0}catch(e){const s=e;s.message.includes("Execution context was destroyed, most likely because of a navigation.")?$.warn(`collectPeerConnectionStats for page ${t} error: ${s.message}`):$.error(`collectPeerConnectionStats for page ${t} error: ${s.stack}`)}}))),Object.assign(e,{pages:s,errors:this.pageErrors,warnings:this.pageWarnings,peerConnections:a,peerConnectionConnectionTime:i,peerConnectionDisconnectionTime:o,peerConnectionsConnected:l,peerConnectionsCreated:n,peerConnectionsClosed:c,peerConnectionsDisconnected:d,peerConnectionsFailed:u,peerConnectionsDelay:h,audioEndToEndDelay:p,audioStartFrameDelay:f,videoEndToEndDelay:g,videoStartFrameDelay:w,screenEndToEndDelay:m,screenStartFrameDelay:y,httpSentBytes:S,httpRecvBytes:P,httpRecvLatency:R,wsSentBytes:T,wsRecvBytes:k,wsRecvLatency:E,cpuPressure:_,videoWidth:A,videoHeight:F,videoBufferedTime:I,videoPlayingTime:D,videoBufferingTime:L,videoBufferingEvents:O,pageCpu:x,pageMemory:C,throttleUpRate:M,throttleUpDelay:B,throttleUpLoss:U,throttleUpQueue:N,throttleDownRate:j,throttleDownDelay:H,throttleDownLoss:q,throttleDownQueue:W,...V}),s.size<this.pages.size&&$.warn(`updateStats collected pages ${s.size} < ${this.pages.size}`),this.stats=e,this.stats}async stop(){if(this.running){if(this.running=!1,$.debug(`${this.id} stop`),this.stopPortForwarder&&this.stopPortForwarder(),this.browser){if($.debug(`${this.id} closing ${this.pages.size} pages`),await Promise.allSettled([...this.pages.values()].map((e=>e.close({runBeforeUnload:!0})))),this.pages.size>0){const e=Date.now(),t=1e3*this.pages.size;for(;this.pages.size>0&&Date.now()-e<t;)$.debug(`${this.id} waiting for ${this.pages.size} pages to close`),await(0,b.sleep)(200);this.pages.size>0&&$.warn(`${this.id} timeout closing ${this.pages.size} pages`)}if(this.screensharePage&&(await this.screensharePage.close(),this.screensharePage=void 0),this.browser.removeAllListeners(),this.chromiumUrl){$.debug(`${this.id} disconnect from browser`);try{await this.browser.disconnect()}catch(e){$.warn(`${this.id} browser disconnect error: ${e.message}`)}}else{const e=this.browser.process()?.pid;if(e){$.debug(`${this.id} closing browser (pid: ${e})`);try{await this.browser.close()}catch(e){$.error(`${this.id} browser close error: ${e.stack}`)}await(0,b.waitStopProcess)(e,5e3)}}this.pages.clear(),this.pagesMetrics.clear(),this.browser=void 0}this.emit("stop",this.id)}}async pageScreenshot(e=0,t="webp"){$.debug(`pageScreenshot ${this.id}-${e}`);const s=this.id+e,a=this.pages.get(s);if(!a)throw new Error(`Page ${s} not found`);const r=`/tmp/screenshot-${s}.${t}`;return await a.screenshot({path:r,fullPage:!0}),r}}t.Session=R},6982:e=>{e.exports=require("crypto")},6997:function(e,t,s){var a,r=this&&this.__createBinding||(Object.create?function(e,t,s,a){void 0===a&&(a=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,a,r)}:function(e,t,s,a){void 0===a&&(a=s),e[a]=t[s]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||(a=function(e){return a=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},a(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s=a(e),o=0;o<s.length;o++)"default"!==s[o]&&r(t,e,s[o]);return i(t,e),t}),n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Server=void 0;const c=n(s(7174)),l=s(6982),d=o(s(7252)),u=n(s(9896)),h=s(8611),p=s(5692),f=n(s(857)),g=n(s(6928)),m=n(s(2365)),v=s(5086),w=n(s(3106)),b=n(s(699)),y=s(7028),S=s(6976),$=s(6185),P=s(7191),R=s(2680),T=(0,$.logger)("webrtcperf:server");t.Server=class{serverPort;serverSecret;serverUseHttps;serverData;pageLogPath;videoCachePath;stats;app;server=null;wss=null;constructor({serverPort:e=5e3,serverSecret:t="secret",serverUseHttps:s=!1,serverData:a="",pageLogPath:r="",videoCachePath:i=""}={},o){this.serverPort=e,this.serverSecret=t,this.serverUseHttps=s,this.serverData=a,this.pageLogPath=r,this.videoCachePath=i,this.stats=o,this.app=(0,d.default)(),this.app.use((0,c.default)()),this.app.use((0,d.json)({limit:"10mb"})),this.app.use(((e,t,s)=>{if(e.query.auth===this.serverSecret)return s();const a=(0,b.default)(e);if(!a||"admin"!==a.name||a.pass!==this.serverSecret)return t.setHeader("WWW-Authenticate",'Basic realm="Restricted Area"'),void t.status(401).send("Unauthorized");s()})),this.app.get("/",((e,t)=>{t.send("")})),this.app.get("/stats",this.getStats.bind(this)),this.app.get("/collected-stats",this.getCollectedStats.bind(this)),this.app.get("/screenshot/:sessionId",this.getScreenshot.bind(this)),this.app.put("/collected-stats",this.putCollectedStats.bind(this)),this.app.put("/session",this.putSession.bind(this)),this.app.put("/sessions",this.putSessions.bind(this)),this.app.delete("/session",this.deleteSession.bind(this)),this.app.delete("/sessions",this.deleteSessions.bind(this)),this.app.get("/view/page.log",this.getPageLog.bind(this)),this.app.get("/view/docker.log",this.getDockerLog.bind(this)),this.app.get("/download/alert-rules",this.getAlertRules.bind(this)),this.app.get("/download/stats",this.getStatsFile.bind(this)),this.app.get("/download/detailed-stats",this.getDetailedStatsFile.bind(this)),this.app.get("/empty-page",this.getEmptyPage.bind(this)),this.serverData&&(T.debug(`using serverData: ${this.serverData}`),u.default.promises.mkdir(this.serverData,{recursive:!0}).catch((e=>{T.error(`mkdir ${this.serverData} error: ${e.message}`)})),this.app.get("/data",this.getDataArchive.bind(this)),this.app.get("/data/:path",this.getData.bind(this))),this.videoCachePath&&(T.debug(`using videoCachePath: ${this.videoCachePath}`),u.default.promises.mkdir(this.videoCachePath,{recursive:!0}).catch((e=>{T.error(`mkdir ${this.videoCachePath} error: ${e.message}`)})),this.app.get("/cache/:path",this.getCache.bind(this))),this.app.use(((e,t,s,a)=>{if(T.error(`request path=${t.path} error:`,e.stack),s.headersSent)return a(e);s.status(500).send(e.message)}))}async getStats(e,t,s){T.debug("GET /stats");const a=[];try{for(const e of this.stats.sessions.values())a.push(e.stats);t.json(a)}catch(e){s(e)}}getStatsFile(e,t,s){if(T.debug("/download/stats",e.query),!this.stats.statsWriter)return s(new Error("statsPath not set"));t.download(this.stats.statsPath)}getDetailedStatsFile(e,t,s){if(T.debug("/download/detailed-stats",e.query),!this.stats.detailedStatsWriter)return s(new Error("detailedStatsPath not set"));t.download(this.stats.detailedStatsPath)}getCollectedStats(e,t,s){T.debug("GET /collected-stats");const a={};try{for(const[e,t]of Object.entries(this.stats.collectedStats))a[e]=t.data;t.json(a)}catch(e){s(e)}}async getScreenshot(e,t,s){const a=parseInt(e.params.sessionId),r=parseInt(e.query.page||"0"),i=e.query.format||"webp";T.debug(`GET /screenshot/${a} page=${r} format=${i}`);try{const e=this.stats.sessions.get(a);if(!e)throw new Error(`Session not found: "${a}"`);const s=await e.pageScreenshot(r,i);t.sendFile(g.default.resolve(s))}catch(e){s(e)}}putCollectedStats(e,t,s){T.debug("PUT /collected-stats");const{id:a,stats:r,config:i}=e.body;try{this.stats.addExternalCollectedStats(a,r,i),t.json({message:"Collected stats added"})}catch(e){s(e)}}async putSession(e,t,s){T.debug("PUT /session",e.body);try{const s=e.body,a=this.stats.consumeSessionId(s.tabsPerSession);await this.startLocalSession(a,e.body),t.json({message:"Session created",data:{id:a}})}catch(e){s(e)}}async putSessions(e,t,s){T.debug("PUT /sessions",e.body);try{const{sessions:s,tabsPerSession:a}=e.body,r=[];for(let t=0;t<s;t++){const t=this.stats.consumeSessionId(a);await this.startLocalSession(t,e.body),r.push(t)}t.json({message:`${s} sessions created`,data:{ids:r}})}catch(e){s(e)}}async deleteSession(e,t,s){T.debug("DELETE /session",e.body);try{const{id:s}=e.body;await this.stopLocalSession(s),t.json({message:"Session deleted",data:{id:s}})}catch(e){s(e)}}async deleteSessions(e,t,s){T.debug("DELETE /sessions",e.body);try{const{ids:s}=e.body;for(const e of s)await this.stopLocalSession(e);t.json({message:`${s.length} sessions deleted`,data:{ids:s}})}catch(e){s(e)}}getPageLog(e,t,s){if(T.debug("GET /view/page.log",e.query),!this.pageLogPath)return s(new Error("pageLogPath not set"));e.query.range&&!e.headers.range&&(e.headers.range=`bytes=${e.query.range}`),t.sendFile(g.default.resolve(this.pageLogPath))}async getDockerLog(e,t,s){T.debug("GET /view/docker.log",e.query);try{const s=await(0,$.getDockerLogsPath)();e.query.range&&!e.headers.range&&(e.headers.range=`bytes=${e.query.range}`),t.sendFile(g.default.resolve(s))}catch(e){s(e)}}getAlertRules(e,t,s){if(T.debug("GET /download/alert-rules",e.query),!this.stats.alertRulesOutput)return s(new Error("Stats alertRulesOutput not set"));t.download(this.stats.alertRulesOutput)}getEmptyPage(e,t){T.debug("GET /empty-page",e.query);const s=e.query.title||"EmptyPage";t.send(`<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1">\n<title>${s}</title>\n</head>\n<body></body>\n</html>`)}getData(e,t,s){const a=g.default.normalize(e.params.path).replace(/^(\.\.(\/|\\|$))+/,"");T.debug(`GET /data/${a}`,e.query);const r=g.default.resolve(this.serverData,a);if(!u.default.existsSync(r))return s(new Error(`${a} not found`));e.query.range&&!e.headers.range&&(e.headers.range=`bytes=${e.query.range}`),t.sendFile(r)}getDataArchive(e,t,s){T.debug("GET /data",e.query);const a=g.default.resolve(this.serverData);if(!u.default.lstatSync(a).isDirectory())return s(new Error(`${a} is not a directory`));t.header("Content-Disposition",`attachment; filename="${g.default.basename(a)}.tar.gz"`),t.setHeader("content-type","application/gzip"),m.default.pack(a).pipe(w.default.createGzip()).pipe(t)}getCache(e,t,s){const a=g.default.normalize(e.params.path).replace(/^(\.\.(\/|\\|$))+/,"");T.debug(`GET /cache/${a}`,e.query);const r=g.default.resolve(this.videoCachePath,a);if(!u.default.existsSync(r))return s(new Error(`${a} not found`));e.query.range&&!e.headers.range&&(e.headers.range=`bytes=${e.query.range}`),t.sendFile(r)}async startLocalSession(e,t){const s=(await(0,y.loadConfig)(void 0,t))[0],a=(0,P.getSessionThrottleIndex)(e),r=1e3/s.spawnRate,i=[];if(s.videoPath)for(const e of s.videoPath.split(",")){const t=await(0,R.prepareFakeMedia)({...s,videoPath:e});i.push(t)}const o=i.length?i[e%i.length]:void 0,n=new S.Session({...s,throttleIndex:a,spawnPeriod:r,mediaPath:o,id:e});n.once("stop",(()=>{console.warn(`Session ${e} stopped, reloading...`),setTimeout(this.startLocalSession.bind(this),r,e,t)})),this.stats.addSession(n);try{await n.start()}catch(e){throw this.stats.removeSession(n.id),e}return n}async stopLocalSession(e){const t=this.stats.sessions.get(e);t?(t.removeAllListeners(),this.stats.removeSession(e),await t.stop()):T.warn(`stopLocalSession session ${e} not found`)}async start(){if(T.debug("start"),this.serverUseHttps){const e=g.default.join(f.default.homedir(),".webrtcperf/ssl"),t=g.default.join(e,"domain.key"),s=g.default.join(e,"domain.crt");u.default.existsSync(t)&&u.default.existsSync(s)||await(0,$.runShellCommand)(`mkdir -p ${e} && openssl req -newkey rsa:2048 -nodes -keyout ${t} -x509 -days 365 -out ${s} -subj "/C=EU/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com"`),this.server=(0,p.createServer)({key:u.default.readFileSync(t),cert:u.default.readFileSync(s)},this.app)}else this.server=(0,h.createServer)(this.app);const e=new v.WebSocketServer({noServer:!0});e.on("connection",((e,t)=>{try{const s=new URLSearchParams(t.url?.split("?")[1]||""),a=s.get("action")||"";switch(T.debug(`ws connection from ${t.socket.remoteAddress} action: ${a}`),a){case"write-stream":{if(!this.serverData)throw new Error("serverData option not set");const t=s.get("filename")||"";if(!t)throw new Error("filename not set");const a=g.default.normalize(t).replace(/^(\.\.(\/|\\|$))+/,"");T.debug(`ws write-stream ${a}`);const r=g.default.resolve(this.serverData,a);if(u.default.existsSync(r))throw new Error(`file already exists: ${r}`);const i=u.default.createWriteStream(r);let o=!1,n=0;const c=async()=>{i.close(),e.close();try{n||await u.default.promises.unlink(r)}catch(e){T.error(`ws write-stream close error: ${e.message}`)}};i.on("error",(e=>{T.error(`ws write-stream error: ${e.message}`),c()})),e.on("error",(e=>{T.error(`ws write-stream error: ${e.message}`),c()})),e.on("close",(()=>{T.debug("ws write-stream close"),c()})),e.on("message",(e=>{if(e?.byteLength){if(!o)return i.write(e),void(o=!0);i.write(e),n++}}));break}default:throw new Error(`invalid action: ${a}`)}}catch(t){T.error(`ws connection error: ${t.message}`),e.close()}})),this.wss=e,this.server.on("upgrade",((t,s,a)=>{T.debug(`ws upgrade ${t.url}`);try{const e=new URLSearchParams(t.url?.split("?")[1]||"").get("auth");if(!e||!(0,l.timingSafeEqual)(Buffer.from(e),Buffer.from(this.serverSecret)))throw new Error("invalid auth")}catch(e){return T.error(`ws upgrade error: ${e.message}`),s.write("HTTP/1.1 401 Unauthorized\r\n\r\n"),void s.destroy()}e.handleUpgrade(t,s,a,(s=>{e.emit("connection",s,t)}))})),this.server.listen(this.serverPort,(()=>{T.debug(`HTTPS server listening on port ${this.serverPort}`)}))}stop(){this.wss&&(this.wss.close(),this.wss=null),this.server&&(T.debug("stop"),this.server.close(),this.server=null)}}},7028:function(e,t,s){var a,r=this&&this.__createBinding||(Object.create?function(e,t,s,a){void 0===a&&(a=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,a,r)}:function(e,t,s,a){void 0===a&&(a=s),e[a]=t[s]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||(a=function(e){return a=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},a(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s=a(e),o=0;o<s.length;o++)"default"!==s[o]&&r(t,e,s[o]);return i(t,e),t}),n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getConfigDocs=function(){return P({},null,(0,c.default)($).getSchema())},t.loadConfig=async function(e,t){const s=[];if(e)if(e.startsWith("http")){b.debug(`Loading config from url: ${e}`);const s=await(0,w.downloadUrl)(e);if(!s?.data)throw new Error(`Failed to download configuration from: ${e}`);t="application/x-yaml"===s.contentType?f.default.parse(s.data):"application/toml"===s.contentType?g.default.parse(s.data):p.default.parse(s.data)}else{if(!(0,d.existsSync)(e))throw new Error(`Config file not found: ${e}`);if(b.debug(`Loading config from local file: ${e}`),e.endsWith(".js")||e.endsWith(".mjs")){const s=await import(h.default.resolve(e));t=await s.default()}else{const s=String(await m.default.promises.readFile(e));t=e.endsWith(".yml")||e.endsWith(".yaml")?f.default.parse(s):e.endsWith(".toml")?g.default.parse(s):p.default.parse(s)}}Array.isArray(t)||(t=[t||{}]);for(const e of t){const t=(0,c.default)($);t.load(e||{}),t.validate({allowed:"strict"}),s.push(t.getProperties())}return b.debug("Using config:",s),s},t.loadConfigFromPrompt=async function(e){if(b.debug(`loadConfigFromPrompt: "${e}"`),!process.env.GEMINI_API_KEY)throw new Error("GEMINI_API_KEY environment variable is not set. Please set it to use the Google GenAI API.");const t=new T({apiKey:process.env.GEMINI_API_KEY}),s=await t.models.generateContent({model:"gemini-2.5-flash",contents:e,config:{tools:[{functionDeclarations:[R()]}],thinkingConfig:{thinkingBudget:0}}});if(s.functionCalls&&s.functionCalls.length>0){const e=s.functionCalls[0];return b.debug("Using function call:",e.name,e.args),e.args}throw new Error("No function call found in the response. Please check the prompt and try again.")};const c=o(s(4950)),l=s(7618),d=s(9896),u=n(s(857)),h=o(s(6928)),p=n(s(5865)),f=n(s(2115)),g=n(s(4908)),m=n(s(9896)),v=s(8034),w=s(6185),b=(0,w.logger)("webrtcperf:config"),y={name:"float",coerce:e=>parseFloat(e),validate:e=>{if(!Number.isFinite(e))throw new Error(`Invalid float: ${e}`)}},S={name:"index",coerce:e=>e,validate:e=>{if("string"!=typeof e){if("number"!=typeof e&&"boolean"!=typeof e)throw new Error(`Invalid index: "${e}" (type: ${typeof e})`)}else{if("true"===e||"false"===e||""===e)return;if(e.includes("-"))return void e.split("-").forEach((e=>{if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid string index: ${e}`)}));if(e.includes(","))return void e.split(",").forEach((e=>{if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid string index: ${e}`)}));if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid string index: ${e}`)}}};(0,c.addFormats)({ipaddress:l.ipaddress,url:l.url,float:y,index:S}),c.default.addParser([{extension:"json",parse:p.default.parse},{extension:["yml","yaml"],parse:f.default.parse},{extension:"toml",parse:g.default.parse}]);const $={url:{doc:"The page url to load.",format:String,default:"",nullable:!0,env:"URL",arg:"url"},urlQuery:{doc:"The query string to append to the page url; the following template variables are replaced: `$p` the process pid, `$s` the session index, `$S` the total sessions, `$t` the tab index, `$T` the total tabs per session, `$i` the tab absolute index.",format:String,default:"",nullable:!0,env:"URL_QUERY",arg:"url-query"},customUrlHandler:{doc:"This argument specifies the file path for the custom page URL handler that will be exported by default. The custom page URL handler allows you to define custom URLs that can be used to open your application. The handler function will be called with the following variables: - sessions: the total number of sessions; - tabsPerSession: the total number of tabs per session; - id: the session global index (0-indexed); - index: the tab global index (0-indexed); - tabIndex: the tab index in the current session (0-indexed); - pid: the process pid; - env: the environment variables object; - params: the script parameters object. You can use these variables to create custom URL schemes that suit your application's needs.",format:String,default:"",nullable:!0,env:"CUSTOM_URL_HANDLER",arg:"custom-url-handler"},videoPath:{doc:"The fake video path; if set, the video will be used as fake media source. It accepts a single path or a comma-separated list of videos paths that will be used in round-robin by the started sessions. The docker pre-built image contains a 2 minutes video sequence stored at `/app/video.mp4`. It accepts a local file, an http endpoint or a string starting with\n`generate:` (example: `generate:null` will generate a black video with silent audio). The temporary files containing the raw video and audio will be stored at `${VIDEO_CACHE_PATH}/video.${VIDEO_FORMAT}` and `${VIDEO_CACHE_PATH}/audio.wav`.",format:String,default:"https://github.com/vpalmisano/webrtcperf/releases/download/videos-1.0/kt.mp4",env:"VIDEO_PATH",arg:"video-path"},videoWidth:{doc:"The fake video resize width.",format:"nat",default:1280,env:"VIDEO_WIDTH",arg:"video-width"},videoHeight:{doc:"The fake video resize height.",format:"nat",default:720,env:"VIDEO_HEIGHT",arg:"video-height"},videoFramerate:{doc:"The fake video framerate.",format:"nat",default:25,env:"VIDEO_FRAMERATE",arg:"video-framerate"},videoSeek:{doc:"The fake audio/video seek position in seconds.",format:"nat",default:0,env:"VIDEO_SEEK",arg:"video-seek"},videoDuration:{doc:"The fake audio/video duration in seconds.",format:"nat",default:120,env:"VIDEO_DURATION",arg:"video-duration"},videoCacheRaw:{doc:"If the temporary video and audio raw files can be reused across multiple runs.",format:"Boolean",default:!0,env:"VIDEO_CACHE_RAW",arg:"video-cache-raw"},videoCachePath:{doc:"The path where the video and audio raw files are stored.",format:String,default:(0,h.join)(u.default.homedir(),".webrtcperf/cache"),env:"VIDEO_CACHE_PATH",arg:"video-cache-path"},videoFormat:{doc:"The fake video file format presented to the browser.",format:["y4m","mjpeg"],default:"y4m",env:"VIDEO_FORMAT",arg:"video-format"},useFakeMedia:{doc:"If true, the audio/video/screenshare will be generated using the browser fake device.\nOtherwise, the audio and video streams will be captured from a video element attached to the page, \nwhile the screenshare will be captured from a new browser tab.",format:"Boolean",default:!0,env:"USE_FAKE_MEDIA",arg:"use-fake-media"},runDuration:{doc:"If greater than 0, the test will stop after the provided number of seconds.",format:"nat",default:0,env:"RUN_DURATION",arg:"run-duration"},throttleConfig:{doc:'A JSON5 string with a valid throttler configuration (https://github.com/vpalmisano/throttler). Example: \n ```javascript\n [{\n sessions: \'0-1\',\n device: \'eth0\',\n protocol: \'udp\',\n skipSourcePorts: "443",\n skipDestinationPorts: "443",\n filter: "--sports 443 --dports 443",\n match: \'nbyte("ababa" at 12 layer 1)\',\n capture: \'capture.pcap\',\n up: {\n rate: 1000,\n delay: 50,\n loss: 5,\n queue: 10,\n },\n down: [\n { rate: 2000, delay: 50, delayJitter: 10, delayJitterCorrelation: 25, loss: 2, lossBurst: 2, queue: 20 },\n { rate: 1000, delay: 50, loss: 2, queue: 20, at: 60 },\n ]\n }]\n ```\n- The sessions field represents the sessions IDs range that will be affected by the rule, e.g.: "0-10", "2,4" or simply "2".\n- 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\'), only the packets with the specified protocol will be affected by the shaping rules.\n- The capture field is optional and specifies the pcap file to save the captured packets.\n- With skipSourcePorts and skipDestinationPorts you can specify a comma-separated list of ports that will not be affected by the shaping rules.\n- The filter field is optional and specifies the additional IPTables filter to apply for filtering the packets.\n- 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).\n- 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:\n - rate: the shaping rate in Kbps;\n - delay: the shaping delay in milliseconds;\n - delayJitter: the shaping delay jitter in milliseconds;\n - delayJitterCorrelation: the shaping delay jitter correlation in milliseconds;\n - loss: the packet loss percentage;\n - lossBurst: the packet loss burst percentage;\n - queue: the shaping queue size in packets;\n - at: the time in seconds when the shaping rule will be applied (default: 0).\nThe up and down rules can be specified as a single object or an array of objects.\nWhen 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. ',format:String,nullable:!0,default:"",env:"THROTTLE_CONFIG",arg:"throttle-config"},useBrowserThrottling:{doc:"If true, the network will be throttled using the browser internal throttling mechanism.",format:"Boolean",default:!1,env:"USE_BROWSER_THROTTLING",arg:"use-browser-throttling"},randomAudioPeriod:{doc:"If not zero, it specifies the maximum period in seconds after which a new random active session is selected, enabling the getUserMedia audio tracks in that session and disabling all of the others.",format:"nat",default:0,env:"RANDOM_AUDIO_PERIOD",arg:"random-audio-period"},randomAudioProbability:{doc:"When using random audio period, it defines the probability % that the selected audio will be activated (value: 0-100).",format:"nat",default:100,env:"RANDOM_AUDIO_PROBABILITY",arg:"random-audio-probability"},randomAudioRange:{doc:"When using random audio period, it defines the session indexes to be included into the random selection (default: include all the sessions).",format:"index",default:"true",nullable:!0,env:"RANDOM_AUDIO_RANGE",arg:"random-audio-range"},chromiumPath:{doc:"The Chromium executable path.",format:String,nullable:!0,default:"",env:"CHROMIUM_PATH",arg:"chromium-path"},chromiumVersion:{doc:"The Chromium version. It will be downloaded if the chromium path is not provided.",format:String,nullable:!1,default:v.PUPPETEER_REVISIONS.chrome,env:"CHROMIUM_VERSION",arg:"chromium-version"},chromiumUrl:{doc:"The remote Chromium URL (`http://HOST:PORT`).\nIf provided, the remote instance will be used instead of running a local\nchromium process.",format:String,default:"",nullable:!0,env:"CHROMIUM_URL",arg:"chromium-url"},chromiumFieldTrials:{doc:"Chromium additional field trials.",format:String,nullable:!0,default:"",env:"CHROMIUM_FIELD_TRIALS",arg:"chromium-field-trials"},windowWidth:{doc:"The browser window width.",format:"nat",default:1920,env:"WINDOW_WIDTH",arg:"window-width"},windowHeight:{doc:"The browser window height.",format:"nat",default:1080,env:"WINDOW_HEIGHT",arg:"window-height"},deviceScaleFactor:{doc:"The browser device scale factor.",format:"float",default:1,env:"DEVICE_SCALE_FACTOR",arg:"device-scale-factor"},maxVideoDecoders:{doc:"Specifies the maximum number of concurrent WebRTC video decoder instances that can be created on the same host.\nIf set it will disable the received video resolution and jitter buffer stats. This option is supported only when using the custom chromium build. The total decoders count is stored into the virtual file `/dev/shm/chromium-video-decoders`",format:Number,default:-1,env:"MAX_VIDEO_DECODERS",arg:"max-video-decoders"},maxVideoDecodersRange:{doc:"It applies the max video decoders option to the sessions included into this list (default: include all the sessions)",format:"index",default:"true",nullable:!0,env:"MAX_VIDEO_DECODERS_RANGE",arg:"max-video-decoders-range"},incognito:{doc:"Runs the browser in incognito mode.",format:"Boolean",default:!1,env:"INCOGNITO",arg:"incognito"},display:{doc:"If unset, the browser will run in headless mode, otherwise it will run in normal windowed mode.\nWhen running on MacOS or Windows, set it to any not-empty string.\nOn Linux, set it to a valid X server `DISPLAY` string (e.g. `:0`).",format:String,default:"",nullable:!0,arg:"display"},sessions:{doc:"The number of browser sessions to start.",format:"nat",default:0,env:"SESSIONS",arg:"sessions"},tabsPerSession:{doc:"The number of tabs to open in each browser session.",format:"nat",default:1,env:"TABS_PER_SESSION",arg:"tabs-per-session"},startSessionId:{doc:"The starting ID assigned to sessions.",format:"nat",default:0,env:"START_SESSION_ID",arg:"start-session-id"},startTimestamp:{doc:"The start timestamp (in milliseconds). If 0, the value will be calculated using `Date.now()`",format:"nat",default:0,env:"START_TIMESTAMP",arg:"start-timestamp"},enableDetailedStats:{doc:"If detailed participant metrics values should be collected.",format:"index",default:"0-24",nullable:!0,env:"ENABLE_DETAILED_STATS",arg:"enable-detailed-stats"},spawnRate:{doc:"The pages spawn rate (pages/s).",format:"float",default:1,env:"SPAWN_RATE",arg:"spawn-rate"},showPageLog:{doc:"If `true`, the pages console logs will be shown on console. Set to false to disable the page logs.",format:"Boolean",default:!1,env:"SHOW_PAGE_LOG",arg:"show-page-log"},pageLogFilter:{doc:"If set, only the logs with the matching text will be printed on the console. Regexp string allowed.",format:String,default:"",nullable:!0,env:"PAGE_LOG_FILTER",arg:"page-log-filter"},pageLogPath:{doc:"If set, the page console logs will be saved on the selected file path.",format:String,default:"",nullable:!0,env:"PAGE_LOG_PATH",arg:"page-log-path"},enableBrowserLogging:{doc:"It enables the Chromium browser logging for the specified session indexes. It requires the page log path option to be set. ",format:"index",nullable:!0,default:"",env:"ENABLE_BROWSER_LOGGING",arg:"enable-browser-logging"},userAgent:{doc:"The user agent override.",format:String,default:`Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${v.PUPPETEER_REVISIONS.chrome} Safari/537.36`,nullable:!0,env:"USER_AGENT",arg:"user-agent"},scriptPath:{doc:"One or more JavaScript file paths (comma-separated). If set, the files contents will be executed inside each opened tab page; the following global variables will be attached to the `webrtcperf` global object: `WEBRTC_PERF_SESSION` the session number (0-indexed); `WEBRTC_PERF_TAB` the tab number inside the same session (0-indexed); `WEBRTC_PERF_INDEX` the page absolute index (0-indexed).\nSuggested values:\n- With meet.google.com: https://raw.githubusercontent.com/vpalmisano/webrtcperf/refs/heads/devel/examples/google-meet.js\n",format:String,default:"",env:"SCRIPT_PATH",arg:"script-path"},scriptParams:{doc:"Additional parameters (in JSON format) that will be exposed into\nthe page context as `webrtcperf.params`.",format:String,nullable:!0,default:"",env:"SCRIPT_PARAMS",arg:"script-params"},disabledVideoCodecs:{doc:"A string with the video codecs to disable (comma-separated); e.g. `vp9,av1`",format:String,nullable:!0,default:"",env:"DISABLED_VIDEO_CODECS",arg:"disabled-video-codecs"},localStorage:{doc:"A JSON string with the `localStorage` object to be set on page load.",format:String,nullable:!0,default:"",env:"LOCAL_STORAGE",arg:"local-storage"},sessionStorage:{doc:"A JSON string with the `sessionStorage` object to be set on page load.",format:String,nullable:!0,default:"",env:"SESSION_STORAGE",arg:"session-storage"},clearCookies:{doc:"If true, all the page cookies are cleared.",format:"Boolean",default:!1,env:"CLEAR_COOKIES",arg:"clear-cookies"},enableGpu:{doc:'It enables the GPU acceleration (experimental). Set to "desktop" to use the host X server instance.',format:String,nullable:!0,default:"",env:"ENABLE_GPU",arg:"enable-gpu"},blockedUrls:{doc:"A comma-separated list of request URLs that will be automatically blocked.",format:String,nullable:!0,default:"",env:"BLOCKED_URLS",arg:"blocked-urls"},extraHeaders:{doc:'A dictionary of headers keyed by the url in JSON5 format (e.g. `{ "https://url.com/*": { "header-name": "value" } }`).',format:String,nullable:!0,default:"",env:"EXTRA_HEADERS",arg:"extra-headers"},responseModifiers:{doc:'A dictionary of content replacements keyed by the url in JSON5 format.\nExamples:\n- replace strings using a regular expression:\n `{ "https://url.com/*": [{ search: "searchString": replace: "anotherString" }] }`\n- completely replace the content:\n `{ "https://url.com/file.js": [{ file: "path/to/newFile.js" }] }`\n',format:String,nullable:!0,default:"",env:"RESPONSE_MODIFIERS",arg:"response-modifiers"},downloadResponses:{doc:'An array of url responses that will be saved to the disk, keyed by the url in JSON5 format.\nExample: `[{ urlPattern: "https://url.com/*", output: "save/directory" }]`\n',format:String,nullable:!0,default:"",env:"DOWNLOAD_RESPONSES",arg:"download-responses"},extraCSS:{doc:'A string with a CSS styles to inject into each page. Rules containing "important" will be replaced with "!important".',format:String,nullable:!0,default:"",env:"EXTRA_CSS",arg:"extra-css"},cookies:{doc:"A string with an array of [CookieParam](https://pptr.dev/api/puppeteer.cookieparam) to set into each page in JSON5 format.",format:String,nullable:!0,default:"",env:"COOKIES",arg:"cookies"},overridePermissions:{doc:"A comma-separated list of permissions to grant to the opened url.",format:String,nullable:!0,default:"",env:"OVERRIDE_PERMISSIONS",arg:"override-permissions"},hardwareConcurrency:{doc:"When set, it overrides the navigator.hardwareConcurrency property.",format:"nat",default:0,env:"HARDWARE_CONCURRENCY",arg:"hardware-concurrency"},debuggingPort:{doc:"The chrome debugging port. If this value != 0, the chrome instance will listen on the provided port + the start-session-id value.",format:"nat",default:0,env:"DEBUGGING_PORT",arg:"debugging-port"},debuggingAddress:{doc:"The chrome debugging listening address. If unset, the network default interface address will be used.",format:String,nullable:!0,default:"127.0.0.1",env:"DEBUGGING_ADDRESS",arg:"debugging-address"},emulateCpuThrottling:{doc:"The emulated CPU throttling factor. If set, the page will be throttled to the specified factor.",format:"nat",default:0,env:"EMULATE_CPU_THROTTLING",arg:"emulate-cpu-throttling"},showStats:{doc:"If the statistics should be displayed on the console output.",format:"Boolean",default:!0,env:"SHOW_STATS",arg:"show-stats"},statsPath:{doc:"The log file path; if set, the stats will be written in a .csv file inside that file.",format:String,default:"",env:"STATS_PATH",arg:"stats-path"},detailedStatsPath:{doc:"The log file path; if set, the detailed stats will be written in a .csv file inside that file.",format:String,default:"",env:"DETAILED_STATS_PATH",arg:"detailed-stats-path"},statsInterval:{doc:"The stats collect interval in seconds. It should be lower than the Prometheus scraping interval.",format:"nat",default:15,env:"STATS_INTERVAL",arg:"stats-interval"},rtcStatsTimeout:{doc:"The timeout in seconds after which the RTC stats coming from inactive hosts are removed. It should be higher than the `statsInterval` value.",format:"nat",default:60,env:"RTC_STATS_TIMEOUT",arg:"rtc-stats-timeout"},customMetrics:{doc:"A dictionary of custom metrics keys in JSON5 format (e.g. '{ statName1: { labels: [\"label1\"] } }').",format:String,nullable:!0,default:"",env:"CUSTOM_METRICS",arg:"custom-metrics"},prometheusPushgateway:{doc:'If set, logs are sent to the specified Prometheus Pushgateway service (example: "http://127.0.0.1:9091").',format:"String",default:"",nullable:!0,env:"PROMETHEUS_PUSHGATEWAY",arg:"prometheus-pushgateway"},prometheusPushgatewayJobName:{doc:"The Prometheus Pushgateway job name.",format:"String",default:"default",env:"PROMETHEUS_PUSHGATEWAY_JOB_NAME",arg:"prometheus-pushgateway-job-name"},prometheusPushgatewayAuth:{doc:"The Prometheus Pushgateway basic auth (username:password).",format:"String",default:"",nullable:!0,env:"PROMETHEUS_PUSHGATEWAY_AUTH",arg:"prometheus-pushgateway-auth"},prometheusPushgatewayGzip:{doc:"Allows to use gzip encoded pushgateway requests.",format:"Boolean",default:!0,env:"PROMETHEUS_PUSHGATEWAY_GZIP",arg:"prometheus-pushgateway-gzip"},alertRules:{doc:"Alert rules definition (in JSON format).",format:String,nullable:!0,default:"",env:"ALERT_RULES",arg:"alert-rules"},alertRulesOutput:{doc:"The alert rules report output filename. If the file ends with .log extension, a detailed log will be generated, otherwise a JSON report will be generated.",format:String,nullable:!0,default:"",env:"ALERT_RULES_OUTPUT",arg:"alert-rules-output"},alertRulesFailPercentile:{doc:"The alert rules report fails percentile (0-100). With the default value the alert will be successful only when at least 95% of the checks pass.",format:"nat",nullable:!1,default:95,env:"ALERT_RULES_FAIL_PERCENTILE",arg:"alert-rules-fail-percentile"},pushStatsUrl:{doc:"The URL to push the collected stats.",format:String,nullable:!0,default:"",env:"PUSH_STATS_URL",arg:"push-stats-url"},pushStatsId:{doc:"The ID of the collected stats to push.",format:String,nullable:!0,default:"default",env:"PUSH_STATS_ID",arg:"push-stats-id"},serverPort:{doc:"The HTTP server listening port.",format:"nat",nullable:!0,default:0,env:"SERVER_PORT",arg:"server-port"},serverSecret:{doc:"The HTTP server basic auth secret. The auth user name is set to `admin` by default.",format:String,default:"secret",env:"SERVER_SECRET",arg:"server-secret"},serverUseHttps:{doc:"If true, the server will use the HTTPS protocol.",format:"Boolean",default:!1,env:"SERVER_USE_HTTPS",arg:"server-use-https"},serverData:{doc:"An optional path that the HTTP server will expose with the /data endpoint.",format:String,nullable:!0,default:"",env:"SERVER_DATA",arg:"server-data"},vmafPath:{doc:"When set, it runs the VMAF calculator for the video files saved under the provided directory path.",format:String,nullable:!0,default:"",env:"VMAF_PATH",arg:"vmaf-path"},vmafPreview:{doc:"If true, for each VMAF comparison it creates a side-by-side video with the reference and degraded versions.",format:"Boolean",default:!1,env:"VMAF_PREVIEW",arg:"vmaf-preview"},vmafKeepIntermediateFiles:{doc:"If true, the VMAF intermediate files will not be deleted.",format:"Boolean",default:!1,env:"VMAF_KEEP_INTERMEDIATE_FILES",arg:"vmaf-keep-intermediate-files"},vmafKeepSourceFiles:{doc:"If true, the VMAF source files will not be deleted.",format:"Boolean",default:!0,env:"VMAF_KEEP_SOURCE_FILES",arg:"vmaf-keep-source-files"},vmafSkipDuplicated:{doc:"If true, the VMAF will skip duplicated recognized frames.",format:"Boolean",default:!1,env:"VMAF_SKIP_DUPLICATED",arg:"vmaf-skip-duplicated"},vmafCrop:{doc:'If set, the reference and degraded videos will be cropped using the specified configuration in JSON5 format. Crop configuration should be expressed using the ffmpeg crop filter syntax (https://ffmpeg.org/ffmpeg-filters.html#crop). E.g. `{ "Participant-000001_recv-by_Participant-000000": { ref: { w: "iw-10", h: "ih-5" }, deg: { w: "200", h: "200" } } }`',format:String,nullable:!0,default:"",env:"VMAF_CROP",arg:"vmaf-crop"},vmafPrepareVideo:{doc:"When set, it prepares the selected video applying a timestamp overlay on top of it. The filename must be provided in the format `<video path>,<ID>`, where the selected ID will be used unique video identifier in the overlay.",format:String,nullable:!0,default:"",env:"VMAF_PREPARE_VIDEO",arg:"vmaf-prepare-video"},vmafProcessVideo:{doc:"When set, it runs the VMAF video preprocessor, that converts a video file into the IVF format with timestamps matching the overlay recognition. The filename must contain a `recv` or `send` string to identify if the video was a reference (send) or a degraded version (recv), e.g. `Participant1_recv.mp4`.",format:String,nullable:!0,default:"",env:"VMAF_PROCESS_VIDEO",arg:"vmaf-process-video"},vmafVideoCrop:{doc:'If set, the vmaf prepared/processed video will be cropped using the specified configuration in JSON5 format. Crop configuration should be expressed using the ffmpeg crop filter syntax (https://ffmpeg.org/ffmpeg-filters.html#crop). E.g. `{ w: "iw-10", h: "ih-5", x: "10", y: \'5\' }`',format:String,nullable:!0,default:"",env:"VMAF_VIDEO_CROP",arg:"vmaf-video-crop"},visqolPath:{doc:"When set, it runs the visqol calculator for the audio files saved under the provided directory path.",format:String,nullable:!0,default:"",env:"VISQOL_PATH",arg:"visqol-path"},visqolKeepSourceFiles:{doc:"If true, the visqol source files will not be deleted.",format:"Boolean",default:!0,env:"VISQOL_KEEP_SOURCE_FILES",arg:"visqol-keep-source-files"}};function P(e,t,s){return s._cvtProperties?(Object.entries(s._cvtProperties).forEach((([s,a])=>{P(e,`${t?`${t}.`:""}${s}`,a)})),e):(t&&(e[t]={doc:s.doc,format:JSON.stringify(s.format,null,2),default:JSON.stringify(s.default,null,2)}),e)}(0,c.default)($).getProperties();function R(){const e={},t=(0,c.default)($).getSchema();return Object.entries(t._cvtProperties).forEach((([t,s])=>{const{format:a,doc:r,nullable:i}=s;e[t]={type:a,description:r,nullable:i}})),{name:"webrtcperf",description:"Starts a webrtcperf test.",parameters:{type:"object",properties:e,required:[]}}}const{GoogleGenAI:T}=s(72)},7174:e=>{e.exports=require("compression")},7191:e=>{e.exports=require("@vpalmisano/throttler")},7252:e=>{e.exports=require("express")},7564:function(e,t,s){var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.RtcStatsMetricNames=t.PageStatsNames=void 0,t.rtcStatKey=d,t.parseRtStatKey=function(e){const[t,s,a,r,i]=e.split(":",5);return{pageIndex:t?parseInt(t):void 0,trackId:i||void 0,hostName:a||"unknown",codec:r||void 0,participantName:s||void 0}},t.updateRtcStats=function(e,t,s,a,r,o){const{enabled:n,inboundRtp:c,outboundRtp:u,remoteAddress:h,videoSentActiveEncodings:p,sentMaxBitrate:f,isDisplay:g,codec:m,availableOutgoingBitrate:v}=a,w=d({pageIndex:t,trackId:s,hostName:r||h,codec:m,participantName:o});if(c){const t="video"===c.kind?g?"screen":"video":"audio";l(e,t+"RecvCodec",w,m),n&&(l(e,t+"RecvAvgJitterBufferDelay",w,c.jitterBuffer),l(e,t+"RecvBitrates",w,c.bitrate),l(e,t+"RecvBytes",w,c.bytesReceived),l(e,t+"RecvJitter",w,c.jitter),l(e,t+"RecvRoundTripTime",w,c.transportRoundTripTime),l(e,t+"RecvPackets",w,c.packetsReceived),l(e,t+"RecvRetransmittedPackets",w,c.retransmittedPacketsReceived),l(e,t+"RecvPacketsLost",w,c.packetsLossRate),l(e,t+"RecvPacketsLossRate",w,c.packetsLossRate),l(e,t+"RecvLostPackets",w,c.packetsLost),l(e,t+"RecvNackCountSent",w,c.nackCount),l(e,t+"RecvEndToEndDelay",w,c.endToEndDelay),"audio"===c.kind&&["audioLevel","totalSamplesReceived","concealedSamples","concealmentEvents","insertedSamplesForDeceleration","removedSamplesForAcceleration"].forEach((s=>{l(e,t+"Recv"+(0,i.toTitleCase)(s.replace("audio","")),w,c[s])})),"video"===c.kind&&c.keyFramesDecoded>0&&(l(e,t+"RecvFrames",w,c.framesReceived),l(e,t+"RecvFps",w,c.framesPerSecond),l(e,t+"RecvHeight",w,c.frameHeight),l(e,t+"RecvWidth",w,c.frameWidth),l(e,t+"RecvFrameRate",w,c.frameRate),l(e,t+"FirCountSent",w,c.firCount),l(e,t+"PliCountSent",w,c.pliCount),l(e,t+"DecodeLatency",w,c.decodeLatency),l(e,t+"TotalFreezesDuration",w,c.totalFreezesDuration)))}if(u){const t="video"===u.kind?g?"screen":"video":"audio";l(e,t+"SentCodec",w,m),n&&(l(e,t+"SentBitrates",w,u.bitrate),l(e,t+"SentBytes",w,u.bytesSent+u.headerBytesSent),l(e,t+"SentPackets",w,u.packetsSent),l(e,t+"SentPacketsLost",w,u.packetsLossRate),l(e,t+"SentNackCountRecv",w,u.nackCount),l(e,t+"SentRoundTripTime",w,u.roundTripTime),l(e,t+"SentJitter",w,u.jitter),l(e,t+"SentTransportRoundTripTime",w,u.transportRoundTripTime),l(e,"transportSentAvailableOutgoingBitrate",w,v),l(e,t+"SentMaxBitrate",w,f),l(e,t+"SentRetransmittedPackets",w,u.retransmittedPacketsSent),"video"===u.kind&&(l(e,t+"SentActiveEncodings",w,p),l(e,t+"QualityLimitationResolutionChanges",w,u.qualityLimitationResolutionChanges),l(e,t+"QualityLimitationCpu",w,u.qualityLimitationCpu),l(e,t+"QualityLimitationBandwidth",w,u.qualityLimitationBandwidth),l(e,t+"SentWidth",w,u.frameWidth),l(e,t+"SentHeight",w,u.frameHeight),l(e,t+"SentFrames",w,u.framesSent),l(e,t+"SentFps",w,u.framesPerSecond),l(e,t+"FirCountReceived",w,u.firCountReceived),l(e,t+"PliCountReceived",w,u.pliCountReceived),l(e,t+"EncodeLatency",w,u.encodeLatency),l(e,t+"SentLatency",w,u.sentLatency)))}};const r=a(s(2613)),i=s(6185);var o,n;!function(e){e.cpu="cpu",e.memory="memory",e.nodeCpu="nodeCpu",e.nodeMemory="nodeMemory",e.usedCpu="usedCpu",e.usedMemory="usedMemory",e.usedGpu="usedGpu",e.pageCpu="pageCpu",e.pageMemory="pageMemory",e.pages="pages",e.peerConnections="peerConnections",e.peerConnectionConnectionTime="peerConnectionConnectionTime",e.peerConnectionDisconnectionTime="peerConnectionDisconnectionTime",e.peerConnectionsCreated="peerConnectionsCreated",e.peerConnectionsConnected="peerConnectionsConnected",e.peerConnectionsClosed="peerConnectionsClosed",e.peerConnectionsDisconnected="peerConnectionsDisconnected",e.peerConnectionsFailed="peerConnectionsFailed",e.peerConnectionsDelay="peerConnectionsDelay",e.errors="errors",e.warnings="warnings",e.httpSentBytes="httpSentBytes",e.httpRecvBytes="httpRecvBytes",e.httpRecvLatency="httpRecvLatency",e.wsSentBytes="wsSentBytes",e.wsRecvBytes="wsRecvBytes",e.wsRecvLatency="wsRecvLatency",e.audioEndToEndDelay="audioEndToEndDelay",e.audioStartFrameDelay="audioStartFrameDelay",e.videoEndToEndDelay="videoEndToEndDelay",e.videoStartFrameDelay="videoStartFrameDelay",e.screenEndToEndDelay="screenEndToEndDelay",e.screenStartFrameDelay="screenStartFrameDelay",e.cpuPressure="cpuPressure",e.videoWidth="videoWidth",e.videoHeight="videoHeight",e.videoBufferedTime="videoBufferedTime",e.videoPlayingTime="videoPlayingTime",e.videoBufferingTime="videoBufferingTime",e.videoBufferingEvents="videoBufferingEvents",e.throttleUpRate="throttleUpRate",e.throttleUpDelay="throttleUpDelay",e.throttleUpLoss="throttleUpLoss",e.throttleUpQueue="throttleUpQueue",e.throttleDownRate="throttleDownRate",e.throttleDownDelay="throttleDownDelay",e.throttleDownLoss="throttleDownLoss",e.throttleDownQueue="throttleDownQueue"}(o||(t.PageStatsNames=o={})),function(e){e.audioSentCodec="audioSentCodec",e.audioSentBytes="audioSentBytes",e.audioSentPackets="audioSentPackets",e.audioSentBitrates="audioSentBitrates",e.audioSentPacketsLost="audioSentPacketsLost",e.audioSentNackCountRecv="audioSentNackCountRecv",e.audioSentRoundTripTime="audioSentRoundTripTime",e.audioSentJitter="audioSentJitter",e.audioSentTransportRoundTripTime="audioSentTransportRoundTripTime",e.audioSentMaxBitrate="audioSentMaxBitrate",e.audioSentRetransmittedPackets="audioSentRetransmittedPackets",e.videoSentCodec="videoSentCodec",e.videoFirCountReceived="videoFirCountReceived",e.videoPliCountReceived="videoPliCountReceived",e.videoEncodeLatency="videoEncodeLatency",e.videoSentLatency="videoSentLatency",e.videoQualityLimitationBandwidth="videoQualityLimitationBandwidth",e.videoQualityLimitationCpu="videoQualityLimitationCpu",e.videoQualityLimitationResolutionChanges="videoQualityLimitationResolutionChanges",e.videoSentActiveEncodings="videoSentActiveEncodings",e.videoSentBitrates="videoSentBitrates",e.videoSentBytes="videoSentBytes",e.videoSentPackets="videoSentPackets",e.videoSentFrames="videoSentFrames",e.videoSentFps="videoSentFps",e.videoSentWidth="videoSentWidth",e.videoSentHeight="videoSentHeight",e.videoSentMaxBitrate="videoSentMaxBitrate",e.videoSentPacketsLost="videoSentPacketsLost",e.videoSentNackCountRecv="videoSentNackCountRecv",e.videoSentRoundTripTime="videoSentRoundTripTime",e.videoSentTransportRoundTripTime="videoSentTransportRoundTripTime",e.videoSentJitter="videoSentJitter",e.videoSentRetransmittedPackets="videoSentRetransmittedPackets",e.screenSentCodec="screenSentCodec",e.screenFirCountReceived="screenFirCountReceived",e.screenPliCountReceived="screenPliCountReceived",e.screenEncodeLatency="screenEncodeLatency",e.screenSentLatency="screenSentLatency",e.screenQualityLimitationBandwidth="screenQualityLimitationBandwidth",e.screenQualityLimitationCpu="screenQualityLimitationCpu",e.screenQualityLimitationResolutionChanges="screenQualityLimitationResolutionChanges",e.screenSentActiveEncodings="screenSentActiveEncodings",e.screenSentBitrates="screenSentBitrates",e.screenSentBytes="screenSentBytes",e.screenSentPackets="screenSentPackets",e.screenSentFrames="screenSentFrames",e.screenSentFps="screenSentFps",e.screenSentWidth="screenSentWidth",e.screenSentHeight="screenSentHeight",e.screenSentMaxBitrate="screenSentMaxBitrate",e.screenSentPacketsLost="screenSentPacketsLost",e.screenSentNackCountRecv="screenSentNackCountRecv",e.screenSentRoundTripTime="screenSentRoundTripTime",e.screenSentTransportRoundTripTime="screenSentTransportRoundTripTime",e.screenSentJitter="screenSentJitter",e.screenSentRetransmittedPackets="screenSentRetransmittedPackets",e.audioRecvCodec="audioRecvCodec",e.audioRecvBytes="audioRecvBytes",e.audioRecvAvgJitterBufferDelay="audioRecvAvgJitterBufferDelay",e.audioRecvBitrates="audioRecvBitrates",e.audioRecvJitter="audioRecvJitter",e.audioRecvRoundTripTime="audioRecvRoundTripTime",e.audioRecvPackets="audioRecvPackets",e.audioRecvPacketsLost="audioRecvPacketsLost",e.audioRecvLostPackets="audioRecvLostPackets",e.audioRecvPacketsLossRate="audioRecvPacketsLossRate",e.audioRecvRetransmittedPackets="audioRecvRetransmittedPackets",e.audioRecvNackCountSent="audioRecvNackCountSent",e.audioRecvLevel="audioRecvLevel",e.audioRecvTotalSamplesReceived="audioRecvTotalSamplesReceived",e.audioRecvConcealedSamples="audioRecvConcealedSamples",e.audioRecvConcealmentEvents="audioRecvConcealmentEvents",e.audioRecvInsertedSamplesForDeceleration="audioRecvInsertedSamplesForDeceleration",e.audioRecvRemovedSamplesForAcceleration="audioRecvRemovedSamplesForAcceleration",e.audioRecvEndToEndDelay="audioRecvEndToEndDelay",e.videoRecvCodec="videoRecvCodec",e.videoFirCountSent="videoFirCountSent",e.videoPliCountSent="videoPliCountSent",e.videoDecodeLatency="videoDecodeLatency",e.videoRecvFrames="videoRecvFrames",e.videoRecvFps="videoRecvFps",e.videoRecvAvgJitterBufferDelay="videoRecvAvgJitterBufferDelay",e.videoRecvBitrates="videoRecvBitrates",e.videoRecvBytes="videoRecvBytes",e.videoRecvHeight="videoRecvHeight",e.videoRecvJitter="videoRecvJitter",e.videoRecvRoundTripTime="videoRecvRoundTripTime",e.videoRecvPackets="videoRecvPackets",e.videoRecvLostPackets="videoRecvLostPackets",e.videoRecvPacketsLost="videoRecvPacketsLost",e.videoRecvPacketsLossRate="videoRecvPacketsLossRate",e.videoRecvRetransmittedPackets="videoRecvRetransmittedPackets",e.videoRecvNackCountSent="videoRecvNackCountSent",e.videoRecvWidth="videoRecvWidth",e.videoRecvFrameRate="videoRecvFrameRate",e.videoTotalFreezesDuration="videoTotalFreezesDuration",e.videoRecvEndToEndDelay="videoRecvEndToEndDelay",e.screenRecvCodec="screenRecvCodec",e.screenFirCountSent="screenFirCountSent",e.screenPliCountSent="screenPliCountSent",e.screenDecodeLatency="screenDecodeLatency",e.screenRecvFrames="screenRecvFrames",e.screenRecvFps="screenRecvFps",e.screenRecvAvgJitterBufferDelay="screenRecvAvgJitterBufferDelay",e.screenRecvBitrates="screenRecvBitrates",e.screenRecvBytes="screenRecvBytes",e.screenRecvHeight="screenRecvHeight",e.screenRecvJitter="screenRecvJitter",e.screenRecvRoundTripTime="screenRecvRoundTripTime",e.screenRecvPackets="screenRecvPackets",e.screenRecvLostPackets="screenRecvLostPackets",e.screenRecvPacketsLost="screenRecvPacketsLost",e.screenRecvPacketsLossRate="screenRecvPacketsLossRate",e.screenRecvRetransmittedPackets="screenRecvRetransmittedPackets",e.screenRecvNackCountSent="screenRecvNackCountSent",e.screenRecvWidth="screenRecvWidth",e.screenRecvFrameRate="screenRecvFrameRate",e.screenTotalFreezesDuration="screenTotalFreezesDuration",e.screenRecvEndToEndDelay="screenRecvEndToEndDelay",e.transportSentAvailableOutgoingBitrate="transportSentAvailableOutgoingBitrate"}(n||(t.RtcStatsMetricNames=n={}));const c=Object.keys(n);function l(e,t,s,a){(0,r.default)(c.includes(t),`Unknown stat name: ${t}`),void 0!==a&&(e[t]||(e[t]={}),e[t][s]=a)}function d({pageIndex:e,trackId:t,hostName:s,codec:a,participantName:r}){return[e??"",r||"",s||"unknown",a||"",t||""].join(":")}},7618:e=>{e.exports=require("convict-format-with-validator")},7648:e=>{e.exports=require("pidusage")},8034:e=>{e.exports=require("puppeteer-core")},8219:e=>{e.exports=require("puppeteer-intercept-and-modify-requests")},8611:e=>{e.exports=require("http")},8938:e=>{e.exports=require("axios")},9026:e=>{e.exports=require("lorem-ipsum")},9278:e=>{e.exports=require("net")},9896:e=>{e.exports=require("fs")},9993:e=>{e.exports=require("puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency")}},t={};function s(a){var r=t[a];if(void 0!==r)return r.exports;var i=t[a]={id:a,loaded:!1,exports:{}};return e[a].call(i.exports,i,i.exports,s),i.loaded=!0,i.exports}s.c=t,s.nmd=e=>(e.paths=[],e.children||(e.children=[]),e);s(s.s=1859)})();
2
+ (()=>{"use strict";var e={72:e=>{e.exports=require("@google/genai")},423:e=>{e.exports=require("change-case")},699:e=>{e.exports=require("basic-auth")},857:e=>{e.exports=require("os")},981:e=>{e.exports=require("pidtree")},1021:e=>{e.exports=require("chart.js")},1046:e=>{e.exports=require("@puppeteer/browsers")},1410:e=>{e.exports=require("dockerode")},1707:function(e,t,s){var a,r=this&&this.__createBinding||(Object.create?function(e,t,s,a){void 0===a&&(a=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,a,r)}:function(e,t,s,a){void 0===a&&(a=s),e[a]=t[s]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||(a=function(e){return a=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},a(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s=a(e),o=0;o<s.length;o++)"default"!==s[o]&&r(t,e,s[o]);return i(t,e),t}),n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Stats=t.FastStats=void 0;const c=n(s(8938)),l=o(s(6261)),d=s(4040);Object.defineProperty(t,"FastStats",{enumerable:!0,get:function(){return d.Stats}});const u=o(s(9896)),h=o(s(8611)),p=o(s(5692)),f=n(s(5865)),g=o(s(6928)),m=o(s(4622)),v=s(4066),w=o(s(3106)),b=s(7564),y=s(6185),S=(0,y.logger)("webrtcperf:stats"),{default:$}=s(5072);function P(e,t=95){return Math.round(e.percentile(t))}class R{fname;columns;headerWritten=!1;constructor(e="stats.log",t){this.fname=e,this.columns=t}async push(e,t=!0){if(!this.headerWritten||!t){const e=["datetime",...this.columns].join(",")+"\n";await u.promises.mkdir(g.dirname(this.fname),{recursive:!0}),await u.promises.writeFile(this.fname,e),this.headerWritten=!0}const s=[Date.now(),...e].join(",")+"\n";return u.promises.appendFile(this.fname,s)}}function T(e,t=!1){return t?[(0,y.toPrecision)(e.length||0,0),(0,y.toPrecision)(e.sum||0),(0,y.toPrecision)(e.amean()||0),(0,y.toPrecision)(e.stddev()||0),(0,y.toPrecision)(e.percentile(5)||0),(0,y.toPrecision)(e.percentile(95)||0),(0,y.toPrecision)(e.min||0),(0,y.toPrecision)(e.max||0)]:{length:e.length||0,sum:e.sum||0,mean:e.amean()||0,stddev:e.stddev()||0,p5:e.percentile(5)||0,p95:e.percentile(95)||0,min:e.min||0,max:e.max||0}}function k(e){return(0,v.sprintf)($`-- {bold %(name)s} %(fill)s\n`,{name:e,fill:"-".repeat(110-e.length-4)})}function E(e,t,s=".2f",a="",r=1,i=!1){if(!t?.all.length)return"";r||(r=1);const o=T(t.all);return(0,v.sprintf)($`{red {bold %(name)\' 30s}}`+$` {bold %(length)\' 8d}`+(i?" ":$` {bold %(sum)\' 8${s}}`)+$` {bold %(mean)\' 8${s}}`+$` {bold %(stddev)\' 8${s}}`+$` {bold %(p5)\' 8${s}}`+$` {bold %(p95)\' 8${s}}`+$` {bold %(min)\' 8${s}}`+$` {bold %(max)\' 8${s}}%(unit)s\n`,{name:e,length:o.length,sum:o.sum*r,mean:o.mean*r,stddev:o.stddev*r,p5:o.p5*r,p95:o.p95*r,min:o.min*r,max:o.max*r,unit:a?$` {red {bold ${a}}}`:""})}const x=(e,t,s="",a=[],r)=>new m.Gauge({name:`wst_${t}${s&&"_"+s}`,help:`${t} ${s}`,labelNames:a,registers:[e],collect:r}),C=(e,t)=>t?100*Math.min(1,Math.abs(e-t)/t):100*Math.min(1,Math.abs(e));class _ extends l.EventEmitter{statsPath;detailedStatsPath;prometheusPushgateway;prometheusPushgatewayJobName;prometheusPushgatewayAuth;prometheusPushgatewayGzip;showStats;showPageLog;statsInterval;rtcStatsTimeout;customMetrics={};startTimestamp;enableDetailedStats;startTimestampString;sessions=new Map;nextSessionId;statsWriter;detailedStatsWriter;detailedStatsSummaryWriter;scheduler;alertRules=null;alertRulesOutput;alertRulesFailPercentile;pushStatsUrl;pushStatsId;serverSecret;alertRulesReport=new Map;gateway=null;elapsedTimeMetric=null;metrics={};alertTagsMetrics;customMetricsLabels;collectedStats;collectedStatsConfig={url:"",pages:0,startTime:0};externalCollectedStats=new Map;pushStatsInstance=null;detailedStatsSummary={};running=!1;constructor({statsPath:e,detailedStatsPath:t,prometheusPushgateway:s,prometheusPushgatewayJobName:a,prometheusPushgatewayAuth:r,prometheusPushgatewayGzip:i,showStats:o,showPageLog:n,statsInterval:l,rtcStatsTimeout:d,customMetrics:u,alertRules:g,alertRulesOutput:m,alertRulesFailPercentile:v,pushStatsUrl:b,pushStatsId:y,serverSecret:$,startSessionId:P,startTimestamp:R,enableDetailedStats:T,customMetricsLabels:k}){if(super(),this.statsPath=e,this.detailedStatsPath=t,this.prometheusPushgateway=s,this.prometheusPushgatewayJobName=a||"default",this.prometheusPushgatewayAuth=r||void 0,this.prometheusPushgatewayGzip=i,this.showStats=void 0===o||o,this.showPageLog=!!n,this.statsInterval=l||10,this.rtcStatsTimeout=Math.max(d,this.statsInterval),u.trim()&&(this.customMetrics=f.default.parse(u),S.debug(`using customMetrics: ${JSON.stringify(this.customMetrics,void 0,2)}`)),this.collectedStats=this.initCollectedStats(),this.sessions=new Map,this.nextSessionId=P,this.startTimestamp=R||Date.now(),this.startTimestampString=new Date(this.startTimestamp).toISOString(),this.enableDetailedStats=T,this.customMetricsLabels=k?k.split(",").reduce(((e,t)=>((t=t.trim())&&(e[t]=void 0),e)),{}):{},this.statsWriter=null,this.detailedStatsWriter=null,this.detailedStatsSummaryWriter=null,g.trim()&&(this.alertRules=f.default.parse(g),S.debug(`using alertRules: ${JSON.stringify(this.alertRules,void 0,2)}`)),this.alertRulesOutput=m,this.alertRulesFailPercentile=v,this.pushStatsUrl=b,this.pushStatsId=y,this.serverSecret=$,this.pushStatsUrl){const e=new h.Agent({keepAlive:!1}),t=new p.Agent({keepAlive:!1,rejectUnauthorized:!1});this.pushStatsInstance=c.default.create({httpAgent:e,httpsAgent:t,baseURL:this.pushStatsUrl,auth:{username:"admin",password:this.serverSecret},maxBodyLength:2e7,transformRequest:[...c.default.defaults.transformRequest,(e,t)=>t&&"string"==typeof e&&e.length>16384?(t["Content-Encoding"]="gzip",w.gzipSync(e)):e]})}}initCollectedStats(){return this.statsNames.reduce(((e,t)=>(e[t]={all:new d.Stats,byHost:{},byCodec:{},byParticipantAndTrack:{}},e)),{})}get statsNames(){return Object.keys(b.PageStatsNames).concat(Object.keys(b.RtcStatsMetricNames)).concat(Object.keys(this.customMetrics))}consumeSessionId(e=1){const t=this.nextSessionId;return this.nextSessionId+=e,t}addSession(e){if(S.debug(`addSession ${e.id}`),this.sessions.has(e.id))throw new Error(`session id ${e.id} already present`);e.once("stop",(e=>{S.debug(`Session ${e} stopped`),this.sessions.delete(e)})),this.sessions.set(e.id,e)}removeSession(e){S.debug(`removeSession ${e}`),this.sessions.delete(e)}setCustomMetricLabel(e,t){if(!(e in this.customMetricsLabels))throw new Error(`Unknown custom metric label: ${e}`);this.customMetricsLabels[e]=t}async start(){if(this.running)S.warn("already running");else{if(S.debug("start"),this.running=!0,this.statsPath){S.debug(`Logging stats into ${this.statsPath}`);const e=this.statsNames.reduce(((e,t)=>{return e.concat([`${s=t}_length`,`${s}_sum`,`${s}_mean`,`${s}_stdev`,`${s}_5p`,`${s}_95p`,`${s}_min`,`${s}_max`]);var s}),[]);this.statsWriter=new R(this.statsPath,e)}if(this.detailedStatsPath&&(S.debug(`Logging stats into ${this.statsPath}`),this.detailedStatsWriter=new R(this.detailedStatsPath,["participantName","trackId",...this.statsNames]),this.detailedStatsSummaryWriter=new R(this.detailedStatsPath.replace(/\.(.+)$/,"-summary.$1"),["participantName","trackId",...this.statsNames])),this.prometheusPushgateway){const e=new m.Registry,t=this.prometheusPushgateway.startsWith("https://")?new p.Agent({keepAlive:!0,keepAliveMsecs:6e4,maxSockets:5}):new h.Agent({keepAlive:!0,keepAliveMsecs:6e4,maxSockets:5});this.gateway=new m.Pushgateway(this.prometheusPushgateway,{timeout:5e3,auth:this.prometheusPushgatewayAuth,rejectUnauthorized:!1,agent:t,headers:this.prometheusPushgatewayGzip?{"Content-Encoding":"gzip"}:void 0},e),this.elapsedTimeMetric=x(e,"elapsedTime","",["datetime",...Object.keys(this.customMetricsLabels)],(()=>this.elapsedTimeMetric?.set({datetime:this.startTimestampString,...this.customMetricsLabels},(Date.now()-this.startTimestamp)/1e3))),this.statsNames.forEach((t=>{if(this.metrics[t]={length:x(e,t,"length",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),sum:x(e,t,"sum",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),mean:x(e,t,"mean",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),stddev:x(e,t,"stddev",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),p5:x(e,t,"p5",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),p95:x(e,t,"p95",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),min:x(e,t,"min",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),max:x(e,t,"max",["host","codec","datetime",...Object.keys(this.customMetricsLabels)]),alertRules:{}},!1!==this.enableDetailedStats&&(this.metrics[t].value=x(e,t,"",["participantName","trackId","datetime",...Object.keys(this.customMetricsLabels)])),this.alertRules&&this.alertRules[t]){const s=this.alertRules[t];for(const a of Object.keys(s)){const s=`alert_${t}_${a}`;this.metrics[t].alertRules[s]={report:x(e,s,"report",["rule","datetime",...Object.keys(this.customMetricsLabels)]),rule:x(e,s,"",["rule","datetime",...Object.keys(this.customMetricsLabels)]),mean:x(e,s,"mean",["rule","datetime",...Object.keys(this.customMetricsLabels)])}}}})),this.alertRules&&(this.alertTagsMetrics=x(e,"alert_report","",["datetime","tag",...Object.keys(this.customMetricsLabels)])),await this.deletePushgatewayStats()}this.scheduler=new y.Scheduler("stats",this.statsInterval,this.collectStats.bind(this)),this.scheduler.start()}}async deletePushgatewayStats(){if(this.gateway)try{const{resp:e,body:t}=await this.gateway.delete({jobName:this.prometheusPushgatewayJobName});t.length&&S.warn(`Pushgateway delete error ${e.statusCode}: ${t}`)}catch(e){S.error(`Pushgateway delete error: ${e.stack}`)}}async collectStats(e){if(!this.running)return;if(!this.sessions.size&&!this.externalCollectedStats.size)return;this.collectedStatsConfig.pages=0,this.collectedStatsConfig.startTime=this.startTimestamp;const t={};Object.entries(this.collectedStats).forEach((([e,s])=>{s.all.reset(),Object.values(s.byHost).forEach((e=>e.reset())),Object.values(s.byCodec).forEach((e=>e.reset())),t[e]=new Set(Object.keys(s.byParticipantAndTrack)),s.byParticipantAndTrack={}}));for(const[e,s]of this.sessions.entries()){this.collectedStatsConfig.url=`${(0,y.hideAuth)(s.url)}?${s.urlQuery}`,this.collectedStatsConfig.pages+=s.pages.size||0;const a=await s.updateStats();for(const[s,r]of Object.entries(a)){if(void 0===r)return;try{const a=this.collectedStats[s],i=t[s];if("number"==typeof r&&isFinite(r))a.all.push(r);else for(const[t,s]of Object.entries(r))if("number"==typeof s&&isFinite(s)){a.all.push(s);const{trackId:r,hostName:o,participantName:n}=(0,b.parseRtStatKey)(t);let c=a.byHost[o];if(c||(c=a.byHost[o]=new d.Stats),c.push(s),(0,y.enabledForSession)(e,this.enableDetailedStats)&&n){const e=`${n}:${r||""}`;a.byParticipantAndTrack[e]=s,i.delete(e)}}else if("string"==typeof s){a.all.push(1);let e=a.byCodec[s];e||(e=a.byCodec[s]=new d.Stats),e.push(1)}}catch(e){S.error(`session getStats name: ${s} error: ${e.stack}`,e)}}}for(const[s,a]of this.externalCollectedStats.entries()){const{addedTime:r,externalStats:i,config:o}=a;e-r>1e3*this.rtcStatsTimeout?(S.debug(`remove externalCollectedStats from ${s}`),this.externalCollectedStats.delete(s)):(S.debug(`add external stats from ${s}`),o.url&&(this.collectedStatsConfig.url=o.url),o.pages&&(this.collectedStatsConfig.pages+=o.pages),this.statsNames.forEach((e=>{const s=i[e];if(!s)return;const a=this.collectedStats[e],r=t[e];a.all.push(s.all),Object.entries(s.byHost).forEach((([e,t])=>{a.byHost[e]||(a.byHost[e]=new d.Stats),a.byHost[e].push(t)})),Object.entries(s.byCodec).forEach((([e,t])=>{a.byCodec[e]||(a.byCodec[e]=new d.Stats),a.byCodec[e].push(t)})),Object.entries(s.byParticipantAndTrack).forEach((([e,t])=>{a.byParticipantAndTrack[e]=t,r.delete(e)}))})))}if(this.emit("stats",this.collectedStats),this.pushStatsInstance){const e={};for(const[t,s]of Object.entries(this.collectedStats))e[t]={all:s.all.data,byHost:{},byCodec:{},byParticipantAndTrack:{}},Object.entries(s.byHost).forEach((([s,a])=>{e[t].byHost[s]=a.data})),Object.entries(s.byCodec).forEach((([s,a])=>{e[t].byCodec[s]=a.data})),Object.entries(s.byParticipantAndTrack).forEach((([s,a])=>{e[t].byParticipantAndTrack[s]=a}));try{const t=await this.pushStatsInstance.put("/collected-stats",{id:this.pushStatsId,stats:e,config:this.collectedStatsConfig});S.debug(`pushStats message=${t.data.message}`)}catch(e){S.error(`pushStats error: ${e.stack}`)}}this.checkAlertRules(),this.consoleShowStats(),await Promise.allSettled([this.writeStats(),this.writeDetailedStats(),this.sendToPushGateway(t),this.writeAlertRulesReport()])}async writeStats(){if(!this.statsWriter)return;const e=this.statsNames.reduce(((e,t)=>e.concat(T(this.collectedStats[t].all,!0))),[]);await this.statsWriter.push(e)}async writeDetailedStats(){if(!this.detailedStatsWriter)return;const e=new Map;Object.entries(this.collectedStats).forEach((([t,s])=>{Object.entries(s.byParticipantAndTrack).forEach((([s,a])=>{let r=e.get(s);r||(r={},e.set(s,r)),r[t]=a}))}));for(const[t,s]of e.entries()){const[e,a]=t.split(":",2);this.detailedStatsSummary[t]||(this.detailedStatsSummary[t]={});const r=this.detailedStatsSummary[t],i=[e,a];for(const e of this.statsNames){i.push(void 0!==s[e]?(0,y.toPrecision)(s[e],6):""),r[e]||(r[e]=new d.Stats({store_data:!1}));const t=r[e];void 0!==s[e]&&t.push(s[e])}await this.detailedStatsWriter.push(i)}}async writeDetailedStatsSummary(){if(!this.detailedStatsSummaryWriter)return;let e=!1;for(const t of Object.keys(this.detailedStatsSummary)){const s=this.detailedStatsSummary[t],[a,r]=t.split(":",2),i=[a,r];for(const e of this.statsNames){const t=s[e];i.push(t?.length>0?(0,y.toPrecision)(t.amean(),6):"")}await this.detailedStatsSummaryWriter.push(i,e),e=!0}}addExternalCollectedStats(e,t,s){S.debug(`addExternalCollectedStats from ${e}`);const a=Date.now();this.externalCollectedStats.set(e,{addedTime:a,externalStats:t,config:s})}consoleShowStats(){if(!this.showStats)return;const e=this.collectedStats;let t=k((new Date).toUTCString())+(0,v.sprintf)($`{bold %(name)\' 30s} {bold %(length)\' 8s} {bold %(sum)\' 8s} {bold %(mean)\' 8s} {bold %(stddev)\' 8s} {bold %(p5)\' 8s} {bold %(p95)\' 8s} {bold %(min)\' 8s} {bold %(max)\' 8s}\n`,{name:"name",length:"count",sum:"sum",mean:"mean",stddev:"stddev",p5:"5p",p95:"95p",min:"min",max:"max"})+E("System CPU",e.usedCpu,".2f","%",void 0,!0)+E("System GPU",e.usedGpu,".2f","%",void 0,!0)+E("System Memory",e.usedMemory,".2f","%",void 0,!0)+E("CPU/page",e.cpu,".2f","%")+E("Memory/page",e.memory,".2f","MB")+E("Pages",e.pages,"d","")+E("Errors",e.errors,"d","")+E("Warnings",e.warnings,"d","")+E("Peer Connections",e.peerConnections,"d","")+E("audioSubscribeDelay",e.audioSubscribeDelay,"d","ms",void 0,!0)+E("videoSubscribeDelay",e.videoSubscribeDelay,"d","ms",void 0,!0)+k("Inbound audio")+E("received",e.audioBytesReceived,".2f","MB",1e-6)+E("rate",e.audioRecvBitrates,".2f","Kbps",.001)+E("lost",e.audioRecvPacketsLost,".2f","%",void 0,!0)+E("jitter",e.audioRecvJitter,".2f","s",void 0,!0)+E("avgJitterBufferDelay",e.audioRecvAvgJitterBufferDelay,".2f","ms",1e3,!0)+k("Inbound video")+E("received",e.videoRecvBytes,".2f","MB",1e-6)+E("decoded",e.videoFramesDecoded,"d","frames")+E("rate",e.videoRecvBitrates,".2f","Kbps",.001)+E("lost",e.videoRecvPacketsLost,".2f","%",void 0,!0)+E("jitter",e.videoRecvJitter,".2f","s",void 0,!0)+E("avgJitterBufferDelay",e.videoRecvAvgJitterBufferDelay,".2f","ms",1e3,!0)+E("width",e.videoRecvWidth,"d","px",void 0,!0)+E("height",e.videoRecvHeight,"d","px",void 0,!0)+E("fps",e.videoRecvFps,"d","fps",void 0,!0)+E("firCountSent",e.firCountSent,"d","",void 0,!0)+E("pliCountSent",e.pliCountSent,"d","",void 0,!0)+k("Outbound audio")+E("sent",e.audioBytesSent,".2f","MB",1e-6)+E("retransmitted",e.audioRetransmittedBytesSent,".2f","MB",1e-6)+E("rate",e.audioSentBitrates,".2f","Kbps",.001)+E("lost",e.audioSentPacketsLost,".2f","%",void 0,!0)+E("roundTripTime",e.audioSentRoundTripTime,".3f","s",void 0,!0)+k("Outbound video")+E("sent",e.videoSentBytes,".2f","MB",1e-6)+E("retransmitted",e.videoSentRetransmittedBytes,".2f","MB",1e-6)+E("rate",e.videoSentBitrates,".2f","Kbps",.001)+E("lost",e.videoSentPacketsLost,".2f","%",void 0,!0)+E("roundTripTime",e.videoSentRoundTripTime,".3f","s",void 0,!0)+E("qualityLimitResolutionChanges",e.videoQualityLimitationResolutionChanges,"d","")+E("qualityLimitationCpu",e.videoQualityLimitationCpu,"d","%")+E("qualityLimitationBandwidth",e.videoQualityLimitationBandwidth,"d","%")+E("sentActiveSpatialLayers",e.videoSentActiveSpatialLayers,"d","layers",void 0,!0)+E("sentMaxBitrate",e.videoSentMaxBitrate,".2f","Kbps",.001)+E("width",e.videoSentWidth,"d","px",void 0,!0)+E("height",e.videoSentHeight,"d","px",void 0,!0)+E("fps",e.videoSentFps,"d","fps",void 0,!0)+E("firCountReceived",e.videoFirCountReceived,"d","",void 0,!0)+E("pliCountReceived",e.videoPliCountReceived,"d","",void 0,!0);if(this.alertRules){const e=this.formatAlertRulesReport();e.length&&(t+=k("Alert rules report"),t+=e)}this.showPageLog||console.clear(),console.log(t)}async sendToPushGateway(e){if(!this.gateway||!this.running)return;const t=(Date.now()-this.startTimestamp)/1e3,s=this.startTimestampString;Object.entries(this.metrics).forEach((([a,r])=>{if(!this.collectedStats[a])return;const i=(e,t,a)=>{const i={host:t,codec:a,datetime:s,...this.customMetricsLabels},{length:o,sum:n,mean:c,stddev:l,p5:d,p95:u,min:h,max:p}=T(e);r.length.set(i,o),r.sum.set(i,n),r.mean.set(i,c),r.stddev.set(i,l),r.p5.set(i,d),r.p95.set(i,u),r.min.set(i,h),r.max.set(i,p)};i(this.collectedStats[a].all,"all","all"),Object.entries(this.collectedStats[a].byHost).forEach((([e,t])=>{i(t,e,"all")})),Object.entries(this.collectedStats[a].byCodec).forEach((([e,t])=>{i(t,"all",e)})),r.value&&Object.entries(this.collectedStats[a].byParticipantAndTrack).forEach((([e,t])=>{const[a,i]=e.split(":",2);r.value?.set({participantName:a,trackId:i,datetime:s,...this.customMetricsLabels},t)}));const o=e[a];if(o)for(const e of o){const[t,a]=e.split(":",2);r.value?.remove({participantName:t,trackId:a,datetime:s,...this.customMetricsLabels})}if(this.alertRules&&this.alertRules[a]){const e=this.alertRules[a];for(let[r,i]of Object.entries(e))if("tags"!==r){Array.isArray(i)||(i=[i]);for(const e of i){if(void 0!==e.$after&&t<e.$after)continue;const i=`alert_${a}_${r}`,o=this.metrics[a].alertRules[i],n=void 0!==e.$before&&t>e.$before,c=this.getAlertRuleDesc(r,e),l=this.alertRulesReport.get(a);if(l){const e=l.get(c);if(e){const t={rule:c,datetime:s,...this.customMetricsLabels};n?(o.report.remove(t),o.mean.remove(t)):(o.report.set(t,e.failAmountPercentile),o.mean.set(t,e.valueStats.amean()))}}if(void 0!==e.$eq){const t={rule:`${a} ${r} =`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$eq)}if(void 0!==e.$lt){const t={rule:`${a} ${r} <`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$lt)}if(void 0!==e.$lte){const t={rule:`${a} ${r} <=`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$lte)}if(void 0!==e.$gt){const t={rule:`${a} ${r} >`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$gt)}if(void 0!==e.$gte){const t={rule:`${a} ${r} >=`,datetime:s,...this.customMetricsLabels};n?o.rule.remove(t):o.rule.set(t,e.$gte)}}}}}));const a=this.getAlertRulesTags();if(a&&this.alertTagsMetrics)for(const[e,t]of a.entries())this.alertTagsMetrics.set({datetime:s,tag:e,...this.customMetricsLabels},P(t,this.alertRulesFailPercentile));try{const{resp:e,body:t}=await this.gateway.push({jobName:this.prometheusPushgatewayJobName});t.length&&S.warn(`Pushgateway error ${e.statusCode}: ${t}`)}catch(e){S.error(`Pushgateway push error: ${e.stack}`)}}getAlertRuleDesc(e,t){const s=[];void 0!==t.$eq&&s.push(`= ${t.$eq}`),void 0!==t.$gt&&s.push(`> ${t.$gt}`),void 0!==t.$gte&&s.push(`>= ${t.$gte}`),void 0!==t.$lt&&s.push(`< ${t.$lt}`),void 0!==t.$lte&&s.push(`<= ${t.$lte}`);let a=`${e} ${s.join(" and ")}`;return void 0!==t.$after&&(a+=` after ${t.$after}s`),void 0!==t.$before&&(a+=` before ${t.$before}s`),a}checkAlertRules(){if(!this.alertRules||!this.running)return;const e=Date.now(),t=(e-this.startTimestamp)/1e3;for(const[s,a]of Object.entries(this.alertRules)){if(!this.collectedStats[s])continue;let r=this.alertRulesFailPercentile;const i=T(this.collectedStats[s].all);for(let[o,n]of Object.entries(a)){if(["tags","failPercentile"].includes(o)){"failPercentile"===o&&(r=n);continue}Array.isArray(n)||(n=[n]);let a=t;for(const c of n){if(void 0!==c.$after&&t<c.$after||void 0!==c.$before&&t>c.$before)continue;void 0!==c.$after&&(a-=c.$after);const n=i[o];if(!isFinite(n))continue;const l=this.getAlertRuleDesc(o,c);let d=!1,u=0;void 0!==c.$skip_lt&&n<c.$skip_lt||void 0!==c.$skip_lte&&n<=c.$skip_lte||void 0!==c.$skip_gt&&n>c.$skip_gt||void 0!==c.$skip_gte&&n>=c.$skip_gte||(void 0!==c.$eq?n!==c.$eq&&(d=!0,u=C(n,c.$eq)):(void 0!==c.$lt?n>=c.$lt&&(d=!0,u=C(n,c.$lt)):void 0!==c.$lte&&n>c.$lte&&(d=!0,u=C(n,c.$lte)),d||(void 0!==c.$gt?n<=c.$gt&&(d=!0,u=C(n,c.$gt)):void 0!==c.$gte&&n<c.$gte&&(d=!0,u=C(n,c.$gte)))),this.updateRulesReport(s,n,l,d,u,e,a,r))}}}}updateRulesReport(e,t,s,a,r,i,o,n){a&&S.debug(`updateRulesReport ${e}.${s} failed: ${a} checkValue: ${t} failAmount: ${r} elapsedSeconds: ${o}`);let c=this.alertRulesReport.get(e);c||(c=new Map,this.alertRulesReport.set(e,c));let l=c.get(s);l||(l={totalFails:0,totalFailsTime:0,totalFailsTimePerc:0,lastFailed:0,valueStats:new d.Stats,failAmountStats:new d.Stats,failAmountPercentile:0},c.set(s,l)),a?(l.totalFails+=1,l.lastFailed&&(l.totalFailsTime+=(i-l.lastFailed)/1e3),l.lastFailed=i):l.lastFailed=0,l.totalFailsTimePerc=Math.round(100*l.totalFailsTime/o),l.valueStats.push(t),l.failAmountStats.push(r),l.failAmountPercentile=P(l.failAmountStats,n)}getAlertRulesTags(){if(!this.alertRules)return;const e=new Map;for(const[t,s]of this.alertRulesReport.entries()){const a=this.alertRules[t].tags||[];for(const t of a)e.has(t)||e.set(t,new d.Stats);for(const t of s.values()){const{failAmountPercentile:s}=t;for(const t of a){const a=e.get(t);a&&a.push(s)}}}return e}formatAlertRulesReport(e=null){if(!this.alertRulesReport||!this.alertRules)return"";const t=this.getAlertRulesTags();if("json"===e){const e={tags:{},reports:{}};for(const[t,s]of this.alertRulesReport.entries())for(const[a,r]of s.entries()){const{totalFails:s,totalFailsTime:i,valueStats:o,totalFailsTimePerc:n,failAmountStats:c,failAmountPercentile:l}=r;s&&(e.reports[`${t} ${a}`]={totalFails:s,totalFailsTime:Math.round(i),valueAverage:o.amean(),totalFailsTimePerc:n,failAmount:l,count:c.length})}for(const[s,a]of t.entries())e.tags[s]=P(a,this.alertRulesFailPercentile);return JSON.stringify(e,null,2)}let s="",a=20;for(const[e,t]of this.alertRulesReport.entries())for(const[s,r]of t.entries()){const{totalFails:t,totalFailsTimePerc:i}=r;if(t&&i>0){const t=`${e} ${s}`;a=Math.max(a,t.length)}}s+=e?(0,v.sprintf)(`| %(check)-${a}s | %(total)-10s | %(totalFailsTime)-15s | %(totalFailsTimePerc)-15s | %(failAmount)-15s |\n`,{check:"Condition",total:"Fails",totalFailsTime:"Fail time (s)",totalFailsTimePerc:"Fail time (%)",failAmount:"Fail amount %"}):(0,v.sprintf)($`{bold %(check)-${a}s} {bold %(total)-10s} {bold %(totalFailsTime)-15s} {bold %(totalFailsTimePerc)-15s} {bold %(failAmount)-15s}\n`,{check:"Condition",total:"Fails",totalFailsTime:"Fail time (s)",totalFailsTimePerc:"Fail time (%)",failAmount:"Fail amount %"});for(const[t,r]of this.alertRulesReport.entries())for(const[i,o]of r.entries()){const{totalFails:r,totalFailsTime:n,failAmountPercentile:c,totalFailsTimePerc:l}=o;r&&l>0&&(s+=e?(0,v.sprintf)(`| %(check)-${a}s | %(totalFails)-10s | %(totalFailsTime)-15s | %(totalFailsTimePerc)-15s | %(failAmountPercentile)-15s |\n`,{check:`${t} ${i}`,totalFails:r,totalFailsTime:Math.round(n),totalFailsTimePerc:l,failAmountPercentile:c}):(0,v.sprintf)($`{red {bold %(check)-${a}s}} {bold %(totalFails)-10s} {bold %(totalFailsTime)-15s} {bold %(totalFailsTimePerc)-15s} {bold %(failAmountPercentile)-15s}\n`,{check:`${t} ${i}`,totalFails:r,totalFailsTime:Math.round(n),totalFailsTimePerc:l,failAmountPercentile:c}))}e?(s+=(0,v.sprintf)("%(fill)s\n",{fill:"-".repeat(a+15+7)}),s+=(0,v.sprintf)(`| %(name)-${a}s | %(failPerc)-15s |\n`,{name:"Tag",failPerc:"Fail %"})):(s+=(0,v.sprintf)("%(fill)s\n",{fill:"-".repeat(a+15)}),s+=(0,v.sprintf)($`{bold %(name)-${a}s} {bold %(failPerc)-15s}\n`,{name:"Tag",failPerc:"Fail %"}));for(const[r,i]of t.entries()){const t=P(i,this.alertRulesFailPercentile);if(e)s+=(0,v.sprintf)(`| %(tag)-${a}s | %(failPerc)-15s |\n`,{tag:r,failPerc:t});else{const e=t<5?"green":t<25?"yellowBright":t<50?"yellow":"red";s+=(0,v.sprintf)($`{${e} {bold %(tag)-${a}s %(failPerc)-15s}}\n`,{tag:r,failPerc:t})}}return s}async writeAlertRulesReport(){if(this.alertRules&&this.alertRulesOutput&&this.running){S.debug(`writeAlertRulesReport writing in ${this.alertRulesOutput}`);try{const e=this.alertRulesOutput.split(".").slice(-1)[0],t=this.formatAlertRulesReport(e);if(!t.length)return;let s;if("log"===e){const e=t.split("\n").filter((e=>e.length)),a=`Alert rules report (${(new Date).toISOString()})`;s=(0,v.sprintf)("-- %(name)s %(fill)s\n",{name:a,fill:"-".repeat(Math.max(4,e[0].length-a.length-4))}),s+=t,s+=(0,v.sprintf)("%(fill)s\n",{fill:"-".repeat(e[e.length-1].length)})}else s=t;await u.promises.mkdir(g.dirname(this.alertRulesOutput),{recursive:!0}),await u.promises.writeFile(this.alertRulesOutput,s)}catch(e){S.error(`writeAlertRulesReport error: ${e.stack}`)}}}async stop(){if(this.running){this.running=!1,S.debug("stop"),this.scheduler&&(await this.scheduler.stop(),this.scheduler=void 0);for(const e of this.sessions.values())try{e.removeAllListeners(),await e.stop()}catch(e){S.error(`session stop error: ${e.stack}`)}this.sessions.clear(),await this.writeDetailedStatsSummary(),this.statsWriter=null,this.detailedStatsWriter=null,this.detailedStatsSummaryWriter=null,this.detailedStatsSummary={},this.gateway&&(await this.deletePushgatewayStats(),this.gateway=null,this.metrics={}),this.collectedStats=this.initCollectedStats(),this.externalCollectedStats.clear()}}}t.Stats=_},1712:function(e,t,s){e=s.nmd(e);var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.calculateVisqolScore=c;const r=s(6185),i=a(s(9896)),o=a(s(6928)),n=(0,r.logger)("webrtcperf:visqol");async function c(e){const{visqolPath:t,visqolKeepSourceFiles:s}=e;n.debug("calculateVisqolScore",{visqolPath:t,visqolKeepSourceFiles:s});const a=new Set,c=new Set,l=await(0,r.getFiles)(t,"");for(const e of l){if(!e.endsWith(".wav")&&!e.endsWith(".f32le.raw"))continue;const t=o.default.basename(e).includes("_send_"),n=o.default.basename(e).includes("_recv_");if(!n&&!t)continue;let l=e;e.endsWith(".f32le.raw")&&(l=o.default.join(o.default.dirname(e),o.default.basename(e).replace(".f32le.raw",".wav")),await(0,r.runShellCommand)(`ffmpeg -hide_banner -loglevel info -f f32le -ar 48000 -ac 1 -i ${e} -ac 1 ${l}`),s||i.default.unlinkSync(e)),t?a.add(l):n&&c.add(l)}for(const e of a.values())for(const t of c.values()){n.info(`Calculating score ${e} -> ${t}`);try{await(0,r.runShellCommand)(`/usr/bin/visqol --reference_file ${e} --degraded_file ${t} --similarity_to_quality_model /usr/share/visqol/model/tcdaudio14_aacvopus_coresv_svrnsim_n.68_g.01_c1.model --results_csv ${o.default.dirname(e)}/visqol.csv`)}catch(e){n.error("Error calculating score:",e.stack)}}}s.c[s.s]===e&&(async()=>{await c({visqolPath:process.argv[2],visqolKeepSourceFiles:!0})})().catch((e=>console.error(e)))},1845:e=>{e.exports=require("node-os-utils")},1859:function(e,t,s){e=s.nmd(e);var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Application=void 0;const r=s(7191),i=s(423),o=a(s(9896)),n=a(s(5865)),c=s(7028),l=s(2680),d=s(6997),u=s(6976),h=s(1707),p=s(6185),f=s(1712),g=s(5812),m=a(s(6928)),v=s(6259),w=s(6261),b=s(6958),{marked:y}=s(3840);y.use((0,v.markedTerminal)({reflowText:!0,tab:2}));const S=(0,p.logger)("webrtcperf");class $ extends w.EventEmitter{config;stats;server;mediaPaths=[];constructor(e){super(),e.startTimestamp||(e.startTimestamp=Date.now()),this.config=e,this.stats=new h.Stats(e),e.serverPort&&(this.server=new d.Server(e,this.stats))}async start(){S.debug(`start (runDuration: ${this.config.runDuration})`),await this.stats.start(),this.server&&await this.server.start();const e=this.config;if(e.vmafPrepareVideo&&await(0,g.prepareVideo)(e,!0),e.vmafProcessVideo&&await(0,g.convertToIvf)(e.vmafProcessVideo,e.vmafVideoCrop,e.vmafKeepSourceFiles,e.vmafSkipDuplicated),e.sessions>0){if(e.videoPath&&!this.mediaPaths.length)for(const t of e.videoPath.split(",")){const s=await(0,l.prepareFakeMedia)({...e,videoPath:t});this.mediaPaths.push(s)}e.throttleConfig&&await(0,r.startThrottle)(e.throttleConfig),e.chromiumUrl||e.chromiumPath||await(0,p.checkChromeExecutable)(),e.randomAudioPeriod&&(0,p.startRandomActivateAudio)(this.stats.sessions,e.randomAudioPeriod,e.randomAudioProbability,e.randomAudioRange);const t=1e3/e.spawnRate;S.debug(`Starting ${e.sessions} sessions (spawnPeriod: ${t}ms)`);const s=Date.now();for(let s=0;s<e.sessions;s+=1){const a=this.stats.consumeSessionId(e.tabsPerSession);await this.startSession(a,t),s<e.sessions-1&&await(0,p.sleep)(t)}const a=Math.round((Date.now()-s)/1e3),i=e.sessions*e.tabsPerSession/a;S.debug(`${e.sessions*e.tabsPerSession} pages started in ${a}s (${i.toFixed(2)}/s)`)}(e.runDuration||e.vmafPath||e.visqolPath)&&setTimeout((()=>this.stop()),1e3*e.runDuration)}async startSession(e,t){S.debug(`startSession ${e}`);const s=(0,r.getSessionThrottleIndex)(e),a=this.mediaPaths.length?this.mediaPaths[e%this.mediaPaths.length]:void 0,i=new u.Session({...this.config,mediaPath:a,spawnPeriod:t,id:e,throttleIndex:s});i.once("stop",(()=>{console.warn(`Session ${e} stopped, reloading...`),setTimeout((()=>this.startSession(e,t)),t)})),this.stats.addSession(i),await i.start()}async postTest(){if(S.debug("postTest"),this.config.vmafPath){console.log("Calculating VMAF score...");try{await(0,g.calculateVmafScore)(this.config)}catch(e){S.error(`vmaf score error: ${e.stack}`)}}if(this.config.visqolPath){console.log("Calculating Visqol score...");try{await(0,f.calculateVisqolScore)(this.config)}catch(e){S.error(`visqol score error: ${e.stack}`)}}}async stop(e=!1){if(S.debug(`stop (canceled: ${e})`),(0,p.stopRandomActivateAudio)(),await this.stats.stop(),this.config.throttleConfig&&await(0,r.stopThrottle)(),(0,p.stopTimers)(),await this.postTest(),this.config.pageLogPath)try{const e=await(0,p.getDockerLogsPath)(),t=m.default.dirname(this.config.pageLogPath);await o.default.promises.cp(e,m.default.resolve(t,"docker.log"))}catch(e){S.debug(`docker logs not found: ${e.message}`)}this.server?.stop(),this.emit("stop",e)}}t.Application=$,s.c[s.s]===e&&async function(){!function(){if(process.argv.includes("--help")||process.argv.includes("-h")){const e=(0,c.getConfigDocs)();let t=y.parse("**Webrtcperf parameters**\n\n`--version` It shows the package version.\n");Object.entries(e).forEach((([e,s])=>{t+=y.parse(`\n\`--${(0,i.paramCase)(e)}\`\n${s.doc}\nDefault value: \`${s.default}\`\n`)})),console.log(t),process.exit(0)}else if(process.argv.includes("--version")||process.argv.includes("-v")){const e=n.default.parse(o.default.readFileSync((0,p.resolvePackagePath)("package.json")).toString()).version;console.log(e),process.exit(0)}}();const e=process.argv.slice(2);if(e.includes("--docker")){try{await(0,b.runWithDocker)(e)}catch(e){S.error(`runWithDocker error: ${e.stack}`),process.exit(1)}process.exit(0)}let t,s;if(e.includes("--prompt")){const s=await(0,c.loadConfigFromPrompt)(e.filter((e=>!["--prompt","--dry-run"].includes(e))).join(" "));process.argv.slice(2).includes("--dry-run")&&(console.log(n.default.stringify(s,null,2)),process.exit(0)),t=await(0,c.loadConfig)(void 0,s)}else t=await(0,c.loadConfig)(process.argv[2]);if(!t.length)throw new Error("No configuration found");const a=()=>{const e=t.splice(0,1)[0];return s=new $(e),s.once("stop",(e=>{!e&&t.length?(S.info(`Application stopped, running next (${t.length} left)...`),a()):process.exit(0)})),s.start()},r=async()=>{console.log("Exiting..."),await s.stop(!0)};(0,p.registerExitHandler)((()=>r())),await a(),process.stdin&&process.stdin.setRawMode&&(console.log("Press [q] to quit or [x] to exit immediately"),process.stdin.setRawMode(!0),process.stdin.resume(),process.stdin.on("data",(async e=>{if(S.debug("[stdin]",e[0]),e[0]==="q".charCodeAt(0))try{await r()}catch(e){S.error(`stop error: ${e.stack}`),process.exit(1)}else e[0]==="x".charCodeAt(0)&&process.exit(1)})))}().catch((e=>{console.error(e),process.exit(-1)}))},2011:e=>{e.exports=require("node-cache")},2115:e=>{e.exports=require("yaml")},2250:e=>{e.exports=require("dns")},2305:e=>{e.exports=require("form-data")},2365:e=>{e.exports=require("tar-fs")},2613:e=>{e.exports=require("assert")},2680:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.prepareFakeMedia=async function({videoPath:e,videoWidth:t,videoHeight:s,videoFramerate:n,videoSeek:c,videoDuration:l,videoCacheRaw:d,videoCachePath:u,videoFormat:h,useFakeMedia:p}){if(i.debug("prepareFakeMedia",{videoPath:e,videoWidth:t,videoHeight:s,videoFramerate:n,videoSeek:c,videoDuration:l,videoCacheRaw:d,videoCachePath:u,videoFormat:h,useFakeMedia:p}),!e)throw new Error("empty video path");e.startsWith("http")||e.startsWith("generate:")||(0,a.existsSync)(e)||(i.warn(`video not found: ${e}, using default test video`),e=o);await a.promises.mkdir(u,{recursive:!0});const f=(0,r.sha256)(e),g=`${u}/${f}_${t}x${s}_${n}fps.${h}`,m=`${u}/${f}.wav`,v=p?"":`${u}/${f}_${t}x${s}_${n}fps.mp4`,w=p?"":`${u}/${f}.m4a`;if(!(0,a.existsSync)(g)||!(0,a.existsSync)(m)||v&&!(0,a.existsSync)(v)||w&&!(0,a.existsSync)(w)||!d){i.info(`Converting ${e} to ${g}, ${m}${v?`, ${v}`:""}${w?`, ${w}`:""}`);const o=`${u}/${f}_${t}x${s}_${n}fps.tmp.${h}`,d=`${u}/${f}.tmp.wav`,b=p?"":`${u}/${f}_${t}x${s}_${n}fps.tmp.mp4`,y=p?"":`${u}/${f}.tmp.m4a`;try{let i=`-i "${e}"`;const u="-map 0:v",h=e.startsWith("generate:")?"-map 1:a":"-map 0:a";"generate:null"===e?i=`-f lavfi -i color=size=${t}x${s}:rate=${n}:color=black -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000`:"generate:test"===e&&(i=`-f lavfi -i testsrc=size=${t}x${s}:rate=${n} -pix_fmt yuv420p -f lavfi -i sine=frequency=220:beep_factor=4:sample_rate=48000`),await(0,r.runShellCommand)(`ffmpeg -loglevel warning -y -threads 0 ${i} -s ${t}:${s} -r ${n} -ss ${c} -t ${l} -shortest -af apad ${u} ${o} ${h} -ar 48000 ${d}`+(b?` ${u} -c:v libx264 -crf 10 -f mp4 -movflags faststart ${b} ${h} -c:a aac -ar 48000 -b:a 192k -f mp4 -movflags faststart ${y}`:"")),await a.promises.rename(o,g),await a.promises.rename(d,m),b&&await a.promises.rename(b,v),y&&await a.promises.rename(y,w)}catch(e){throw i.error(`Error converting video: ${e.stack}`),a.promises.unlink(o).catch((e=>i.debug(e.message))),a.promises.unlink(d).catch((e=>i.debug(e.message))),b&&a.promises.unlink(b).catch((e=>i.debug(e.message))),e}}return{video:g,audio:m,mp4:v,m4a:w}};const a=s(9896),r=s(6185),i=(0,r.logger)("webrtcperf:media"),o="https://github.com/vpalmisano/webrtcperf/releases/download/v2.0.4/video.mp4"},3106:e=>{e.exports=require("zlib")},3200:e=>{e.exports=require("child_process")},3840:e=>{e.exports=require("marked")},3940:e=>{e.exports=require("debug-level")},4040:e=>{e.exports=require("fast-stats")},4066:e=>{e.exports=require("sprintf-js")},4412:e=>{e.exports=require("pidusage/lib/ps")},4622:e=>{e.exports=require("prom-client")},4908:e=>{e.exports=require("toml")},4950:e=>{e.exports=require("convict")},5072:e=>{e.exports=require("chalk-template")},5086:e=>{e.exports=require("ws")},5692:e=>{e.exports=require("https")},5727:e=>{e.exports=require("skia-canvas")},5812:function(e,t,s){e=s.nmd(e);var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.parseVideo=h,t.prepareVideo=async function({vmafPrepareVideo:e,vmafVideoCrop:t,videoWidth:s,videoHeight:a,videoFramerate:n,videoDuration:l},p=!0){const[f,g]=e.split(","),m=o.default.join(o.default.dirname(f),`${g}_send.mp4`);if(r.default.existsSync(m))throw new Error(`Output file ${m} already exists`);const{width:v,height:w,frameRate:b}=await h(f);d.info(`prepareVideo ${f} ${v}x${w}@${b} -> ${m} ${t&&`crop: ${t}`}`);const y=Math.round((a||w)/18),S=Math.round(1.2*y),P=t?$(i.default.parse(t),0,","):"";await(0,c.runShellCommand)(`ffmpeg -hide_banner -loglevel warning -threads ${Math.min(u,16)} ${l?`-t ${l}`:""} -stream_loop -1 -i ${f} -filter_complex "[0:v]scale=w=${s||v}:h=${a||w},fps=${n||b},${P}drawbox=x=0:y=0:w=iw:h=${S}:color=black:t=fill,drawtext=fontfile=/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf:text='${g||0}-%{eif\\:t*1000\\:u}':fontcolor=white:fontsize=${y}:x=(w-text_w)/2:y=(${S}-text_h)/2[out]" -map [out] -fps_mode vfr -c:v libx264 -crf 10 -an -f mp4 -movflags +faststart ${m}`,!0),p||await r.default.promises.unlink(f)},t.convertToIvf=p,t.recognizeFrames=f,t.fixIvfFrames=m,t.fixIvfFiles=v,t.runVmaf=b,t.calculateVmafScore=R;const r=a(s(9896)),i=a(s(5865)),o=a(s(6928)),n=a(s(857)),c=s(6185),l=s(1707),d=(0,c.logger)("webrtcperf:vmaf"),u=n.default.cpus().length;async function h(e){let t=0,s=0,a=0;return await(0,c.ffprobe)(e,"video","frame=pts,width,height,duration_time","",(e=>{const r=parseInt(e.width),i=parseInt(e.height);r>t&&(t=r),i>s&&(s=i);const o=parseFloat(e.duration_time);return o&&(a=Math.max(Math.round(1/o),a)),c.FFProbeProcess.Skip})),{width:t,height:s,frameRate:a}}async function p(e,t,s=!0,a=!1){const{width:o,height:n,frameRate:l}=await h(e),p=e.replace(/\.[^.]+$/,".ivf.raw");d.debug(`convertToIvf ${e} ${o}x${n}@${l} -> ${p} crop:`,t);const f=t?`-vf '${$(i.default.parse(t))}'`:"";await(0,c.runShellCommand)(`ffmpeg -y -hide_banner -y -loglevel warning -i ${e} -map 0:v -c:v vp8 -quality best -cpu-used 0 -crf 1 -b:v 20M -qmin 1 -qmax 10 -g 1 -threads ${Math.min(u,16)} ${f} -an -f ivf ${p}`,!0),s||await r.default.promises.unlink(e),await m(p,!1,a)}async function f(e,t=!1,s=!1){const{width:a,height:r,frameRate:i}=await h(e),n=o.default.basename(e),l=new Map;let u=0,p=0,f=0,g=0,m=0,v="";const w=/(?<name>[0-9]{1,6})-(?<time>[0-9]{1,13})/;if(await(0,c.ffprobe)(e,"video","frame=pts,frame_tags=lavfi.ocr.text,lavfi.ocr.confidence","scale=w=1280:h=-1:flags=bicubic,crop=w=min(iw\\,ih):h=max((ih/15)\\,32):x=(iw-ow)/2:y=0:exact=1,ocr=whitelist=0123456789-",(e=>{const a=parseInt(e.pts);if(l.has(a)&&l.get(a)||!i)u++;else{const r=parseFloat(e.tag_lavfi_ocr_confidence?.trim()||"0"),o=w.exec(e.tag_lavfi_ocr_text?.trim()||"");if(r>0&&o){const{name:e,time:t}=o.groups;v=`Participant-${e.padStart(6,"0")}`;const c=parseInt(t),u=Math.round(i*c/1e3);s&&d.debug(`recognized frame ${n} confidence=${r} pts=${a} name=${e} time=${t} recognized=${u}`),l.set(a,u),g||(g=u/i),m=u/i}else t&&l.set(a,0),p++}return c.FFProbeProcess.Skip})),t){const e=Array.from(l.keys()).sort(((e,t)=>e-t));for(const[t,s]of e.entries()){if(!l.get(s)&&t){const a=l.get(e[t-1]);a?(l.set(s,a+s-e[t-1]),f++):l.delete(s)}}}return d.info(`recognizeFrames ${n} ${a}x${r}@${i} "${v}" frames: ${l.size} skipped: ${u} recovered: ${f} failed: ${p} ts: ${g.toFixed(2)}-${m.toFixed(2)} (${(m-g).toFixed(2)})`),{width:a,height:r,frameRate:i,frames:l,participantDisplayName:v}}async function g(e,t=!1){const{width:s,height:a}=await h(e),i=o.default.basename(e),n=await r.default.promises.open(e,"r"),c=new ArrayBuffer(32),l=new DataView(c);if(32!==(await n.read(l,0,32,0)).bytesRead)throw await n.close(),new Error("Invalid IVF file");const u=l.getUint32(16,!0)/l.getUint32(20,!0);let p="",g=0;const m=new DataView(new ArrayBuffer(12));let v=0,w=32,b=0;const y=new Map;let S=0,$=0;do{if(b=(await n.read(m,0,m.byteLength,w)).bytesRead,12!==b)break;const e=m.getUint32(0,!0),t=Number(m.getBigUint64(4,!0));y.has(t)?(d.debug(`IVF file ${i}: pts ${t} already present, skipping`),g++):(y.set(t,{pts:t,index:v,position:w,size:e+12}),v++,S||(S=t/u),$=t/u),w+=e+12}while(12===b);if(await n.close(),d.debug(`parseIvf ${i}: ${s}x${a}@${u} frames: ${y.size} skipped: ${g} ts: ${S.toFixed(2)}-${$.toFixed(2)} (${($-S).toFixed(2)}s)`),t){const{frames:t,participantDisplayName:s}=await f(e);p=s;for(const[e,s]of y.entries()){const a=t.get(e);a&&(s.recognizedPts=a)}}return{width:s,height:a,frameRate:u,frames:y,participantDisplayName:p}}async function m(e,t=!0,s=!1){const a=o.default.basename(e);d.debug(`fixIvfFrames ${a} keepSourceFile=${t} skipDuplicated=${s}`);const i=o.default.dirname(e);if(!a.endsWith(".ivf.raw"))throw new Error(`fixIvfFrames ${a}: invalid file extension, expected ".ivf.raw"`);const{width:n,height:c,frames:l,participantDisplayName:u}=await g(e,!0);if(!u)throw new Error(`fixIvfFrames ${a}: no participant name found`);if(!l.size)throw new Error(`fixIvfFrames ${a}: no frames found`);d.debug(`fixIvfFrames ${a} width=${n} height=${c} (${l.size} frames)`);const h=await r.default.promises.open(e,"r"),p=o.default.basename(e).split("_");if(!p[1].startsWith("send")&&!p[1].startsWith("recv"))throw new Error(`fixIvfFrames ${a}: invalid file name, expected "<name>_send" or "<name>_recv"`);const f=o.default.join(i,p[1].startsWith("send")?`${u}.ivf`:`${u}_recv-by_${p[0]}.ivf`),m=await r.default.promises.open(f,"w"),v=new DataView(new ArrayBuffer(32));await h.read(v,0,v.byteLength,0);let w=32,b=0;const y=Array.from(l.keys()).filter((e=>l.get(e)?.recognizedPts)).sort(((e,t)=>e===t?(l.get(e)?.recognizedPts||0)-(l.get(t)?.recognizedPts||0):e-t));for(const[e,t]of y.entries()){const r=l.get(t);if(!r||!r.recognizedPts){d.warn(`fixIvfFrames ${a}: pts ${t} not found, skipping`);continue}const i=l.get(y[e-1]),o=l.get(y[e+1]);if(o?.recognizedPts&&(o?.recognizedPts||0)<r.recognizedPts)continue;if(i?.recognizedPts&&r.recognizedPts===i.recognizedPts){if(d.debug(`${a}: duplicate recognized frame pts=${t}:${r.recognizedPts} prev=${i.pts}:${i.recognizedPts} next=${o?.pts}:${o?.recognizedPts} (${s?"skipped":"fixed"})`),s)continue;r.recognizedPts+=r.pts-i.pts}const n=new DataView(new ArrayBuffer(r.size));await h.read(n,0,r.size,r.position),n.setBigUint64(4,BigInt(r.recognizedPts),!0),await m.write(new Uint8Array(n.buffer),0,n.byteLength,w),w+=n.byteLength,b++}return v.setUint16(12,n,!0),v.setUint16(14,c,!0),v.setUint32(24,b,!0),await m.write(new Uint8Array(v.buffer),0,v.byteLength,0),await h.close(),await m.close(),t||await r.default.promises.unlink(e),{participantDisplayName:u,outFilePath:f}}async function v(e,t=!0,s=!1){d.debug(`fixIvfFiles ${e} keepSourceFiles=${t} skipDuplicated=${s}`);const a=new Map,r=new Map,i=(e,t)=>{t.includes("_recv-by_")?(r.has(e)||r.set(e,[]),r.get(e)?.push(t)):a.set(e,t)},n=await(0,c.getFiles)(e,".ivf");if(n.length){d.debug(`using existing ${n.length} ivf files`);for(const e of n)try{i(o.default.basename(e).replace(".ivf","").split("_")[0],e)}catch(e){d.error(`fixIvfFrames error: ${e.stack}`)}}const l=await(0,c.getFiles)(e,".ivf.raw");if(l.length){d.debug(`processing ${l.length} raw ivf files`);const e=await(0,c.chunkedPromiseAll)(l,(async e=>{try{const{participantDisplayName:a,outFilePath:r}=await m(e,t,s);return{participantDisplayName:a,outFilePath:r}}catch(e){d.error(`fixIvfFrames error: ${e.stack}`)}}),Math.ceil(u/4));for(const t of e){if(!t)continue;const{participantDisplayName:e,outFilePath:s}=t;i(e,s)}}return{reference:a,degraded:r}}async function w(e,t){const s=e.replace(".ivf",".filtered.ivf"),a=await r.default.promises.open(e,"r"),i=await r.default.promises.open(s,"w"),o=new DataView(new ArrayBuffer(32));await a.read(o,0,o.byteLength,0);let n=32,c=0;for(const e of t.values()){const t=new DataView(new ArrayBuffer(e.size));await a.read(t,0,e.size,e.position),await i.write(new Uint8Array(t.buffer),0,t.byteLength,n),n+=t.byteLength,c++}return o.setUint32(24,c,!0),await i.write(new Uint8Array(o.buffer),0,o.byteLength,0),await a.close(),await i.close(),s}async function b(e,t,s,a={},i=!1){const n=o.default.dirname(t),l=o.default.basename(t.replace(/\.[^.]+$/,"")),h=a[l],p={ref:S(h?.ref),deg:S(h?.deg)};d.info("runVmaf",{referencePath:e,degradedPath:t,preview:s,crop:p}),await r.default.promises.mkdir(o.default.join(n,l),{recursive:!0});const f=o.default.join(n,l,"vmaf-log.json"),m=o.default.join(n,l,"psnr.log"),v=o.default.join(n,l,"comparison.mp4"),b=o.default.basename(e).replace(".ivf",""),R=o.default.basename(t).replace(".ivf","").split("_recv-by_")[1],{width:T,height:k,frameRate:E,frames:x}=await g(e,!1),{width:C,height:_,frameRate:A,frames:F}=await g(t,!1),I=i?"(ih/15)":"";if(I&&(p.ref.h=`${p.ref.h}-${I}`,p.ref.y=`${p.ref.y}+${I}`,p.deg.h=`${p.deg.h}-${I}`,p.deg.y=`${p.deg.y}+${I}`),E!==A)throw new Error(`runVmaf: frame rates do not match: ref=${E} deg=${A}`);const D=[],L=[];let O=0,M=0;for(const[e,t]of x.entries()){const s=F.get(e);s&&(D.push(t),L.push(s),O||(O=e),M=e)}const B=(M-O)/E;e=await w(e,D),t=await w(t,L),d.debug(`common frames: ${D.length} ref: ${x.size} deg: ${F.size} duration: ${B}s`,{crop:p});const U=`ffmpeg -hide_banner -loglevel warning -y -threads ${Math.min(u,16)} -i ${t} -i ${e} `;let N=0;const j=C/_;T/k>j&&(N=Math.round(j*k));const H=`[0:v]scale=w=-1:h=${k}:flags=bicubic,${N?`crop=w=${N}:x=(iw-${N})/2,`:""}${$(p.deg,0,",")}${P(["deg_vmaf","deg_psnr",s?"deg_preview":""])};[1:v]scale=w=-1:h=${k}:flags=bicubic,${$(p.ref,0,",")}${P(["ref_vmaf","ref_psnr",s?"ref_preview":""])};[deg_vmaf][ref_vmaf]libvmaf=model='path=/usr/share/model/vmaf_v0.6.1.json':log_fmt=json:log_path=${f}:n_subsample=1:n_threads=${u}:shortest=1[vmaf];[deg_psnr][ref_psnr]psnr=stats_file=${m}[psnr]`,q=s?`${U} -filter_complex "${H};[ref_preview][deg_preview]hstack[stacked]" -map [vmaf] -f null - -map [psnr] -f null - -map [stacked] -fps_mode vfr -c:v libx264 -crf 10 -f mp4 -movflags +faststart ${v} `:`${U} -filter_complex "${H}" -map [vmaf] -f null - -map [psnr] -f null - `;d.debug("runVmaf",q);try{const{stdout:e,stderr:t}=await(0,c.runShellCommand)(q),s=JSON.parse(await r.default.promises.readFile(f,"utf-8"));d.debug("runVmaf",{stdout:e,stderr:t});const a={sender:b,receiver:R,...s.pooled_metrics.vmaf};d.info(`VMAF metrics ${f}:`,a);try{await y(f)}catch(e){d.error(`writeGraph error: ${e.stack}`)}return a}finally{await r.default.promises.unlink(t),await r.default.promises.unlink(e)}}async function y(e){const{CategoryScale:t,Chart:a,LinearScale:i,LineController:n,LineElement:c,PointElement:d,Legend:u,Title:h}=s(1021),{Canvas:p}=s(5727),f=JSON.parse(await r.default.promises.readFile(e,"utf-8")),{min:g,max:m,mean:v}=f.pooled_metrics.vmaf,w=e.replace(".json",".png"),b=Math.ceil(f.frames.length/500),y=new l.FastStats,S=f.frames.reduce(((e,t)=>(t.frameNum%b==0?e.push({x:t.frameNum,y:t.metrics.vmaf,count:1}):(e[e.length-1].y+=t.metrics.vmaf,e[e.length-1].count++),y.push(t.metrics.vmaf),e)),[]).map((e=>({x:e.x,y:e.y/e.count})));a.register([t,n,c,i,d,u,h]);const $=new p(1280,720),P=new a($,{type:"line",data:{labels:S.map((e=>e.x)),datasets:[{label:`VMAF score (min: ${g.toFixed(2)}, max: ${m.toFixed(2)}, mean: ${v.toFixed(2)}, P5: ${y.percentile(5).toFixed(2)})`,data:S.map((e=>e.y)),fill:!1,borderColor:"rgb(0, 0, 0)",borderWidth:1,pointRadius:0}]},options:{plugins:{title:{display:!0,text:o.default.basename(o.default.dirname(e)).replace(/_/g," ")}},scales:{y:{min:0,max:100}}}});await $.saveAs(w,{format:"png",matte:"white"}),P.destroy()}const S=e=>({w:e?.w??"iw",h:e?.h??"ih",x:e?.x??"0",y:e?.y??"0"}),$=(e,t=0,s="")=>{const{w:a,h:r,x:i,y:o}=e;return i||a||i||o?`crop=w=${a}:h=${r}:x=${i}:y=${o}:exact=${t}${s}`:""},P=(e,t="")=>{const s=e.filter((e=>!!e)).map((e=>`[${e}]`)).join("");return s?`split=${e.length}${s}${t}`:""};async function R(e){const{vmafPath:t,vmafPreview:s,vmafKeepIntermediateFiles:a,vmafKeepSourceFiles:n,vmafCrop:c,vmafSkipDuplicated:l}=e;if(!r.default.existsSync(e.vmafPath))throw new Error(`VMAF path ${e.vmafPath} does not exist`);d.debug(`calculateVmafScore referencePath=${t}`);const{reference:u,degraded:h}=await v(t,n,l),p=c?i.default.parse(c):void 0,f=[];for(const e of u.keys()){const t=u.get(e);if(t){for(const i of h.get(e)??[])try{const e=await b(t,i,s,p);f.push(e)}catch(e){d.error(`runVmaf error: ${e.message}`)}finally{a||await r.default.promises.unlink(i)}a||await r.default.promises.unlink(t)}}return await r.default.promises.writeFile(o.default.join(t,"vmaf.json"),JSON.stringify(f,void 0,2)),f}s.c[s.s]===e&&(async()=>{switch(process.argv[2]){case"convert":await p(process.argv[3],process.argv[4],!1);break;case"parse":{const{frames:e}=await g(process.argv[3],!0);console.log(e);break}case"fix":await m(process.argv[3],!0);break;case"analyze":console.log(JSON.stringify(await(0,c.analyzeColors)(process.argv[3]),null,2));break;case"graph":await y(process.argv[3]);break;case"vmaf":await R({vmafPath:process.argv[3],vmafPreview:!0,vmafKeepIntermediateFiles:!0,vmafKeepSourceFiles:!0,vmafCrop:i.default.stringify({"Participant-000001_recv-by_Participant-000000":{ref:{w:"",h:"",x:"",y:""},deg:{w:"",h:"",x:"",y:""}}})});break;default:throw new Error(`Invalid command: ${process.argv[2]}`)}})().catch((e=>console.error(e))).finally((()=>process.exit(0)))},5865:e=>{e.exports=require("json5")},6185:function(e,t,s){var a,r=this&&this.__createBinding||(Object.create?function(e,t,s,a){void 0===a&&(a=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,a,r)}:function(e,t,s,a){void 0===a&&(a=s),e[a]=t[s]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||(a=function(e){return a=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},a(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s=a(e),o=0;o<s.length;o++)"default"!==s[o]&&r(t,e,s[o]);return i(t,e),t}),n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.FFProbeProcess=t.PeerConnectionExternal=t.Scheduler=t.LoggerInterface=t.Log=void 0,t.logger=T,t.resolvePackagePath=function(e){if("__nexe"in process)return k.debug("resolvePackagePath (nexe)",e),e;{const t=S.default.normalize(S.default.join(S.default.dirname(__filename),e));return k.debug("resolvePackagePath (webpack)",t),t}},t.sha256=function(e){return(0,u.createHash)("sha256").update(e).digest("hex")},t.getProcessStats=async function(e=0,t=!1){const s=e||process.pid;let a=E.get(s);if(a)return a;const r=await(0,P.default)(s);a=r?{cpu:r.cpu,memory:r.memory/1e6}:{cpu:0,memory:0};if(t)try{let e=x.get(s);if(e?.length||(e=await(0,$.default)(s),e?.length&&x.set(s,e)),e?.length){const t=await(0,P.default)(e);for(const s of e)t[s]&&(a.cpu+=t[s].cpu,a.memory+=t[s].memory/1e6)}}catch(e){k.error(`getProcessStats children error: ${e.stack}`)}return E.set(s,a),a},t.getSocketStats=async function(e){const t={recvBytes:0,sendBytes:0};try{const{stdout:s}=await G(`ss -nOHpti | { grep pid=${e} || true; }`);for(const{groups:e}of s.matchAll(/bytes_sent:(?<sendBytes>\d+).+bytes_received:(?<recvBytes>\d+)/g)){if(!e)continue;const s=parseInt(e.recvBytes),a=parseInt(e.sendBytes);t.recvBytes+=s,t.sendBytes+=a}}catch(e){k.error(`socketStats error: ${e.stack}`)}return t},t.getSystemStats=function(){A||F();return C.get("default")},t.startUpdateSystemStats=F,t.stopTimers=I,t.sleep=D,t.startRandomActivateAudio=function(e,t,s,a){if(O)return;O=!0,M(e,t,s,a)},t.stopRandomActivateAudio=function(){O=!1,L&&clearTimeout(L)},t.randomActivateAudio=M,t.downloadUrl=async function(e,t,s,a,r=6e4){k.debug(`downloadUrl url=${e} ${s}`);const i=t?.split(":");let o=null;s&&(await f.default.promises.mkdir((0,S.dirname)(s),{recursive:!0}),o=(0,f.createWriteStream)(s));const n=await(0,d.default)({method:"get",url:e,auth:i?{username:i[0],password:i[1]}:void 0,headers:a?{Range:`bytes=${a}`}:void 0,timeout:r,onDownloadProgress:t=>{k.debug(`downloadUrl fileUrl=${e} progress=${t.progress||t.bytes}`)},httpsAgent:new g.Agent({rejectUnauthorized:!1}),responseType:o?"stream":"text"});if(o)return new Promise(((e,t)=>{if(!o)return;n.data.pipe(o);let s=null;o.once("error",(e=>{s=e,o&&o.close(),t(e)})),o.once("close",(()=>{s||e()}))}));{const e=n.headers["content-type"];let t=0,s=0,a=0;if(n.headers["content-range"]){const r=n.headers["content-range"].split("/");k.debug(`downloadUrl ${n.data.length} bytes, contentType=${e}, contentRange=${r}`);const i=r[0].split("-");a=parseInt(r[1]),2===i.length?(t=parseInt(i[0]),s=parseInt(i[1])):r[0].startsWith("-")?s=parseInt(i[0]):r[0].endsWith("-")&&(t=parseInt(i[0]),s=a)}return{data:n.data,start:t,end:s,total:a,contentType:e}}},t.uploadUrl=async function(e,t,s){k.debug(`uploadUrl ${e} to ${t}`);const a=s?.split(":"),r=new p.default;r.append("file",f.default.createReadStream(e));return(await(0,d.default)({method:"post",url:t,auth:a?{username:a[0],password:a[1]}:void 0,headers:r.getHeaders(),timeout:36e5,httpsAgent:new g.Agent({rejectUnauthorized:!1}),responseType:"text",data:r})).data},t.hideAuth=function(e){if(!e)return e;return e.replace(B,"$1")},t.registerExitHandler=function(e){U.add(e)},t.unregisterExitHandler=function(e){U.delete(e)},t.runExitHandlersNow=H,t.checkChromeExecutable=W,t.clampMinMax=V,t.runShellCommand=G,t.resolveIP=async function(e,t=36e5){if(!e)return"";if("private"===m.parse(e).range())return e;const s=Date.now(),a=z.get(e);if(!a||s-a.timestamp>t){return await Promise.race([D(1e3),h.promises.reverse(e).then((a=>a.length?(k.debug(`resolveIP ${e} -> ${a.join(", ")}`),z.set(e,{host:a[0],timestamp:s+10*t}),a[0]):(z.set(e,{host:e,timestamp:s}),e))).catch((t=>{k.debug(`resolveIP error: ${t.stack}`),z.set(e,{host:e,timestamp:s})}))])||e}return a?.host||e},t.stripColors=function(e){return e.replace(/\x1B[[(?);]{0,2}(;?\d)*./g,"")},t.systemGpuStats=Q,t.toTitleCase=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},t.getFiles=async function e(t,s){const a=await f.default.promises.readdir(t,{withFileTypes:!0}),r=await Promise.all(a.map((a=>{const r=S.default.resolve(t,a.name);return a.isDirectory()?e(r,s):r})));return Array.prototype.concat(...r).filter((e=>e.endsWith(s)))},t.toPrecision=function(e,t=3){return(Math.round(e*10**t)/10**t).toFixed(t)},t.getDefaultNetworkInterface=X,t.checkNetworkInterface=async function(e){await G(`ip route | grep -q "dev ${e}"`)},t.portForwarder=async function(e,t){t||(t=await X());const s=new AbortController;return Object.entries((0,y.networkInterfaces)()).forEach((([a,r])=>{if(("0.0.0.0"===t||a===t)&&r)for(const t of r){if(t.internal||"127.0.0.1"===t.address||"IPv4"!==t.family)continue;const r=`portForwarder on ${a} (${t.address}:${e})`,i=v.default.createServer((t=>{const s=v.default.createConnection({host:"127.0.0.1",port:e});t.once("error",(e=>{k.error(`${r} error: ${e.stack}`),s.destroy()})),s.once("error",(e=>{k.error(`${r} error: ${e.stack}`),t.destroy()})),t.pipe(s),s.pipe(t)})).listen({port:e,host:t.address,signal:s.signal});i.on("listening",(()=>{k.debug(`${r} listening`)})),i.once("error",(e=>{k.error(`${r} error: ${e.stack}`)}))}})),()=>{k.debug(`portForwarder on port ${e} stop`),s.abort()}},t.pageScreenshot=async function(e,t,s=1920,a=1024,r="body",i,o){k.debug(`pageScreenshot ${e} -> ${t}`),await f.default.promises.mkdir(S.default.dirname(t),{recursive:!0});let n=process.env.CHROMIUM_PATH;n&&f.default.existsSync(n)||(n=await W());const c=await R.default.launch({headless:!0,executablePath:n,defaultViewport:{width:s,height:a,deviceScaleFactor:1,isMobile:!1,hasTouch:!1,isLandscape:!1},args:["--no-sandbox","--disable-setuid-sandbox"]}),l=await c.newPage();i&&await l.setExtraHTTPHeaders(i);o&&await l.evaluateOnNewDocument((e=>{document.addEventListener("DOMContentLoaded",(()=>{const t=document.createElement("style");t.setAttribute("id","webrtcperf-extra-style"),t.setAttribute("type","text/css"),t.innerHTML=e,document.head.appendChild(t)}))}),o);await l.goto(e,{waitUntil:["domcontentloaded","networkidle0"],timeout:6e4});try{const e=await l.waitForSelector(r,{visible:!0,timeout:15e3});if(!e)throw new Error(`pageScreenshot selector "${r}" not found`);const s=t;await e.screenshot({path:s})}catch(e){k.error(`pageScreenshot error: ${e.message}`)}finally{await l.close(),await c.close()}},t.enabledForSession=Z,t.increaseKey=function(e,t,s){if(void 0===s||!isFinite(s))return;void 0===e[t]&&(e[t]=0);e[t]+=s},t.chunkedPromiseAll=async function(e,t,s=1){const a=Array(e.length);for(let r=0;r<e.length;r+=s)await Promise.allSettled(e.slice(r,r+s).map((async(e,s)=>{const i=await t(e,r+s);void 0!==i&&(a[r+s]=i)})));return a},t.maybeNumber=function(e){const t=parseFloat(e);return isNaN(t)?e:t},t.ffprobe=te,t.buildIvfHeader=function(e=1920,t=1080,s=30,a="MJPG"){const r=Buffer.alloc(32);return r.write("DKIF",0,"utf8"),r.writeUint16LE(0,4),r.writeUint16LE(32,6),r.write(a,8,"utf8"),r.writeUint16LE(e,12),r.writeUint16LE(t,14),r.writeUint32LE(s,16),r.writeUint32LE(1,20),r.writeUint32LE(0,24),r.writeUint32LE(0,28),r},t.ffmpeg=async function(e="video",t){const a=1e4+Math.floor(1e4*Math.random()),r=`exec ffmpeg -hide_banner -loglevel warning ${e} zmq:tcp://127.0.0.1:${a}`;k.debug(`${r}`);let i="";const o=new(s(6584).Subscriber),n=(0,l.spawn)(r,{shell:!0,stdio:["ignore","ignore","pipe"]});n.stderr.on("data",(e=>{i+=e})),n.once("error",(e=>{throw o.close(),e})),n.once("close",(e=>{if(o.close(),0!==e)throw new Error(`${r} failed with code ${e}: ${i}`)})),o.connect(`tcp://127.0.0.1:${a}`),o.subscribe("");for await(const[e]of o)await t(e);o.close()},t.analyzeColors=async function(e){let t=0,s=0,a=0,r=0,i=0,o=0;return await te(e,"video","frame=lavfi.signalstats.YAVG,lavfi.signalstats.UAVG,lavfi.signalstats.VAVG,lavfi.signalstats.SATAVG,lavfi.signalstats.HUEAVG","signalstats",(e=>(t+=parseFloat(e.tag_lavfi_signalstats_YAVG),s+=parseFloat(e.tag_lavfi_signalstats_UAVG),a+=parseFloat(e.tag_lavfi_signalstats_VAVG),r+=parseFloat(e.tag_lavfi_signalstats_SATAVG),i+=parseFloat(e.tag_lavfi_signalstats_HUEAVG),o++,ee.Skip))),{YAvg:t/o,UAvg:s/o,VAvg:a/o,SatAvg:r/o,HueAvg:i/o}},t.waitStopProcess=async function(e,t=5e3){k.debug(`waitStopProcess pid: ${e} timeout: ${t}`);const s=Date.now();for(;Date.now()-s<t;)try{process.kill(e,0),await D(Math.max(t/10,200))}catch{return!0}k.warn(`waitStopProcess pid: ${e} timeout`);try{process.kill(e,"SIGKILL")}catch{return!0}return!1},t.getDockerLogsPath=async function(){const e=await f.default.promises.readFile(`${y.default.homedir()}/.webrtcperf/docker.id`,"utf-8"),t=`/var/lib/docker/containers/${e}/${e}-json.log`;if(!f.default.existsSync(t))throw new Error(`docker logs path ${t} not found`);return t};const c=s(1046),l=s(3200),d=n(s(8938)),u=s(6982),h=o(s(2250)),p=n(s(2305)),f=o(s(9896)),g=s(5692),m=o(s(6247)),v=n(s(9278)),w=n(s(2011)),b=o(s(1845)),y=o(s(857)),S=o(s(6928)),$=n(s(981)),P=n(s(7648)),R=n(s(8034));s(4412);function T(e,s={}){return new t.Log(e,{splitLine:!1,...s})}t.Log=s(3940).Log;t.LoggerInterface=class{name;logInit(e){this.name&&e.unshift(`[${this.name}]`)}debug(...e){this.logInit(e),k.debug(...e)}info(...e){this.logInit(e),k.info(...e)}warn(...e){this.logInit(e),k.warn(...e)}error(...e){this.logInit(e),k.error(...e)}log(...e){this.logInit(e),k.log(...e)}};const k=T("webrtcperf:utils");const E=new w.default({stdTTL:5,checkperiod:10}),x=new w.default({stdTTL:15,checkperiod:15});const C=new w.default({stdTTL:30,checkperiod:60});async function _(){const[e,t,s]=await Promise.all([b.cpu.free(1e4),b.mem.info(),Q()]),a={usedCpu:100-e,usedMemory:100-t.freeMemPercentage,usedGpu:s.gpu,usedGpuMemory:s.mem};C.set("default",a)}let A=null;function F(){A||(A=setInterval(_,5e3))}function I(){A&&(clearInterval(A),A=null)}function D(e){return new Promise((t=>setTimeout((()=>t()),e)))}let L=null,O=!1;async function M(e,t,s,a){if(t&&O)try{let t=[];for(const s of e.values()){const e=[...s.pages.values()];Z(s.id,a)&&(t=t.concat(e))}for(const[e,s]of t.entries()){if(!s)continue;let a=0;try{a=await s.evaluate((()=>webrtcperf.getActiveAudioTracks().length))}catch(e){k.error(`randomActivateAudio error: ${e.stack}`)}a||(t[e]=null)}const r=t.filter((e=>!!e)),i=Math.floor(Math.random()*r.length),o=Math.round(100*Math.random())<=s;for(const[e,t]of r.entries())try{e===i?(k.debug(`Changing audio in page ${e+1}/${r.length} (enable: ${o})`),await t.evaluate((async e=>{"undefined"!=typeof publisherSetMuted?await publisherSetMuted(!e):webrtcperf.getActiveAudioTracks().forEach((t=>{t.enabled=e}))}),o)):await t.evaluate((async()=>{"undefined"!=typeof publisherSetMuted?await publisherSetMuted(!0):webrtcperf.getActiveAudioTracks().forEach((e=>{e.enabled=!1}))}))}catch(t){k.error(`randomActivateAudio in page ${e+1}/${r.length} error: ${t.stack}`)}}catch(e){k.error(`randomActivateAudio error: ${e.stack}`)}finally{if(O){const r=t*(1+Math.random());L&&clearTimeout(L),L=setTimeout(M,1e3*r,e,t,s,a)}}}const B=new RegExp("(http[s]{0,1}://)(.+?:.+?@)","g");const U=new Set;const N=async e=>{let t=0;for(const s of U.values()){const a=`${t+1}/${U.size}`;k.debug(`running exitHandler ${a}`);try{await s(e),k.debug(` exitHandler ${a} done`)}catch(e){k.error(`exitHandler ${a} error: ${e}`)}t++}U.clear()};let j=null;async function H(e){j||(j=N(e)),await j,I()}const q=["beforeExit","uncaughtException","unhandledRejection","SIGHUP","SIGINT","SIGQUIT","SIGILL","SIGTRAP","SIGABRT","SIGBUS","SIGFPE","SIGUSR1","SIGSEGV","SIGUSR2","SIGTERM"];async function W(){const{loadConfig:e}=s(7028),t=(await e())[0],a=S.default.join(y.default.homedir(),".webrtcperf/chrome"),r=e=>e.split(".").slice(0,3).join("."),i=(await(0,c.getInstalledBrowsers)({cacheDir:a})).map((e=>r(e.buildId))),o=c.Browser.CHROME;i.sort((0,c.getVersionComparator)(o)),k.debug(`Available chrome versions: ${i}`);const n=t.chromiumVersion;if(!n)throw new Error("Chromium version not set");if(!i.includes(r(n))){k.info(`Downloading chrome ${n}...`);let e=0;await(0,c.install)({browser:o,buildId:n,cacheDir:a,downloadProgressCallback:(t,s)=>{const a=Math.round(100*t/s);a-e>1&&(e=a,k.info(` ${e}%`))}}),k.info(`Downloading chrome ${n} done.`)}return(0,c.computeExecutablePath)({browser:o,cacheDir:a,buildId:n})}function V(e,t,s){return Math.max(Math.min(e,s),t)}async function G(e,t=!1,s=1048576,{provideStdin:a=!1,returnStdout:r=!0,returnStderr:i=!0}={}){return t&&k.debug(`runShellCommand cmd: ${e}`),new Promise(((o,n)=>{const c=(0,l.spawn)(e,{shell:!0,stdio:[a?"inherit":"ignore",r?"pipe":"inherit",i?"pipe":"inherit"],detached:!0});let d="",u="";r&&c.stdout?.on("data",(e=>{s&&d.length>s&&(d=d.slice(e.length)),d+=e})),i&&c.stderr?.on("data",(e=>{s&&u.length>s&&(u=u.slice(e.length)),u+=e})),c.once("error",(e=>n(e))),c.once("close",(s=>{0!==s?n(new Error(`runShellCommand cmd: ${e} failed with code ${s}: ${u}`)):(t&&k.debug(`runShellCommand cmd: ${e} done`,{stdout:d,stderr:u}),o({stdout:d,stderr:u}))}))}))}process.setMaxListeners(process.getMaxListeners()+q.length),q.forEach((e=>process.once(e,(async e=>{e instanceof Error?k.error(`Exit on error: ${e.stack||e.message}`):k.debug(`Exit on signal: ${e}`),await H(e),process.exit(0)}))));const z=new Map;const J=f.default.existsSync("/usr/bin/nvidia-smi")&&f.default.existsSync("/dev/dri"),K="darwin"===process.platform&&f.default.existsSync("/usr/sbin/ioreg");async function Q(){try{if(J){const{stdout:e}=await G("nvidia-smi --query-gpu=utilization.gpu,utilization.memory --format=csv"),t=e.split("\n")[1].trim(),[s,a]=t.split(",").map((e=>parseFloat(e.replace(" %",""))));return{gpu:s,mem:a}}if(K){const{stdout:e}=await G('ioreg -r -d 1 -w 0 -c IOAccelerator | grep PerformanceStatistics\\"'),t=JSON.parse(e.trim().split(" = ")[1].replace(/=/g,":"));return{gpu:t["Device Utilization %"]||t["GPU Activity(%)"]||0,mem:0}}}catch(e){k.debug(`systemGpuStats error: ${e.stack}`)}return{gpu:0,mem:0}}t.Scheduler=class{name;interval;callback;verbose;running=!1;last=0;errorSum=0;statsTimeoutId;constructor(e,t,s,a=!1){this.name=e,this.interval=1e3*t,this.callback=s,this.verbose=a,k.debug(`[${this.name}-scheduler] constructor interval=${this.interval}ms`)}start(){k.debug(`[${this.name}-scheduler] start`),this.running=!0,this.scheduleNext()}async stop(){k.debug(`[${this.name}-scheduler] stop`),this.running=!1,this.statsTimeoutId&&clearTimeout(this.statsTimeoutId);try{await this.callback(Date.now())}catch(e){k.error(`[${this.name}-scheduler] stop callback error: ${e.stack}`,e)}}scheduleNext(){if(!this.running)return;const e=Date.now();this.last&&(this.errorSum+=V(e-this.last-this.interval,-this.interval,this.interval),this.verbose&&k.debug(`[${this.name}-scheduler] last=${e-this.last}ms drift=${this.errorSum}ms`)),this.last=e,this.statsTimeoutId=setTimeout((async()=>{try{const e=Date.now();await this.callback(e);const t=Date.now()-e;t>this.interval?k.warn(`[${this.name}-scheduler] callback elapsed=${t}ms > ${this.interval}ms`):this.verbose&&k.debug(`[${this.name}-scheduler] callback elapsed=${t}ms`)}catch(e){k.error(`[${this.name}-scheduler] callback error: ${e.stack}`,e)}finally{this.scheduleNext()}}),this.interval-this.errorSum/2)}};class Y{id;process;static cache=new Map;constructor(e){this.process=(0,l.spawn)("sleep",["600"]),this.id=this.process.pid||-1,k.debug(`PeerConnectionExternal contructor: ${this.id}`,e),Y.cache.set(this.id,this),this.process.stdout.on("data",(e=>{k.debug(`PeerConnectionExternal stdout: ${e}`)})),this.process.stderr.on("data",(e=>{k.debug(`PeerConnectionExternal stderr: ${e}`)})),this.process.on("close",(e=>{k.debug(`PeerConnectionExternal process exited with code ${e}`),Y.cache.delete(this.id)}))}static get(e){return Y.cache.get(e)}async createOffer(e){return k.debug("PeerConnectionExternal createOffer",{options:e}),{}}setLocalDescription(e){k.debug("PeerConnectionExternal setLocalDescription",e)}setRemoteDescription(e){k.debug("PeerConnectionExternal setRemoteDescription",e)}}async function X(){const{stdout:e}=await G("ip route | awk '/default/ {print $5; exit}' | tr -d ''");return e.trim()}function Z(e,t){if(!0===t||"true"===t)return!0;if(!1===t||"false"===t||void 0===t)return!1;if("string"==typeof t){if(t.includes("-")){const[s,a]=t.split("-").map((e=>parseInt(e)));return!(isFinite(s)&&e<s)&&!(isFinite(a)&&e>a)}return t.split(",").map((e=>e.trim())).filter((e=>e.length)).map((e=>parseInt(e))).includes(e)}return e===t}var ee;async function te(e,t="video",s="",a="",r){const i=`exec ffprobe -loglevel error ${"video"===t?"-select_streams v":""} -show_frames -print_format compact ${s?`-show_entries ${s}`:""} -f lavfi -i '${"video"===t?"":"a"}movie=${e}${a?`,${a}`:""}'`,o=[];let n="",c=!1;return new Promise(((e,t)=>{const s=(0,l.spawn)(i,{shell:!0,stdio:["ignore","pipe","pipe"]});s.stdout.on("data",(e=>{if(c)return;const t=e.toString().split("|").reduce(((e,t)=>{const[s,a]=t.split("=");return a&&!s.startsWith("side_datum")&&(e[s.replace(/[:.]/g,"_")]=a),e}),{});if(r){const e=r(t);e===ee.Skip||(e===ee.Stop?(c=!0,s.kill("SIGINT")):o.push(e))}else o.push(t)})),s.stderr.on("data",(e=>{n+=e})),s.once("error",(e=>t(e))),s.once("close",(s=>{0===s||c?e(o):t(new Error(`${i} failed with code ${s}: ${n}`))}))}))}t.PeerConnectionExternal=Y,function(e){e.Skip="skip",e.Stop="stop"}(ee||(t.FFProbeProcess=ee={}))},6247:e=>{e.exports=require("ipaddr.js")},6259:e=>{e.exports=require("marked-terminal")},6261:e=>{e.exports=require("events")},6584:e=>{e.exports=require("zeromq")},6928:e=>{e.exports=require("path")},6958:function(e,t,s){var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.runWithDocker=async function(e){const t=new i.default,s=e.filter((e=>"--docker"!==e))[0];if(!s)throw new Error("No configuration file specified");const a=r.default.basename(s),d=(await(0,n.loadConfig)(s))[0],u=Date.now(),h=r.default.resolve(r.default.dirname(s),"logs",`${u}`);await c.default.promises.mkdir(h,{recursive:!0});const p=[`${r.default.resolve(s)}:/config/${a}:ro`,"/dev/shm:/dev/shm",`${h}:/data`,"/tmp/webrtcperf-cache:/root/.webrtcperf"];if(d.scriptPath){const e=r.default.basename(d.scriptPath);p.push(`${r.default.resolve(d.scriptPath)}:/scripts/${e}:ro`)}process.env.DEBUG_SRC&&p.push(`${(0,o.resolvePackagePath)("app.min.js")}:/app/app.min.js:ro`);const f={},g={};if(d.debuggingPort)for(let e=0;e<d.sessions;e++){const t=`${d.debuggingPort+e}/tcp`;f[t]=[{HostPort:`${d.debuggingPort+e}`}],g[t]={}}const m=[`DEBUG_LEVEL=${process.env.DEBUG_LEVEL||"info"}`,"SHOW_PAGE_LOG=false","SHOW_STATS=false","SERVER_PORT=5000","SERVER_USE_HTTPS=true","SERVER_DATA=/data",`START_TIMESTAMP=${u}`,"STATS_PATH=/data/stats.csv","PAGE_LOG_PATH=/data/page.log","DETAILED_STATS_PATH=/data/detailed-stats.csv"];if(d.scriptPath){const e=r.default.basename(d.scriptPath);m.push(`SCRIPT_PATH=/scripts/${e}`)}d.debuggingPort&&m.push(`DEBUGGING_PORT=${d.debuggingPort}`);d.prometheusPushgateway.startsWith("http://localhost")&&m.push("PROMETHEUS_PUSHGATEWAY=http://pushgateway:9091");const v={Image:"ghcr.io/vpalmisano/webrtcperf:devel",name:"webrtcperf",Cmd:[`/config/${a}`],HostConfig:{Binds:p,PortBindings:f,CapAdd:["NET_ADMIN"],NetworkMode:d.prometheusPushgateway.startsWith("http://localhost")?"prometheus-stack_default":"bridge"},Env:m,AttachStdin:!0,AttachStdout:!0,AttachStderr:!0,Tty:!0,OpenStdin:!0,StdinOnce:!0,ExposedPorts:g};try{process.env.DEBUG_SRC||(l.info("Pulling latest webrtcperf image..."),await t.pull("ghcr.io/vpalmisano/webrtcperf:devel"));try{const e=await t.getContainer("webrtcperf");await e.remove({force:!0})}catch(e){}const e=await t.createContainer(v);await e.start();const s=await e.attach({stream:!0,stdin:!0,stdout:!0,stderr:!0});process.stdin.pipe(s),s.pipe(process.stdout),await new Promise((t=>{e.wait(((e,s)=>{e&&l.error("Error waiting for container:",s,e.stack),t(s)}))})),await e.remove()}catch(e){throw l.error("Docker operation failed:",e),e}};const r=a(s(6928)),i=a(s(1410)),o=s(6185),n=s(7028),c=a(s(9896)),l=(0,o.logger)("webrtcperf:docker")},6976:function(e,t,s){var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Session=void 0;const r=s(7191),i=a(s(2613)),o=a(s(8938)),n=a(s(6261)),c=a(s(9896)),l=a(s(5865)),d=s(9026),u=a(s(2011)),h=a(s(857)),p=a(s(6928)),f=a(s(8034)),g=s(8219),m=s(3106),v=s(7564),w=s(1707),b=s(6185),{default:y}=s(5072),S=s(9993),$=(0,b.logger)("webrtcperf:session"),P={error:"red",warn:"yellow",info:"cyan",log:"grey",debug:"white",requestfailed:"magenta"};class R extends n.default{chromiumUrl;chromiumPath;chromiumFieldTrials;windowWidth;windowHeight;deviceScaleFactor;display;mediaPath;videoWidth;videoHeight;videoFramerate;useFakeMedia;enableGpu;enableBrowserLogging;startTimestamp;sessions;tabsPerSession;spawnPeriod;statsInterval;disabledVideoCodecs;localStorage;sessionStorage;clearCookies;scriptPath;showPageLog;pageLogFilter;pageLogPath;userAgent;evaluateAfter;exposedFunctions;scriptParams;blockedUrls;extraHeaders;responseModifiers={};downloadResponses=[];extraCSS;cookies=[];overridePermissions=[];hardwareConcurrency;debuggingPort;debuggingAddress;randomAudioPeriod;maxVideoDecoders;maxVideoDecodersRange;incognito;serverPort;serverSecret;serverUseHttps;emulateCpuThrottling;running=!1;browser;context;stopPortForwarder;id;throttleIndex;useBrowserThrottling;url;urlQuery;customUrlHandler;customUrlHandlerFn;stats={};pages=new Map;httpResourcesStats=new Map;pagesMetrics=new Map;pageWarnings=0;pageErrors=0;screensharePage;static jsonFetchCache=new u.default({stdTTL:30,checkperiod:15});constructor({chromiumUrl:e,chromiumPath:t,chromiumFieldTrials:s,windowWidth:a,windowHeight:r,deviceScaleFactor:o,display:n,url:c,urlQuery:d,customUrlHandler:u,customUrlHandlerFn:h,mediaPath:p,videoWidth:f,videoHeight:m,videoFramerate:v,useFakeMedia:w,enableGpu:y,enableBrowserLogging:S,startTimestamp:P,sessions:R,tabsPerSession:T,spawnPeriod:k,statsInterval:E,disabledVideoCodecs:x,localStorage:C,sessionStorage:_,clearCookies:A,scriptPath:F,showPageLog:I,pageLogFilter:D,pageLogPath:L,userAgent:O,id:M,throttleIndex:B,useBrowserThrottling:U,evaluateAfter:N,exposedFunctions:j,scriptParams:H,blockedUrls:q,extraHeaders:W,responseModifiers:V,downloadResponses:G,extraCSS:z,cookies:J,overridePermissions:K,hardwareConcurrency:Q,debuggingPort:Y,debuggingAddress:X,randomAudioPeriod:Z,maxVideoDecoders:ee,maxVideoDecodersRange:te,incognito:se,serverPort:ae,serverSecret:re,serverUseHttps:ie,emulateCpuThrottling:oe}){if(super(),$.debug("constructor",{id:M}),this.id=M,this.chromiumUrl=e,this.chromiumPath=t||void 0,this.chromiumFieldTrials=s||void 0,this.windowWidth=a||1920,this.windowHeight=r||1080,this.deviceScaleFactor=o||1,this.debuggingPort=Y||0,this.debuggingAddress=X||"",this.display=n,this.url=c,this.urlQuery=d,!this.urlQuery&&c.includes("?")){const e=c.split("?",2);this.url=e[0],this.urlQuery=e[1]}if(this.customUrlHandler=u,this.customUrlHandlerFn=h,this.mediaPath=p,this.videoWidth=f,this.videoHeight=m,this.videoFramerate=v,this.useFakeMedia=w,this.enableGpu=y,this.enableBrowserLogging=(0,b.enabledForSession)(this.id,S),this.startTimestamp=P||Date.now(),this.sessions=R||1,this.tabsPerSession=T||1,(0,i.default)(this.tabsPerSession>=1,"tabsPerSession should be >= 1"),this.spawnPeriod=k||1e3,this.statsInterval=E||10,this.disabledVideoCodecs=x?x.split(",").map((e=>e.trim())).filter((e=>e.length)):[],C)try{this.localStorage=l.default.parse(C)}catch(e){$.error(`error parsing localStorage: ${e.stack}`),this.localStorage=null}if(_)try{this.sessionStorage=l.default.parse(_)}catch(e){$.error(`error parsing sessionStorage: ${e.stack}`),this.sessionStorage=null}if(this.clearCookies=A,this.scriptPath=F,this.showPageLog=I,this.pageLogFilter=D,this.pageLogPath=L,this.userAgent=O,this.randomAudioPeriod=Z,this.maxVideoDecoders=ee,this.maxVideoDecodersRange=te,this.incognito=se,this.serverPort=ae,this.serverSecret=re,this.serverUseHttps=ie,this.emulateCpuThrottling=oe,this.throttleIndex=B,this.useBrowserThrottling=U,this.evaluateAfter=N||[],this.exposedFunctions=j||{},H)try{this.scriptParams=l.default.parse(H)}catch(e){throw $.error(`error parsing scriptParams '${H}': ${e.stack}`),e}else this.scriptParams={};if(this.blockedUrls=(q||"").split(",").map((e=>e.trim())).filter((e=>e.length)),this.blockedUrls.push("ingest.sentry.io"),W)try{this.extraHeaders=l.default.parse(W)}catch(e){$.error(`error parsing extraHeaders: ${e.stack}`),this.extraHeaders=void 0}else this.extraHeaders=void 0;if(V)try{const e=l.default.parse(V);Object.entries(e).forEach((([e,t])=>{if(!Array.isArray(t))throw new Error(`responseModifiers replacements should be an array of { search, replace, body, headers } objects: ${t}`);this.responseModifiers[e]=t.map((({search:e,regexp:t,replace:s,file:a,headers:r})=>({search:t?new RegExp(t,"g"):e,replace:s,file:a,headers:r})))}))}catch(e){throw new Error(`error parsing responseModifiers "${V}": ${e.stack}`)}if(G)try{const e=l.default.parse(G);if(!Array.isArray(e))throw new Error(`downloadResponses should be an array: ${G}`);e.forEach((({urlPattern:e,output:t,append:s})=>{this.downloadResponses.push({urlPattern:(0,g.getUrlPatternRegExp)(e),output:t,append:s})}))}catch(e){throw new Error(`error parsing downloadResponses "${G}": ${e.stack}`)}if(this.extraCSS=z,J)try{this.cookies=l.default.parse(J)}catch(e){$.error(`error parsing cookies: ${e.stack}`)}K&&(this.overridePermissions=K.split(",").map((e=>e.trim())).filter((e=>e.length))),this.hardwareConcurrency=Q}getBrowserArgs(e){const t=["--no-sandbox","--no-zygote","--ignore-certificate-errors","--no-user-gesture-required","--autoplay-policy=no-user-gesture-required","--disable-infobars","--no-default-browser-check","--allow-running-insecure-content",`--unsafely-treat-insecure-origin-as-secure=http://${new URL(this.url||"http://localhost").host}`,"--disable-web-security","--disable-features=IsolateOrigins,Translate,CalculateNativeWinOcclusion","--disable-background-timer-throttling","--disable-backgrounding-occluded-windows","--disable-renderer-backgrounding","--disable-site-isolation-trials","--enable-usermedia-screen-capturing","--allow-http-screen-capture",`--remote-debugging-port=${this.debuggingPort?this.debuggingPort+this.id:0}`,"--enable-features=VaapiVideoDecoder,VaapiVideoEncoder,VaapiVideoDecodeLinuxGL,ElementCapture",`--window-size=${this.windowWidth},${this.windowHeight}`];let s=this.chromiumFieldTrials||"";if(this.enableBrowserLogging&&this.pageLogPath){const a=p.default.dirname(this.pageLogPath),r=p.default.resolve(a,`webrtc-event-logging-${this.id}`);c.default.mkdirSync(r,{recursive:!0}),t.push("--enable-logging","--vmodule=*/webrtc/*=5","--v=0",`--webrtc-event-logging=${r}`),s="WebRTC-RtcEventLogNewFormat/Disabled/"+s,e.CHROME_LOG_FILE=p.default.resolve(a,`chrome-${this.id}.log`)}return-1!==this.maxVideoDecoders&&(0,b.enabledForSession)(this.id,this.maxVideoDecodersRange)&&(s=`WebRTC-MaxVideoDecoders/${this.maxVideoDecoders}/`+s),s.length&&t.push(`--force-fieldtrials=${s}`),this.mediaPath&&(this.useFakeMedia?($.debug(`${this.id} using chromium as fake media source`),t.push("--use-fake-ui-for-media-stream","--use-fake-device-for-media-stream=display-media-type=browser,fps=30",`--use-file-for-fake-video-capture=${this.mediaPath.video}`,`--use-file-for-fake-audio-capture=${this.mediaPath.audio}`)):($.debug(`${this.id} using ${this.mediaPath} as fake media source`),t.push("--auto-accept-camera-and-microphone-capture","--auto-select-tab-capture-source-by-title=webrtcperf-screenshare","--mute-audio"))),this.enableGpu?(t.push("--ignore-gpu-blocklist","--enable-gpu-rasterization","--enable-zero-copy","--disable-gpu-sandbox","--enable-vulkan"),"egl"===this.enableGpu?t.push("--use-gl=egl"):t.push("--use-gl=angle","--use-angle=vulkan")):t.push("--disable-3d-apis","--disable-site-isolation-trials"),t}async start(){if(!this.running)if(this.running=!0,this.browser)$.warn(`${this.id} start: already running`);else{if($.debug(`${this.id} start`),this.chromiumUrl)try{this.browser=await f.default.connect({browserURL:this.chromiumUrl,defaultViewport:{width:this.windowWidth,height:this.windowHeight,deviceScaleFactor:this.deviceScaleFactor,isMobile:!1,hasTouch:!1,isLandscape:!0}})}catch(e){return $.error(`${this.id} browser connect error: ${e.stack}`),this.stop()}else{let e=this.chromiumPath;e&&c.default.existsSync(e)||(e=await(0,b.checkChromeExecutable)(),$.debug(`using executablePath=${e}`)),this.throttleIndex>-1&&"linux"===h.default.platform()&&(e=await(0,r.throttleLauncher)(e,this.throttleIndex));const t={...process.env};this.display?t.DISPLAY=this.display:delete t.DISPLAY;const s=this.getBrowserArgs(t),a=["--disable-dev-shm-usage","--remote-debugging-port","--enable-automation","--window-size"];$.debug(`[session ${this.id}] Using args:\n ${s.join("\n ")}`),$.debug(`[session ${this.id}] Default args:\n ${f.default.defaultArgs().join("\n ")}`);try{this.browser=await f.default.launch({browser:"chrome",headless:!this.display,executablePath:e,handleSIGINT:!1,env:t,defaultViewport:{width:this.windowWidth,height:this.windowHeight,deviceScaleFactor:this.deviceScaleFactor,isMobile:!1,hasTouch:!1,isLandscape:!1},ignoreDefaultArgs:a,args:s});const r=await this.browser.version();$.debug(`[session ${this.id}] Using chrome version: ${r}`)}catch(e){return $.error(`[session ${this.id}] Browser launch error: ${e.stack}`),this.stop()}}(0,i.default)(this.browser,"BrowserNotCreated"),this.debuggingPort&&"127.0.0.1"!==this.debuggingAddress&&(this.stopPortForwarder=await(0,b.portForwarder)(this.debuggingPort+this.id,this.debuggingAddress)),this.browser.once("disconnected",(()=>($.debug("browser disconnected"),this.stop())));for(let e=0;e<this.tabsPerSession;e++)this.openPage(e).catch((e=>$.error(`openPage error: ${e.stack}`))),e<this.tabsPerSession-1&&await(0,b.sleep)(this.spawnPeriod)}}setupPageCmd(e,t,s){let a=`webrtcperf = {};\nwebrtcperf.config = {\n START_TIMESTAMP: ${this.startTimestamp},\n WEBRTC_PERF_URL: "${(0,b.hideAuth)(s)}",\n WEBRTC_PERF_SESSION: ${this.id},\n WEBRTC_PERF_TAB_INDEX: ${t},\n WEBRTC_PERF_INDEX: ${e},\n STATS_INTERVAL: ${this.statsInterval},\n VIDEO_WIDTH: ${this.videoWidth},\n VIDEO_HEIGHT: ${this.videoHeight},\n VIDEO_FRAMERATE: ${this.videoFramerate},\n};\ntry {\n webrtcperf.params = JSON.parse('${JSON.stringify(this.scriptParams)}' || '{}');\n} catch (err) {\n console.error('[webrtcperf] Error parsing scriptParams:', err);\n webrtcperf.params = {};\n};\n `;return this.serverPort&&(a+=`webrtcperf.config.SAVE_MEDIA_URL = "ws${this.serverUseHttps?"s":""}://localhost:${this.serverPort}/?auth=${this.serverSecret}&action=write-stream";\n `,this.mediaPath?.mp4&&!this.useFakeMedia&&(a+=`webrtcperf.config.VIDEO_URL = "http${this.serverUseHttps?"s":""}://localhost:${this.serverPort}/cache/${p.default.basename(this.mediaPath.mp4)}?auth=${this.serverSecret}";\nwebrtcperf.config.AUDIO_URL = "http${this.serverUseHttps?"s":""}://localhost:${this.serverPort}/cache/${p.default.basename(this.mediaPath.m4a)}?auth=${this.serverSecret}";\n `)),this.disabledVideoCodecs.length&&($.debug("Using disabledVideoCodecs:",this.disabledVideoCodecs),a+=`webrtcperf.config.GET_CAPABILITIES_DISABLED_VIDEO_CODECS = JSON.parse('${JSON.stringify(this.disabledVideoCodecs)}');\n`),a}async openPage(e){if(!this.browser)return;const t=this.id+e;let s,a=this.url;if(!a){if(this.customUrlHandler&&!this.customUrlHandlerFn){const e=p.default.resolve(process.cwd(),this.customUrlHandler);if(!c.default.existsSync(e))throw new Error(`Custom url handler script not found: "${e}"`);this.customUrlHandlerFn=(await import(e)).default}if(!this.customUrlHandlerFn)throw new Error("Custom url handler function not set");a=await this.customUrlHandlerFn({id:this.id,sessions:this.sessions,tabIndex:e,tabsPerSession:this.tabsPerSession,index:t,pid:process.pid,env:{...process.env},params:this.scriptParams}),$.debug(`customUrlHandlerFn: ${a}`)}if(!a)throw new Error("Page URL not set");this.urlQuery&&(a+=`?${this.urlQuery.replace(/\$s/g,String(this.id)).replace(/\$S/g,String(this.sessions)).replace(/\$t/g,String(e)).replace(/\$T/g,String(this.tabsPerSession)).replace(/\$i/g,String(t)).replace(/\$p/g,String(process.pid))}`),$.debug(`opening page ${t} (session: ${this.id} tab: ${e}): ${(0,b.hideAuth)(a)}`),this.incognito?this.context=await this.browser.createBrowserContext():this.context=this.browser.defaultBrowserContext(),this.overridePermissions.length&&await this.context.overridePermissions(new URL(a).origin,this.overridePermissions);const i=await this.getNewPage(e),n=i._client();await i.setBypassCSP(!0),this.userAgent&&await i.setUserAgent(this.userAgent),await Promise.all(Object.keys(this.exposedFunctions).map((async e=>await i.exposeFunction(e,((...t)=>this.exposedFunctions[e](...t))))));let l=this.setupPageCmd(t,e,a);if(this.localStorage&&($.debug("Using localStorage:",this.localStorage),Object.entries(this.localStorage).map((([e,t])=>{l+=`window.localStorage.setItem('${e}', ${JSON.stringify(t)});\n`}))),this.sessionStorage&&($.debug("Using sessionStorage:",this.sessionStorage),Object.entries(this.sessionStorage).map((([e,t])=>{l+=`window.sessionStorage.setItem('${e}', ${JSON.stringify(t)});\n`}))),l+=`\nObject.defineProperty(window.screen, 'width', { value: ${this.windowWidth}, writable: false });\nObject.defineProperty(window.screen, 'height', { value: ${this.windowHeight}, writable: false });\nObject.defineProperty(window.screen, 'availWidth', { value: ${this.windowWidth}, writable: false });\nObject.defineProperty(window.screen, 'availHeight', { value: ${this.windowHeight}, writable: false });\nObject.defineProperty(window.screen.orientation, 'type', { value: 'landscape-primary', writable: false });\n `,$.debug("init command:",l),await i.evaluateOnNewDocument(l),this.clearCookies)try{await n.send("Network.clearBrowserCookies")}catch(e){$.error(`clearCookies error: ${e.stack}`)}i.on("dialog",(async e=>{$.debug(`page ${t+1} dialog ${e.type()}: ${e.message()}`);try{await e.accept()}catch(e){$.debug(`dialog accept error: ${e.message}`)}try{await e.dismiss()}catch(e){$.debug(`dialog dismiss error: ${e.message}`)}})),i.once("close",(()=>{$.debug(`page ${t+1} closed`),this.pages.delete(t),this.httpResourcesStats.delete(t),this.pagesMetrics.delete(t),s&&(s.close().catch((e=>{$.error(`saveFile close error: ${e.stack}`)})),s=void 0),this.browser&&this.running&&setTimeout((()=>this.openPage(t).catch((e=>$.error(`openPage after close error: ${e.stack}`)))),1e3)}));let u=!0;await n.send("Network.setBypassServiceWorker",{bypass:!0});const f=new g.RequestInterceptionManager(n,{onError:e=>{$.error("Request interception error:",e)}}),v=[];this.blockedUrls.forEach((e=>{v.push({urlPattern:e,modifyRequest:()=>({errorReason:"BlockedByClient"})})})),this.extraHeaders&&Object.entries(this.extraHeaders).forEach((([e,t])=>{const s=Object.entries(t).map((([e,t])=>({name:e,value:t})));v.push({urlPattern:e,modifyRequest:({event:e})=>($.debug(`adding extraHeaders in: ${e.request.url}`,s),{headers:s})})})),Object.entries(this.responseModifiers).forEach((([e,t])=>{v.push({urlPattern:e,modifyResponse:async({event:e,body:s})=>{const a=e.responseHeaders||[];for(const{search:r,replace:i,file:o,headers:n}of t)if(r&&i?($.debug(`using responseModifiers in: ${e.request.url}: ${r.toString()} => ${i}`),s=s?.replace(r,i)):o&&($.debug(`using responseModifiers in: ${e.request.url}: ${o}`),s=await c.default.promises.readFile(o,"utf8")),n)for(const[e,t]of Object.entries(n))a.push({name:e,value:t});return{body:s,responseHeaders:a}}})})),await f.intercept(...v),this.downloadResponses.length&&i.on("response",(async e=>{if(!e.ok())return;const t=e.url();for(const{urlPattern:s,output:a,append:r}of this.downloadResponses)if(s.test(t))try{const s=await e.buffer();if(s.byteLength>0)if(r){const e=a.replaceAll("${id}",this.id.toString());c.default.existsSync(p.default.dirname(e))||await c.default.promises.mkdir(p.default.dirname(a),{recursive:!0}),$.debug(`appending response body ${s.byteLength} to: ${e}`),await c.default.promises.appendFile(e,s)}else{c.default.existsSync(a)||await c.default.promises.mkdir(a,{recursive:!0});const e=p.default.join(a,`${p.default.basename(new URL(t).pathname)}`);$.debug(`saving response body ${s.byteLength} to: ${e}`),await c.default.promises.writeFile(e,s)}}catch(e){$.error(`downloadResponses error: ${e.stack}`)}}));if(await i.exposeFunction("setRequestInterception",(async e=>{if(e!==u){$.debug(`setRequestInterception to ${e}`);try{e?await f.enable():await f.disable(),u=e}catch(e){$.error(`setRequestInterception error: ${e.stack}`)}}})),await i.exposeFunction("jsonFetch",(async(e,t="",s=0)=>{if(t){const e=R.jsonFetchCache.get(t);if(e)return e}try{e.validStatuses&&(e.validateStatus=t=>e.validStatuses.includes(t));const{status:a,data:r,headers:i}=await(0,o.default)(e);if("stream"===e.responseType){if(e.downloadPath&&!c.default.existsSync(e.downloadPath)){$.debug(`jsonFetch saving file to: ${e.downloadPath}`,i["content-disposition"]),await c.default.promises.mkdir(p.default.dirname(e.downloadPath),{recursive:!0});const t=c.default.createWriteStream(e.downloadPath);await new Promise(((e,s)=>{t.on("error",(e=>s(e))),t.on("close",(()=>e())),r.pipe(t)}))}return t&&R.jsonFetchCache.set(t,{status:a},s),{status:a,headers:i}}return t&&R.jsonFetchCache.set(t,{status:a,data:r},s),{status:a,headers:i,data:r}}catch(e){const t=e.message;return $.warn(`jsonFetch error: ${t}`),{status:500,error:t}}})),await i.exposeFunction("readLocalFile",((e,t)=>(e=p.default.resolve(process.cwd(),e),c.default.promises.readFile(e,t)))),this.pageLogPath){const e=p.default.dirname(this.pageLogPath);await i.exposeFunction("webrtcperf_writeFile",((t,s,a=!1)=>{const r=p.default.resolve(e,t);return a?c.default.promises.appendFile(r,s):c.default.promises.writeFile(r,s)}))}await i.exposeFunction("webrtcperf_emulateCpuThrottling",(e=>i.emulateCPUThrottling(e))),this.emulateCpuThrottling&&($.debug(`emulateCpuThrottling: ${this.emulateCpuThrottling}`),await i.emulateCPUThrottling(this.emulateCpuThrottling)),await i.exposeFunction("keypressText",(async(e,t,s=20)=>{await i.type(e,t,{delay:s})})),await i.exposeFunction("mouseClick",(async(e,t=0,s=0)=>{await i.click(e,{offset:{x:t,y:s}})}));const w=new d.LoremIpsum({sentencesPerParagraph:{max:4,min:1},wordsPerSentence:{max:16,min:2}});if(await i.exposeFunction("loremIpsum",((e=1)=>w.generateSentences(e))),await i.exposeFunction("keypressRandomText",(async(e,t=1,s="",a="",r=0)=>{const o=s+w.generateSentences(t)+a,n=await i.frames();for(const t of n){const s=await t.$(e);s&&(await s.focus(),await t.type(e,o,{delay:r}))}})),await i.exposeFunction("uploadFileFromUrl",(async(e,t)=>{const s=(0,b.sha256)(e)+"."+e.split(".").slice(-1)[0],a=p.default.join(h.default.homedir(),".webrtcperf/uploads",s);c.default.existsSync(a)||await(0,b.downloadUrl)(e,void 0,a),$.debug(`uploadFileFromUrl: ${a}`);const r=await i.frames();for(const e of r){const s=await e.$(t);if(s){await s.uploadFile(a);break}}})),this.extraCSS){$.debug(`Add extraCSS: ${this.extraCSS}`);try{await i.evaluateOnNewDocument((e=>{document.addEventListener("DOMContentLoaded",(()=>{const t=document.createElement("style");t.setAttribute("id","webrtcperf-extra-style"),t.setAttribute("type","text/css"),t.innerHTML=e,document.head.appendChild(t)}))}),this.extraCSS.replace(/important/g,"!important"))}catch(e){$.error(`Add extraCSS error: ${e.stack}`)}}if(this.cookies)try{await i.setCookie(...this.cookies)}catch(e){$.error(`Set cookies error: ${e.stack}`)}if(this.pageLogPath)try{await c.default.promises.mkdir(p.default.dirname(this.pageLogPath),{recursive:!0}),s=await c.default.promises.open(this.pageLogPath,"a")}catch(e){$.error(`error opening page log file: ${this.pageLogPath}: ${e.stack}`)}if(await i.exposeFunction("webrtcperf_serializedConsoleLog",(async(e,a)=>{if(this.showPageLog||s)try{await this.onPageMessage(t,e,a,s)}catch(e){$.error(`serializedConsoleLog error: ${e.stack}`)}})),(this.showPageLog||s)&&(i.on("pageerror",(async e=>{const a=`pageerror: ${e?.message?.message||e?.message||e} - ${e?.message?.stack||e?.stack}`;await this.onPageMessage(t,"error",a,s)})),i.on("requestfailed",(async e=>{const a=(e.failure()?.errorText||"").trim();if("net::ERR_ABORTED"===a)return;const r=`${e.method()} ${e.url()}: ${a}`;await this.onPageMessage(t,"requestfailed",r,s)}))),await i.exposeFunction("webrtcperf_startFakeScreenshare",(async()=>{if(!this.browser)return;let s=i;this.useFakeMedia||this.screensharePage||(s=this.screensharePage=await this.browser.newPage(),await this.screensharePage.evaluateOnNewDocument(this.setupPageCmd(t,e,"about:blank")),await this.screensharePage.evaluateOnNewDocument(c.default.readFileSync((0,b.resolvePackagePath)("node_modules/@vpalmisano/webrtcperf-js/dist/webrtcperf.js"),"utf8")),await s.exposeFunction("webrtcperf_keypressText",(async(e,t,a=20)=>{await s.type(e,t,{delay:a})})),await s.exposeFunction("webrtcperf_keyPress",(async e=>{await s.keyboard.press(e)})),await s.goto(`http${this.serverUseHttps?"s":""}://localhost:${this.serverPort}/empty-page?auth=${this.serverSecret}&title=webrtcperf-screenshare`)),await s.evaluate((()=>webrtcperf.startFakeScreenshare()))})),await i.exposeFunction("webrtcperf_stopFakeScreenshare",(async()=>{!this.useFakeMedia&&this.screensharePage?(await this.screensharePage.close(),this.screensharePage=void 0):await i.evaluate((()=>webrtcperf.stopFakeScreenshare()))})),await i.exposeFunction("webrtcperf_reload",(()=>i.reload())),this.setupPageNetworkStats(n,t),this.hardwareConcurrency){const e=S({hardwareConcurrency:this.hardwareConcurrency});await e.onPageCreated(i)}this.throttleIndex>-1&&("linux"!==process.platform||this.useBrowserThrottling)&&($.debug("Using internal network throttling"),await n.send("Network.emulateNetworkConditions",{offline:!1,uploadThroughput:125e5,downloadThroughput:125e5,latency:0,packetLoss:0,packetQueueLength:0}));{const e=(0,b.resolvePackagePath)("node_modules/@vpalmisano/webrtcperf-js/dist/webrtcperf.js");if(!c.default.existsSync(e))throw new Error(`@vpalmisano/webrtcperf-js script not found: ${e}`);$.debug(`loading @vpalmisano/webrtcperf-js script from: ${e}`),await i.evaluateOnNewDocument(c.default.readFileSync(e,"utf8"))}if(this.scriptPath)if(this.scriptPath.startsWith("base64:gzip:")){const e=Buffer.from(this.scriptPath.replace("base64:gzip:",""),"base64"),t=(0,m.gunzipSync)(e).toString();$.debug(`loading script from ${t.length} bytes`),await i.evaluateOnNewDocument(t)}else for(const e of this.scriptPath.split(","))if(e.trim())if(e.startsWith("http")){$.debug(`loading custom script from url: ${e}`);const t=await(0,b.downloadUrl)(e);if(!t?.data)throw new Error(`Failed to download script from: ${e}`);await i.evaluateOnNewDocument(t.data)}else{if(!c.default.existsSync(e)){$.warn(`custom script not found: ${e}`);continue}$.debug(`loading custom script from file: ${e}`),await i.evaluateOnNewDocument(c.default.readFileSync(e,"utf8"))}$.debug(`Page ${t+1} "${a}" loading`);const y=Date.now();try{await i.goto(a,{waitUntil:"domcontentloaded",timeout:6e4})}catch(e){return $.error(`Page ${t+1} "${a}" load error: ${e.stack}`),void await i.close()}if(this.pages.set(t,i),this.throttleIndex>-1&&("linux"!==process.platform||this.useBrowserThrottling)){const e=async()=>{if(!i.isClosed())try{await this.applyNetworkThrottling(n)}catch(e){$.error(`applyNetworkThrottling error: ${e.stack}`)}};await e(),r.throttleNotifier.on("change",e),i.once("close",(()=>r.throttleNotifier.off("change",e)))}$.debug(`Page ${t+1} "${a}" loaded in ${(Date.now()-y)/1e3}s`);for(let e=0;e<this.evaluateAfter.length;e++)await i.evaluate(this.evaluateAfter[e].pageFunction,...this.evaluateAfter[e].args)}setupPageNetworkStats(e,t){const s={sentBytes:0,recvBytes:0,recvLatency:new w.FastStats({store_data:!1}),wsSentBytes:0,wsRecvBytes:0,wsRecvLatency:new w.FastStats({store_data:!1})};this.httpResourcesStats.set(t,s);const a=new Map;e.on("Network.requestWillBeSent",(e=>{if(e.request.url.startsWith("data:"))return;const{requestId:t,request:r,timestamp:i}=e,o=r.postDataEntries?.reduce(((e,t)=>e+(t.bytes?.length||0)),0);o&&(s.sentBytes+=o),a.set(t,{url:r.url,timestamp:i})})),e.on("Network.responseReceived",(e=>{if(!a.get(e.requestId))return;const{response:t}=e;t.fromDiskCache?a.delete(e.requestId):s.recvBytes+=t.encodedDataLength})),e.on("Network.dataReceived",(e=>{a.get(e.requestId)&&(s.recvBytes+=e.encodedDataLength)})),e.on("Network.loadingFinished",(e=>{const t=a.get(e.requestId);if(!t)return;a.delete(e.requestId);const{timestamp:r}=e;s.recvLatency.push(r-t.timestamp)})),e.on("Network.webSocketCreated",(e=>{a.set(e.requestId,{url:e.url,timestamp:Date.now()})})),e.on("Network.webSocketHandshakeResponseReceived",(e=>{const t=a.get(e.requestId);t&&(a.delete(e.requestId),s.wsRecvLatency.push((Date.now()-t.timestamp)/1e3))})),e.on("Network.webSocketFrameSent",(e=>{s.wsSentBytes+=e.response.payloadData.length})),e.on("Network.webSocketFrameReceived",(e=>{s.wsRecvBytes+=e.response.payloadData.length}))}async applyNetworkThrottling(e){const t=(0,r.getSessionThrottleValues)(this.throttleIndex,"up"),s=(0,r.getSessionThrottleValues)(this.throttleIndex,"down"),a={offline:!1,uploadThroughput:t.rate||-1,downloadThroughput:s.rate||-1,latency:Math.max(t.delay||0,s.delay||0),packetLoss:Math.max(t.loss||0,s.loss||0),packetQueueLength:Math.max(t.queue||0,s.queue||0)};$.debug(`Apply internal network throttling: ${JSON.stringify(a)}`),await e.send("Network.emulateNetworkConditions",{...a,uploadThroughput:-1!==a.uploadThroughput?a.uploadThroughput/8:-1,downloadThroughput:-1!==a.downloadThroughput?a.downloadThroughput/8:-1})}async getNewPage(e){return $.debug(`getNewPage ${e}`),(0,i.default)(this.context,"NoBrowserContextCreated"),await this.context.newPage()}async onPageMessage(e,t,s,a){if(s.endsWith("net::ERR_BLOCKED_BY_CLIENT.Inspector"))return;if(this.blockedUrls.some((e=>("requestfailed"===t||-1!==s.search("FetchError"))&&-1!==s.search(e))))return;const r=P[t]||"grey",i=this.pageLogFilter?new RegExp(this.pageLogFilter,"ig"):null;if(!i||s.match(i)){const i=["error","warning"].includes(t),o=s.startsWith("[webrtcperf");a&&(!i&&!o&&s.length>1024&&(s=s.slice(0,1024)+`... +${s.length-1024} bytes`),await a.write(`${(new Date).toISOString()} [page ${e}] (${t}) ${s}\n`)),this.showPageLog&&(!i&&!o&&s.length>256&&(s=s.slice(0,256)+`... +${s.length-256} bytes`),console.log(y`{bold [page ${e}]} {${r} (${t}) ${s}}`)),"error"===t?this.pageErrors+=1:"warn"===t&&(this.pageWarnings+=1)}}async updateStats(){if(!this.browser)return this.stats={},this.stats;const e={};try{const t=await(0,b.getProcessStats)();Object.assign(e,{nodeCpu:t.cpu,nodeMemory:t.memory})}catch(e){$.error(`node getProcessStats error: ${e.stack}`)}try{const t=(0,b.getSystemStats)();t&&(e.usedCpu=t.usedCpu,e.usedMemory=t.usedMemory,e.usedGpu=t.usedGpu,e.usedCpu>80&&$.warn(`High system CPU usage: ${e.usedCpu.toFixed(2)}%`),e.usedMemory>80&&$.warn(`High system memory usage: ${e.usedMemory.toFixed(2)}%`))}catch(e){$.error(`node getSystemStats error: ${e.stack}`)}const t=this.browser.process();if(t)try{const s=await(0,b.getProcessStats)(t.pid,!0);Object.assign(e,s)}catch(e){$.error(`getProcessStats error: ${e.stack}`)}const s={},a={},i={},o={},n={},c={},l={},d={},u={},h={},p={},f={},g={},m={},w={},y={},S={},P={},R={},T={},k={},E={},x={},C={},_={},A={},F={},I={},D={},L={},O={},M={},B={},U={},N={},j={},H={},q={},W={},V={};return await Promise.allSettled([...this.pages.entries()].map((async([t,G])=>{try{const{peerConnectionStats:z,audioEndToEndDelay:J,videoEndToEndDelay:K,cpuPressure:Q,videoStats:Y,customMetrics:X}=await G.evaluate((async()=>({peerConnectionStats:await webrtcperf.collectPeerConnectionStats(),audioEndToEndDelay:webrtcperf.collectAudioEndToEndStats(),videoEndToEndDelay:webrtcperf.collectVideoEndToEndStats(),cpuPressure:webrtcperf.collectCpuPressure(),videoStats:webrtcperf.collectVideoStats(),customMetrics:"collectCustomMetrics"in window?collectCustomMetrics():null}))),{participantName:Z}=z,ee=this.httpResourcesStats.get(t);if(!z.signalingHost&&z.stats.length){const e=Object.values(z.stats[0]);e.length&&(z.signalingHost=await(0,b.resolveIP)(e[0].remoteAddress))}const{stats:te,activePeerConnections:se,signalingHost:ae}=z,re=(0,v.rtcStatKey)({hostName:ae,participantName:Z}),ie=(0,v.rtcStatKey)({pageIndex:t,hostName:ae,participantName:Z});(0,b.increaseKey)(s,re,1),(0,b.increaseKey)(a,ie,se),(0,b.increaseKey)(i,ie,z.peerConnectionConnectionTime),(0,b.increaseKey)(o,ie,z.peerConnectionDisconnectionTime),(0,b.increaseKey)(n,ie,z.peerConnectionsCreated),(0,b.increaseKey)(c,ie,z.peerConnectionsClosed),(0,b.increaseKey)(l,ie,z.peerConnectionsConnected),(0,b.increaseKey)(d,ie,z.peerConnectionsDisconnected),(0,b.increaseKey)(u,ie,z.peerConnectionsFailed),(0,b.increaseKey)(h,ie,z.peerConnectionsDelay),J&&(p[ie]=J.delay,f[ie]=J.startFrameDelay),K&&(g[ie]=K.videoDelay,w[ie]=K.videoStartFrameDelay,m[ie]=K.screenDelay,y[ie]=K.screenStartFrameDelay),ee&&(ee.sentBytes>0&&(S[ie]=ee.sentBytes),ee.recvBytes>0&&(P[ie]=ee.recvBytes),ee.recvLatency.length&&(R[ie]=ee.recvLatency.amean()),ee.wsSentBytes>0&&(T[ie]=ee.wsSentBytes),ee.wsRecvBytes>0&&(k[ie]=ee.wsRecvBytes),ee.wsRecvLatency.length&&(E[ie]=ee.wsRecvLatency.amean())),void 0!==Q&&(_[ie]=Q),Y&&(A[ie]=Y.width,F[ie]=Y.height,I[ie]=Y.bufferedTime,D[ie]=Y.playingTime,L[ie]=Y.bufferingTime,O[ie]=Y.bufferingEvents);for(const s of te)for(const[a,r]of Object.entries(s))try{(0,v.updateRtcStats)(e,t,a,r,ae,Z)}catch(e){$.error(`updateRtcStats error for ${a}: ${e.stack}`,e)}if(X)for(const[e,t]of Object.entries(X))V[e]||(V[e]={}),V[e][ie]=t;x[ie]=e.cpu/this.tabsPerSession,C[ie]=e.memory/this.tabsPerSession;const oe=(0,r.getSessionThrottleValues)(this.throttleIndex,"up");M[ie]=oe.rate||0,B[ie]=oe.delay||0,U[ie]=oe.loss||0,N[ie]=oe.queue||0;const ne=(0,r.getSessionThrottleValues)(this.throttleIndex,"down");j[ie]=ne.rate||0,H[ie]=ne.delay||0,q[ie]=ne.loss||0,W[ie]=ne.queue||0}catch(e){const s=e;s.message.includes("Execution context was destroyed, most likely because of a navigation.")?$.warn(`collectPeerConnectionStats for page ${t} error: ${s.message}`):$.error(`collectPeerConnectionStats for page ${t} error: ${s.stack}`)}}))),Object.assign(e,{pages:s,errors:this.pageErrors,warnings:this.pageWarnings,peerConnections:a,peerConnectionConnectionTime:i,peerConnectionDisconnectionTime:o,peerConnectionsConnected:l,peerConnectionsCreated:n,peerConnectionsClosed:c,peerConnectionsDisconnected:d,peerConnectionsFailed:u,peerConnectionsDelay:h,audioEndToEndDelay:p,audioStartFrameDelay:f,videoEndToEndDelay:g,videoStartFrameDelay:w,screenEndToEndDelay:m,screenStartFrameDelay:y,httpSentBytes:S,httpRecvBytes:P,httpRecvLatency:R,wsSentBytes:T,wsRecvBytes:k,wsRecvLatency:E,cpuPressure:_,videoWidth:A,videoHeight:F,videoBufferedTime:I,videoPlayingTime:D,videoBufferingTime:L,videoBufferingEvents:O,pageCpu:x,pageMemory:C,throttleUpRate:M,throttleUpDelay:B,throttleUpLoss:U,throttleUpQueue:N,throttleDownRate:j,throttleDownDelay:H,throttleDownLoss:q,throttleDownQueue:W,...V}),s.size<this.pages.size&&$.warn(`updateStats collected pages ${s.size} < ${this.pages.size}`),this.stats=e,this.stats}async stop(){if(this.running){if(this.running=!1,$.debug(`${this.id} stop`),this.stopPortForwarder&&this.stopPortForwarder(),this.browser){if($.debug(`${this.id} closing ${this.pages.size} pages`),await Promise.allSettled([...this.pages.values()].map((e=>e.close({runBeforeUnload:!0})))),this.pages.size>0){const e=Date.now(),t=1e3*this.pages.size;for(;this.pages.size>0&&Date.now()-e<t;)$.debug(`${this.id} waiting for ${this.pages.size} pages to close`),await(0,b.sleep)(200);this.pages.size>0&&$.warn(`${this.id} timeout closing ${this.pages.size} pages`)}if(this.screensharePage&&(await this.screensharePage.close(),this.screensharePage=void 0),this.browser.removeAllListeners(),this.chromiumUrl){$.debug(`${this.id} disconnect from browser`);try{await this.browser.disconnect()}catch(e){$.warn(`${this.id} browser disconnect error: ${e.message}`)}}else{const e=this.browser.process()?.pid;if(e){$.debug(`${this.id} closing browser (pid: ${e})`);try{await this.browser.close()}catch(e){$.error(`${this.id} browser close error: ${e.stack}`)}await(0,b.waitStopProcess)(e,5e3)}}this.pages.clear(),this.pagesMetrics.clear(),this.browser=void 0}this.emit("stop",this.id)}}async pageScreenshot(e=0,t="webp"){$.debug(`pageScreenshot ${this.id}-${e}`);const s=this.id+e,a=this.pages.get(s);if(!a)throw new Error(`Page ${s} not found`);const r=`/tmp/screenshot-${s}.${t}`;return await a.screenshot({path:r,fullPage:!0}),r}}t.Session=R},6982:e=>{e.exports=require("crypto")},6997:function(e,t,s){var a,r=this&&this.__createBinding||(Object.create?function(e,t,s,a){void 0===a&&(a=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,a,r)}:function(e,t,s,a){void 0===a&&(a=s),e[a]=t[s]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||(a=function(e){return a=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},a(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s=a(e),o=0;o<s.length;o++)"default"!==s[o]&&r(t,e,s[o]);return i(t,e),t}),n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Server=void 0;const c=n(s(7174)),l=s(6982),d=o(s(7252)),u=n(s(9896)),h=s(8611),p=s(5692),f=n(s(857)),g=n(s(6928)),m=n(s(2365)),v=s(5086),w=n(s(3106)),b=n(s(699)),y=s(7028),S=s(6976),$=s(6185),P=s(7191),R=s(2680),T=(0,$.logger)("webrtcperf:server");t.Server=class{serverPort;serverSecret;serverUseHttps;serverData;pageLogPath;videoCachePath;stats;app;server=null;wss=null;constructor({serverPort:e=5e3,serverSecret:t="secret",serverUseHttps:s=!1,serverData:a="",pageLogPath:r="",videoCachePath:i=""}={},o){this.serverPort=e,this.serverSecret=t,this.serverUseHttps=s,this.serverData=a,this.pageLogPath=r,this.videoCachePath=i,this.stats=o,this.app=(0,d.default)(),this.app.use((0,c.default)()),this.app.use((0,d.json)({limit:"10mb"})),this.app.use(((e,t,s)=>{if(e.query.auth===this.serverSecret)return s();const a=(0,b.default)(e);if(!a||"admin"!==a.name||a.pass!==this.serverSecret)return t.setHeader("WWW-Authenticate",'Basic realm="Restricted Area"'),void t.status(401).send("Unauthorized");s()})),this.app.get("/",((e,t)=>{t.send("")})),this.app.get("/stats",this.getStats.bind(this)),this.app.get("/collected-stats",this.getCollectedStats.bind(this)),this.app.get("/screenshot/:sessionId",this.getScreenshot.bind(this)),this.app.put("/collected-stats",this.putCollectedStats.bind(this)),this.app.put("/session",this.putSession.bind(this)),this.app.put("/sessions",this.putSessions.bind(this)),this.app.delete("/session",this.deleteSession.bind(this)),this.app.delete("/sessions",this.deleteSessions.bind(this)),this.app.get("/view/page.log",this.getPageLog.bind(this)),this.app.get("/view/docker.log",this.getDockerLog.bind(this)),this.app.get("/download/alert-rules",this.getAlertRules.bind(this)),this.app.get("/download/stats",this.getStatsFile.bind(this)),this.app.get("/download/detailed-stats",this.getDetailedStatsFile.bind(this)),this.app.get("/empty-page",this.getEmptyPage.bind(this)),this.serverData&&(T.debug(`using serverData: ${this.serverData}`),u.default.promises.mkdir(this.serverData,{recursive:!0}).catch((e=>{T.error(`mkdir ${this.serverData} error: ${e.message}`)})),this.app.get("/data",this.getDataArchive.bind(this)),this.app.get("/data/:path",this.getData.bind(this))),this.videoCachePath&&(T.debug(`using videoCachePath: ${this.videoCachePath}`),u.default.promises.mkdir(this.videoCachePath,{recursive:!0}).catch((e=>{T.error(`mkdir ${this.videoCachePath} error: ${e.message}`)})),this.app.get("/cache/:path",this.getCache.bind(this))),this.app.use(((e,t,s,a)=>{if(T.error(`request path=${t.path} error:`,e.stack),s.headersSent)return a(e);s.status(500).send(e.message)}))}async getStats(e,t,s){T.debug("GET /stats");const a=[];try{for(const e of this.stats.sessions.values())a.push(e.stats);t.json(a)}catch(e){s(e)}}getStatsFile(e,t,s){if(T.debug("/download/stats",e.query),!this.stats.statsWriter)return s(new Error("statsPath not set"));t.download(this.stats.statsPath)}getDetailedStatsFile(e,t,s){if(T.debug("/download/detailed-stats",e.query),!this.stats.detailedStatsWriter)return s(new Error("detailedStatsPath not set"));t.download(this.stats.detailedStatsPath)}getCollectedStats(e,t,s){T.debug("GET /collected-stats");const a={};try{for(const[e,t]of Object.entries(this.stats.collectedStats))a[e]=t.data;t.json(a)}catch(e){s(e)}}async getScreenshot(e,t,s){const a=parseInt(e.params.sessionId),r=parseInt(e.query.page||"0"),i=e.query.format||"webp";T.debug(`GET /screenshot/${a} page=${r} format=${i}`);try{const e=this.stats.sessions.get(a);if(!e)throw new Error(`Session not found: "${a}"`);const s=await e.pageScreenshot(r,i);t.sendFile(g.default.resolve(s))}catch(e){s(e)}}putCollectedStats(e,t,s){T.debug("PUT /collected-stats");const{id:a,stats:r,config:i}=e.body;try{this.stats.addExternalCollectedStats(a,r,i),t.json({message:"Collected stats added"})}catch(e){s(e)}}async putSession(e,t,s){T.debug("PUT /session",e.body);try{const s=e.body,a=this.stats.consumeSessionId(s.tabsPerSession);await this.startLocalSession(a,e.body),t.json({message:"Session created",data:{id:a}})}catch(e){s(e)}}async putSessions(e,t,s){T.debug("PUT /sessions",e.body);try{const{sessions:s,tabsPerSession:a}=e.body,r=[];for(let t=0;t<s;t++){const t=this.stats.consumeSessionId(a);await this.startLocalSession(t,e.body),r.push(t)}t.json({message:`${s} sessions created`,data:{ids:r}})}catch(e){s(e)}}async deleteSession(e,t,s){T.debug("DELETE /session",e.body);try{const{id:s}=e.body;await this.stopLocalSession(s),t.json({message:"Session deleted",data:{id:s}})}catch(e){s(e)}}async deleteSessions(e,t,s){T.debug("DELETE /sessions",e.body);try{const{ids:s}=e.body;for(const e of s)await this.stopLocalSession(e);t.json({message:`${s.length} sessions deleted`,data:{ids:s}})}catch(e){s(e)}}getPageLog(e,t,s){if(T.debug("GET /view/page.log",e.query),!this.pageLogPath)return s(new Error("pageLogPath not set"));e.query.range&&!e.headers.range&&(e.headers.range=`bytes=${e.query.range}`),t.sendFile(g.default.resolve(this.pageLogPath))}async getDockerLog(e,t,s){T.debug("GET /view/docker.log",e.query);try{const s=await(0,$.getDockerLogsPath)();e.query.range&&!e.headers.range&&(e.headers.range=`bytes=${e.query.range}`),t.sendFile(g.default.resolve(s))}catch(e){s(e)}}getAlertRules(e,t,s){if(T.debug("GET /download/alert-rules",e.query),!this.stats.alertRulesOutput)return s(new Error("Stats alertRulesOutput not set"));t.download(this.stats.alertRulesOutput)}getEmptyPage(e,t){T.debug("GET /empty-page",e.query);const s=e.query.title||"EmptyPage";t.send(`<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1">\n<title>${s}</title>\n</head>\n<body></body>\n</html>`)}getData(e,t,s){const a=g.default.normalize(e.params.path).replace(/^(\.\.(\/|\\|$))+/,"");T.debug(`GET /data/${a}`,e.query);const r=g.default.resolve(this.serverData,a);if(!u.default.existsSync(r))return s(new Error(`${a} not found`));e.query.range&&!e.headers.range&&(e.headers.range=`bytes=${e.query.range}`),t.sendFile(r)}getDataArchive(e,t,s){T.debug("GET /data",e.query);const a=g.default.resolve(this.serverData);if(!u.default.lstatSync(a).isDirectory())return s(new Error(`${a} is not a directory`));t.header("Content-Disposition",`attachment; filename="${g.default.basename(a)}.tar.gz"`),t.setHeader("content-type","application/gzip"),m.default.pack(a).pipe(w.default.createGzip()).pipe(t)}getCache(e,t,s){const a=g.default.normalize(e.params.path).replace(/^(\.\.(\/|\\|$))+/,"");T.debug(`GET /cache/${a}`,e.query);const r=g.default.resolve(this.videoCachePath,a);if(!u.default.existsSync(r))return s(new Error(`${a} not found`));e.query.range&&!e.headers.range&&(e.headers.range=`bytes=${e.query.range}`),t.sendFile(r)}async startLocalSession(e,t){const s=(await(0,y.loadConfig)(void 0,t))[0],a=(0,P.getSessionThrottleIndex)(e),r=1e3/s.spawnRate,i=[];if(s.videoPath)for(const e of s.videoPath.split(",")){const t=await(0,R.prepareFakeMedia)({...s,videoPath:e});i.push(t)}const o=i.length?i[e%i.length]:void 0,n=new S.Session({...s,throttleIndex:a,spawnPeriod:r,mediaPath:o,id:e});n.once("stop",(()=>{console.warn(`Session ${e} stopped, reloading...`),setTimeout(this.startLocalSession.bind(this),r,e,t)})),this.stats.addSession(n);try{await n.start()}catch(e){throw this.stats.removeSession(n.id),e}return n}async stopLocalSession(e){const t=this.stats.sessions.get(e);t?(t.removeAllListeners(),this.stats.removeSession(e),await t.stop()):T.warn(`stopLocalSession session ${e} not found`)}async start(){if(T.debug("start"),this.serverUseHttps){const e=g.default.join(f.default.homedir(),".webrtcperf/ssl"),t=g.default.join(e,"domain.key"),s=g.default.join(e,"domain.crt");u.default.existsSync(t)&&u.default.existsSync(s)||await(0,$.runShellCommand)(`mkdir -p ${e} && openssl req -newkey rsa:2048 -nodes -keyout ${t} -x509 -days 365 -out ${s} -subj "/C=EU/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com"`),this.server=(0,p.createServer)({key:u.default.readFileSync(t),cert:u.default.readFileSync(s)},this.app)}else this.server=(0,h.createServer)(this.app);const e=new v.WebSocketServer({noServer:!0});e.on("connection",((e,t)=>{try{const s=new URLSearchParams(t.url?.split("?")[1]||""),a=s.get("action")||"";switch(T.debug(`ws connection from ${t.socket.remoteAddress} action: ${a}`),a){case"write-stream":{if(!this.serverData)throw new Error("serverData option not set");const t=s.get("filename")||"";if(!t)throw new Error("filename not set");const a=g.default.normalize(t).replace(/^(\.\.(\/|\\|$))+/,"");T.debug(`ws write-stream ${a}`);const r=g.default.resolve(this.serverData,a);if(u.default.existsSync(r))throw new Error(`file already exists: ${r}`);const i=u.default.createWriteStream(r);let o=!1,n=0;const c=async()=>{i.close(),e.close();try{n||await u.default.promises.unlink(r)}catch(e){T.error(`ws write-stream close error: ${e.message}`)}};i.on("error",(e=>{T.error(`ws write-stream error: ${e.message}`),c()})),e.on("error",(e=>{T.error(`ws write-stream error: ${e.message}`),c()})),e.on("close",(()=>{T.debug("ws write-stream close"),c()})),e.on("message",(e=>{if(e?.byteLength){if(!o)return i.write(e),void(o=!0);i.write(e),n++}}));break}default:throw new Error(`invalid action: ${a}`)}}catch(t){T.error(`ws connection error: ${t.message}`),e.close()}})),this.wss=e,this.server.on("upgrade",((t,s,a)=>{T.debug(`ws upgrade ${t.url}`);try{const e=new URLSearchParams(t.url?.split("?")[1]||"").get("auth");if(!e||!(0,l.timingSafeEqual)(Buffer.from(e),Buffer.from(this.serverSecret)))throw new Error("invalid auth")}catch(e){return T.error(`ws upgrade error: ${e.message}`),s.write("HTTP/1.1 401 Unauthorized\r\n\r\n"),void s.destroy()}e.handleUpgrade(t,s,a,(s=>{e.emit("connection",s,t)}))})),this.server.listen(this.serverPort,(()=>{T.debug(`HTTPS server listening on port ${this.serverPort}`)}))}stop(){this.wss&&(this.wss.close(),this.wss=null),this.server&&(T.debug("stop"),this.server.close(),this.server=null)}}},7028:function(e,t,s){var a,r=this&&this.__createBinding||(Object.create?function(e,t,s,a){void 0===a&&(a=s);var r=Object.getOwnPropertyDescriptor(t,s);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[s]}}),Object.defineProperty(e,a,r)}:function(e,t,s,a){void 0===a&&(a=s),e[a]=t[s]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||(a=function(e){return a=Object.getOwnPropertyNames||function(e){var t=[];for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&(t[t.length]=s);return t},a(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var s=a(e),o=0;o<s.length;o++)"default"!==s[o]&&r(t,e,s[o]);return i(t,e),t}),n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getConfigDocs=function(){return P({},null,(0,c.default)($).getSchema())},t.loadConfig=async function(e,t){const s=[];if(e)if(e.startsWith("http")){b.debug(`Loading config from url: ${e}`);const s=await(0,w.downloadUrl)(e);if(!s?.data)throw new Error(`Failed to download configuration from: ${e}`);t="application/x-yaml"===s.contentType?f.default.parse(s.data):"application/toml"===s.contentType?g.default.parse(s.data):p.default.parse(s.data)}else{if(!(0,d.existsSync)(e))throw new Error(`Config file not found: ${e}`);if(b.debug(`Loading config from local file: ${e}`),e.endsWith(".js")||e.endsWith(".mjs")){const s=await import(h.default.resolve(e));t=await s.default()}else{const s=String(await m.default.promises.readFile(e));t=e.endsWith(".yml")||e.endsWith(".yaml")?f.default.parse(s):e.endsWith(".toml")?g.default.parse(s):p.default.parse(s)}}Array.isArray(t)||(t=[t||{}]);for(const e of t){const t=(0,c.default)($);t.load(e||{}),t.validate({allowed:"strict"}),s.push(t.getProperties())}return b.debug("Using config:",s),s},t.loadConfigFromPrompt=async function(e){if(b.debug(`loadConfigFromPrompt: "${e}"`),!process.env.GEMINI_API_KEY)throw new Error("GEMINI_API_KEY environment variable is not set. Please set it to use the Google GenAI API.");const t=new T({apiKey:process.env.GEMINI_API_KEY}),s=await t.models.generateContent({model:"gemini-2.5-flash",contents:e,config:{tools:[{functionDeclarations:[R()]}],thinkingConfig:{thinkingBudget:0}}});if(s.functionCalls&&s.functionCalls.length>0){const e=s.functionCalls[0];return b.debug("Using function call:",e.name,e.args),e.args}throw new Error("No function call found in the response. Please check the prompt and try again.")};const c=o(s(4950)),l=s(7618),d=s(9896),u=n(s(857)),h=o(s(6928)),p=n(s(5865)),f=n(s(2115)),g=n(s(4908)),m=n(s(9896)),v=s(8034),w=s(6185),b=(0,w.logger)("webrtcperf:config"),y={name:"float",coerce:e=>parseFloat(e),validate:e=>{if(!Number.isFinite(e))throw new Error(`Invalid float: ${e}`)}},S={name:"index",coerce:e=>e,validate:e=>{if("string"!=typeof e){if("number"!=typeof e&&"boolean"!=typeof e)throw new Error(`Invalid index: "${e}" (type: ${typeof e})`)}else{if("true"===e||"false"===e||""===e)return;if(e.includes("-"))return void e.split("-").forEach((e=>{if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid string index: ${e}`)}));if(e.includes(","))return void e.split(",").forEach((e=>{if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid string index: ${e}`)}));if(isNaN(parseInt(e))||!isFinite(parseInt(e)))throw new Error(`Invalid string index: ${e}`)}}};(0,c.addFormats)({ipaddress:l.ipaddress,url:l.url,float:y,index:S}),c.default.addParser([{extension:"json",parse:p.default.parse},{extension:["yml","yaml"],parse:f.default.parse},{extension:"toml",parse:g.default.parse}]);const $={url:{doc:"The page url to load.",format:String,default:"",nullable:!0,env:"URL",arg:"url"},urlQuery:{doc:"The query string to append to the page url; the following template variables are replaced: `$p` the process pid, `$s` the session index, `$S` the total sessions, `$t` the tab index, `$T` the total tabs per session, `$i` the tab absolute index.",format:String,default:"",nullable:!0,env:"URL_QUERY",arg:"url-query"},customUrlHandler:{doc:"This argument specifies the file path for the custom page URL handler that will be exported by default. The custom page URL handler allows you to define custom URLs that can be used to open your application. The handler function will be called with the following variables: - sessions: the total number of sessions; - tabsPerSession: the total number of tabs per session; - id: the session global index (0-indexed); - index: the tab global index (0-indexed); - tabIndex: the tab index in the current session (0-indexed); - pid: the process pid; - env: the environment variables object; - params: the script parameters object. You can use these variables to create custom URL schemes that suit your application's needs.",format:String,default:"",nullable:!0,env:"CUSTOM_URL_HANDLER",arg:"custom-url-handler"},videoPath:{doc:"The fake video path; if set, the video will be used as fake media source. It accepts a single path or a comma-separated list of videos paths that will be used in round-robin by the started sessions. The docker pre-built image contains a 2 minutes video sequence stored at `/app/video.mp4`. It accepts a local file, an http endpoint or a string starting with\n`generate:` (example: `generate:null` will generate a black video with silent audio). The temporary files containing the raw video and audio will be stored at `${VIDEO_CACHE_PATH}/video.${VIDEO_FORMAT}` and `${VIDEO_CACHE_PATH}/audio.wav`.",format:String,default:"https://github.com/vpalmisano/webrtcperf/releases/download/videos-1.0/kt.mp4",env:"VIDEO_PATH",arg:"video-path"},videoWidth:{doc:"The fake video resize width.",format:"nat",default:1280,env:"VIDEO_WIDTH",arg:"video-width"},videoHeight:{doc:"The fake video resize height.",format:"nat",default:720,env:"VIDEO_HEIGHT",arg:"video-height"},videoFramerate:{doc:"The fake video framerate.",format:"nat",default:25,env:"VIDEO_FRAMERATE",arg:"video-framerate"},videoSeek:{doc:"The fake audio/video seek position in seconds.",format:"nat",default:0,env:"VIDEO_SEEK",arg:"video-seek"},videoDuration:{doc:"The fake audio/video duration in seconds.",format:"nat",default:120,env:"VIDEO_DURATION",arg:"video-duration"},videoCacheRaw:{doc:"If the temporary video and audio raw files can be reused across multiple runs.",format:"Boolean",default:!0,env:"VIDEO_CACHE_RAW",arg:"video-cache-raw"},videoCachePath:{doc:"The path where the video and audio raw files are stored.",format:String,default:(0,h.join)(u.default.homedir(),".webrtcperf/cache"),env:"VIDEO_CACHE_PATH",arg:"video-cache-path"},videoFormat:{doc:"The fake video file format presented to the browser.",format:["y4m","mjpeg"],default:"y4m",env:"VIDEO_FORMAT",arg:"video-format"},useFakeMedia:{doc:"If true, the audio/video/screenshare will be generated using the browser fake device.\nOtherwise, the audio and video streams will be captured from a video element attached to the page, \nwhile the screenshare will be captured from a new browser tab.",format:"Boolean",default:!0,env:"USE_FAKE_MEDIA",arg:"use-fake-media"},runDuration:{doc:"If greater than 0, the test will stop after the provided number of seconds.",format:"nat",default:0,env:"RUN_DURATION",arg:"run-duration"},throttleConfig:{doc:'A JSON5 string with a valid throttler configuration (https://github.com/vpalmisano/throttler). Example: \n ```javascript\n [{\n sessions: \'0-1\',\n device: \'eth0\',\n protocol: \'udp\',\n skipSourcePorts: "443",\n skipDestinationPorts: "443",\n filter: "--sports 443 --dports 443",\n match: \'nbyte("ababa" at 12 layer 1)\',\n capture: \'capture.pcap\',\n up: {\n rate: 1000,\n delay: 50,\n loss: 5,\n queue: 10,\n },\n down: [\n { rate: 2000, delay: 50, delayJitter: 10, delayJitterCorrelation: 25, loss: 2, lossBurst: 2, queue: 20 },\n { rate: 1000, delay: 50, loss: 2, queue: 20, at: 60 },\n ]\n }]\n ```\n- The sessions field represents the sessions IDs range that will be affected by the rule, e.g.: "0-10", "2,4" or simply "2".\n- 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\'), only the packets with the specified protocol will be affected by the shaping rules.\n- The capture field is optional and specifies the pcap file to save the captured packets.\n- With skipSourcePorts and skipDestinationPorts you can specify a comma-separated list of ports that will not be affected by the shaping rules.\n- The filter field is optional and specifies the additional IPTables filter to apply for filtering the packets.\n- 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).\n- 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:\n - rate: the shaping rate in Kbps;\n - delay: the shaping delay in milliseconds;\n - delayJitter: the shaping delay jitter in milliseconds;\n - delayJitterCorrelation: the shaping delay jitter correlation in milliseconds;\n - loss: the packet loss percentage;\n - lossBurst: the packet loss burst percentage;\n - queue: the shaping queue size in packets;\n - at: the time in seconds when the shaping rule will be applied (default: 0).\nThe up and down rules can be specified as a single object or an array of objects.\nWhen 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. ',format:String,nullable:!0,default:"",env:"THROTTLE_CONFIG",arg:"throttle-config"},useBrowserThrottling:{doc:"If true, the network will be throttled using the browser internal throttling mechanism.",format:"Boolean",default:!1,env:"USE_BROWSER_THROTTLING",arg:"use-browser-throttling"},randomAudioPeriod:{doc:"If not zero, it specifies the maximum period in seconds after which a new random active session is selected, enabling the getUserMedia audio tracks in that session and disabling all of the others.",format:"nat",default:0,env:"RANDOM_AUDIO_PERIOD",arg:"random-audio-period"},randomAudioProbability:{doc:"When using random audio period, it defines the probability % that the selected audio will be activated (value: 0-100).",format:"nat",default:100,env:"RANDOM_AUDIO_PROBABILITY",arg:"random-audio-probability"},randomAudioRange:{doc:"When using random audio period, it defines the session indexes to be included into the random selection (default: include all the sessions).",format:"index",default:"true",nullable:!0,env:"RANDOM_AUDIO_RANGE",arg:"random-audio-range"},chromiumPath:{doc:"The Chromium executable path.",format:String,nullable:!0,default:"",env:"CHROMIUM_PATH",arg:"chromium-path"},chromiumVersion:{doc:"The Chromium version. It will be downloaded if the chromium path is not provided.",format:String,nullable:!1,default:v.PUPPETEER_REVISIONS.chrome,env:"CHROMIUM_VERSION",arg:"chromium-version"},chromiumUrl:{doc:"The remote Chromium URL (`http://HOST:PORT`).\nIf provided, the remote instance will be used instead of running a local\nchromium process.",format:String,default:"",nullable:!0,env:"CHROMIUM_URL",arg:"chromium-url"},chromiumFieldTrials:{doc:"Chromium additional field trials.",format:String,nullable:!0,default:"",env:"CHROMIUM_FIELD_TRIALS",arg:"chromium-field-trials"},windowWidth:{doc:"The browser window width.",format:"nat",default:1920,env:"WINDOW_WIDTH",arg:"window-width"},windowHeight:{doc:"The browser window height.",format:"nat",default:1080,env:"WINDOW_HEIGHT",arg:"window-height"},deviceScaleFactor:{doc:"The browser device scale factor.",format:"float",default:1,env:"DEVICE_SCALE_FACTOR",arg:"device-scale-factor"},maxVideoDecoders:{doc:"Specifies the maximum number of concurrent WebRTC video decoder instances that can be created on the same host.\nIf set it will disable the received video resolution and jitter buffer stats. This option is supported only when using the custom chromium build. The total decoders count is stored into the virtual file `/dev/shm/chromium-video-decoders`",format:Number,default:-1,env:"MAX_VIDEO_DECODERS",arg:"max-video-decoders"},maxVideoDecodersRange:{doc:"It applies the max video decoders option to the sessions included into this list (default: include all the sessions)",format:"index",default:"true",nullable:!0,env:"MAX_VIDEO_DECODERS_RANGE",arg:"max-video-decoders-range"},incognito:{doc:"Runs the browser in incognito mode.",format:"Boolean",default:!1,env:"INCOGNITO",arg:"incognito"},display:{doc:"If unset, the browser will run in headless mode, otherwise it will run in normal windowed mode.\nWhen running on MacOS or Windows, set it to any not-empty string.\nOn Linux, set it to a valid X server `DISPLAY` string (e.g. `:0`).",format:String,default:"",nullable:!0,arg:"display"},sessions:{doc:"The number of browser sessions to start.",format:"nat",default:0,env:"SESSIONS",arg:"sessions"},tabsPerSession:{doc:"The number of tabs to open in each browser session.",format:"nat",default:1,env:"TABS_PER_SESSION",arg:"tabs-per-session"},startSessionId:{doc:"The starting ID assigned to sessions.",format:"nat",default:0,env:"START_SESSION_ID",arg:"start-session-id"},startTimestamp:{doc:"The start timestamp (in milliseconds). If 0, the value will be calculated using `Date.now()`",format:"nat",default:0,env:"START_TIMESTAMP",arg:"start-timestamp"},enableDetailedStats:{doc:"If detailed participant metrics values should be collected.",format:"index",default:"0-24",nullable:!0,env:"ENABLE_DETAILED_STATS",arg:"enable-detailed-stats"},spawnRate:{doc:"The pages spawn rate (pages/s).",format:"float",default:1,env:"SPAWN_RATE",arg:"spawn-rate"},showPageLog:{doc:"If `true`, the pages console logs will be shown on console. Set to false to disable the page logs.",format:"Boolean",default:!1,env:"SHOW_PAGE_LOG",arg:"show-page-log"},pageLogFilter:{doc:"If set, only the logs with the matching text will be printed on the console. Regexp string allowed.",format:String,default:"",nullable:!0,env:"PAGE_LOG_FILTER",arg:"page-log-filter"},pageLogPath:{doc:"If set, the page console logs will be saved on the selected file path.",format:String,default:"",nullable:!0,env:"PAGE_LOG_PATH",arg:"page-log-path"},enableBrowserLogging:{doc:"It enables the Chromium browser logging for the specified session indexes. It requires the page log path option to be set. ",format:"index",nullable:!0,default:"",env:"ENABLE_BROWSER_LOGGING",arg:"enable-browser-logging"},userAgent:{doc:"The user agent override.",format:String,default:`Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${v.PUPPETEER_REVISIONS.chrome} Safari/537.36`,nullable:!0,env:"USER_AGENT",arg:"user-agent"},scriptPath:{doc:"One or more JavaScript file paths (comma-separated). If set, the files contents will be executed inside each opened tab page; the following global variables will be attached to the `webrtcperf` global object: `WEBRTC_PERF_SESSION` the session number (0-indexed); `WEBRTC_PERF_TAB` the tab number inside the same session (0-indexed); `WEBRTC_PERF_INDEX` the page absolute index (0-indexed).\nSuggested values:\n- With meet.google.com: https://raw.githubusercontent.com/vpalmisano/webrtcperf/refs/heads/devel/examples/google-meet.js\n",format:String,default:"",env:"SCRIPT_PATH",arg:"script-path"},scriptParams:{doc:"Additional parameters (in JSON format) that will be exposed into\nthe page context as `webrtcperf.params`.",format:String,nullable:!0,default:"",env:"SCRIPT_PARAMS",arg:"script-params"},disabledVideoCodecs:{doc:"A string with the video codecs to disable (comma-separated); e.g. `vp9,av1`",format:String,nullable:!0,default:"",env:"DISABLED_VIDEO_CODECS",arg:"disabled-video-codecs"},localStorage:{doc:"A JSON string with the `localStorage` object to be set on page load.",format:String,nullable:!0,default:"",env:"LOCAL_STORAGE",arg:"local-storage"},sessionStorage:{doc:"A JSON string with the `sessionStorage` object to be set on page load.",format:String,nullable:!0,default:"",env:"SESSION_STORAGE",arg:"session-storage"},clearCookies:{doc:"If true, all the page cookies are cleared.",format:"Boolean",default:!1,env:"CLEAR_COOKIES",arg:"clear-cookies"},enableGpu:{doc:'It enables the GPU acceleration (experimental). Set to "desktop" to use the host X server instance.',format:String,nullable:!0,default:"",env:"ENABLE_GPU",arg:"enable-gpu"},blockedUrls:{doc:"A comma-separated list of request URLs that will be automatically blocked.",format:String,nullable:!0,default:"",env:"BLOCKED_URLS",arg:"blocked-urls"},extraHeaders:{doc:'A dictionary of headers keyed by the url in JSON5 format (e.g. `{ "https://url.com/*": { "header-name": "value" } }`).',format:String,nullable:!0,default:"",env:"EXTRA_HEADERS",arg:"extra-headers"},responseModifiers:{doc:'A dictionary of content replacements keyed by the url in JSON5 format.\nExamples:\n- replace strings using a regular expression:\n `{ "https://url.com/*": [{ search: "searchString": replace: "anotherString" }] }`\n- completely replace the content:\n `{ "https://url.com/file.js": [{ file: "path/to/newFile.js" }] }`\n',format:String,nullable:!0,default:"",env:"RESPONSE_MODIFIERS",arg:"response-modifiers"},downloadResponses:{doc:'An array of url responses that will be saved to the disk, keyed by the url in JSON5 format.\nExample: `[{ urlPattern: "https://url.com/*", output: "save/directory" }]`\n',format:String,nullable:!0,default:"",env:"DOWNLOAD_RESPONSES",arg:"download-responses"},extraCSS:{doc:'A string with a CSS styles to inject into each page. Rules containing "important" will be replaced with "!important".',format:String,nullable:!0,default:"",env:"EXTRA_CSS",arg:"extra-css"},cookies:{doc:"A string with an array of [CookieParam](https://pptr.dev/api/puppeteer.cookieparam) to set into each page in JSON5 format.",format:String,nullable:!0,default:"",env:"COOKIES",arg:"cookies"},overridePermissions:{doc:"A comma-separated list of permissions to grant to the opened url.",format:String,nullable:!0,default:"",env:"OVERRIDE_PERMISSIONS",arg:"override-permissions"},hardwareConcurrency:{doc:"When set, it overrides the navigator.hardwareConcurrency property.",format:"nat",default:0,env:"HARDWARE_CONCURRENCY",arg:"hardware-concurrency"},debuggingPort:{doc:"The chrome debugging port. If this value != 0, the chrome instance will listen on the provided port + the start-session-id value.",format:"nat",default:0,env:"DEBUGGING_PORT",arg:"debugging-port"},debuggingAddress:{doc:"The chrome debugging listening address. If unset, the network default interface address will be used.",format:String,nullable:!0,default:"127.0.0.1",env:"DEBUGGING_ADDRESS",arg:"debugging-address"},emulateCpuThrottling:{doc:"The emulated CPU throttling factor. If set, the page will be throttled to the specified factor.",format:"nat",default:0,env:"EMULATE_CPU_THROTTLING",arg:"emulate-cpu-throttling"},showStats:{doc:"If the statistics should be displayed on the console output.",format:"Boolean",default:!0,env:"SHOW_STATS",arg:"show-stats"},statsPath:{doc:"The log file path; if set, the stats will be written in a .csv file inside that file.",format:String,default:"",env:"STATS_PATH",arg:"stats-path"},detailedStatsPath:{doc:"The log file path; if set, the detailed stats will be written in a .csv file inside that file.",format:String,default:"",env:"DETAILED_STATS_PATH",arg:"detailed-stats-path"},statsInterval:{doc:"The stats collect interval in seconds. It should be lower than the Prometheus scraping interval.",format:"nat",default:15,env:"STATS_INTERVAL",arg:"stats-interval"},rtcStatsTimeout:{doc:"The timeout in seconds after which the RTC stats coming from inactive hosts are removed. It should be higher than the `statsInterval` value.",format:"nat",default:60,env:"RTC_STATS_TIMEOUT",arg:"rtc-stats-timeout"},customMetrics:{doc:"A dictionary of custom metrics keys in JSON5 format (e.g. '{ statName1: { labels: [\"label1\"] } }').",format:String,nullable:!0,default:"",env:"CUSTOM_METRICS",arg:"custom-metrics"},prometheusPushgateway:{doc:'If set, logs are sent to the specified Prometheus Pushgateway service (example: "http://127.0.0.1:9091").',format:"String",default:"",nullable:!0,env:"PROMETHEUS_PUSHGATEWAY",arg:"prometheus-pushgateway"},prometheusPushgatewayJobName:{doc:"The Prometheus Pushgateway job name.",format:"String",default:"default",env:"PROMETHEUS_PUSHGATEWAY_JOB_NAME",arg:"prometheus-pushgateway-job-name"},prometheusPushgatewayAuth:{doc:"The Prometheus Pushgateway basic auth (username:password).",format:"String",default:"",nullable:!0,env:"PROMETHEUS_PUSHGATEWAY_AUTH",arg:"prometheus-pushgateway-auth"},prometheusPushgatewayGzip:{doc:"Allows to use gzip encoded pushgateway requests.",format:"Boolean",default:!0,env:"PROMETHEUS_PUSHGATEWAY_GZIP",arg:"prometheus-pushgateway-gzip"},alertRules:{doc:"Alert rules definition (in JSON format).",format:String,nullable:!0,default:"",env:"ALERT_RULES",arg:"alert-rules"},alertRulesOutput:{doc:"The alert rules report output filename. If the file ends with .log extension, a detailed log will be generated, otherwise a JSON report will be generated.",format:String,nullable:!0,default:"",env:"ALERT_RULES_OUTPUT",arg:"alert-rules-output"},alertRulesFailPercentile:{doc:"The alert rules report fails percentile (0-100). With the default value the alert will be successful only when at least 95% of the checks pass.",format:"nat",nullable:!1,default:95,env:"ALERT_RULES_FAIL_PERCENTILE",arg:"alert-rules-fail-percentile"},pushStatsUrl:{doc:"The URL to push the collected stats.",format:String,nullable:!0,default:"",env:"PUSH_STATS_URL",arg:"push-stats-url"},pushStatsId:{doc:"The ID of the collected stats to push.",format:String,nullable:!0,default:"default",env:"PUSH_STATS_ID",arg:"push-stats-id"},serverPort:{doc:"The HTTP server listening port.",format:"nat",nullable:!0,default:0,env:"SERVER_PORT",arg:"server-port"},serverSecret:{doc:"The HTTP server basic auth secret. The auth user name is set to `admin` by default.",format:String,default:"secret",env:"SERVER_SECRET",arg:"server-secret"},serverUseHttps:{doc:"If true, the server will use the HTTPS protocol.",format:"Boolean",default:!1,env:"SERVER_USE_HTTPS",arg:"server-use-https"},serverData:{doc:"An optional path that the HTTP server will expose with the /data endpoint.",format:String,nullable:!0,default:"",env:"SERVER_DATA",arg:"server-data"},vmafPath:{doc:"When set, it runs the VMAF calculator for the video files saved under the provided directory path.",format:String,nullable:!0,default:"",env:"VMAF_PATH",arg:"vmaf-path"},vmafPreview:{doc:"If true, for each VMAF comparison it creates a side-by-side video with the reference and degraded versions.",format:"Boolean",default:!1,env:"VMAF_PREVIEW",arg:"vmaf-preview"},vmafKeepIntermediateFiles:{doc:"If true, the VMAF intermediate files will not be deleted.",format:"Boolean",default:!1,env:"VMAF_KEEP_INTERMEDIATE_FILES",arg:"vmaf-keep-intermediate-files"},vmafKeepSourceFiles:{doc:"If true, the VMAF source files will not be deleted.",format:"Boolean",default:!0,env:"VMAF_KEEP_SOURCE_FILES",arg:"vmaf-keep-source-files"},vmafSkipDuplicated:{doc:"If true, the VMAF will skip duplicated recognized frames.",format:"Boolean",default:!1,env:"VMAF_SKIP_DUPLICATED",arg:"vmaf-skip-duplicated"},vmafCrop:{doc:'If set, the reference and degraded videos will be cropped using the specified configuration in JSON5 format. Crop configuration should be expressed using the ffmpeg crop filter syntax (https://ffmpeg.org/ffmpeg-filters.html#crop). E.g. `{ "Participant-000001_recv-by_Participant-000000": { ref: { w: "iw-10", h: "ih-5" }, deg: { w: "200", h: "200" } } }`',format:String,nullable:!0,default:"",env:"VMAF_CROP",arg:"vmaf-crop"},vmafPrepareVideo:{doc:"When set, it prepares the selected video applying a timestamp overlay on top of it. The filename must be provided in the format `<video path>,<ID>`, where the selected ID will be used unique video identifier in the overlay.",format:String,nullable:!0,default:"",env:"VMAF_PREPARE_VIDEO",arg:"vmaf-prepare-video"},vmafProcessVideo:{doc:"When set, it runs the VMAF video preprocessor, that converts a video file into the IVF format with timestamps matching the overlay recognition. The filename must contain a `recv` or `send` string to identify if the video was a reference (send) or a degraded version (recv), e.g. `Participant1_recv.mp4`.",format:String,nullable:!0,default:"",env:"VMAF_PROCESS_VIDEO",arg:"vmaf-process-video"},vmafVideoCrop:{doc:'If set, the vmaf prepared/processed video will be cropped using the specified configuration in JSON5 format. Crop configuration should be expressed using the ffmpeg crop filter syntax (https://ffmpeg.org/ffmpeg-filters.html#crop). E.g. `{ w: "iw-10", h: "ih-5", x: "10", y: \'5\' }`',format:String,nullable:!0,default:"",env:"VMAF_VIDEO_CROP",arg:"vmaf-video-crop"},visqolPath:{doc:"When set, it runs the visqol calculator for the audio files saved under the provided directory path.",format:String,nullable:!0,default:"",env:"VISQOL_PATH",arg:"visqol-path"},visqolKeepSourceFiles:{doc:"If true, the visqol source files will not be deleted.",format:"Boolean",default:!0,env:"VISQOL_KEEP_SOURCE_FILES",arg:"visqol-keep-source-files"}};function P(e,t,s){return s._cvtProperties?(Object.entries(s._cvtProperties).forEach((([s,a])=>{P(e,`${t?`${t}.`:""}${s}`,a)})),e):(t&&(e[t]={doc:s.doc,format:JSON.stringify(s.format,null,2),default:JSON.stringify(s.default,null,2)}),e)}(0,c.default)($).getProperties();function R(){const e={},t=(0,c.default)($).getSchema();return Object.entries(t._cvtProperties).forEach((([t,s])=>{const{format:a,doc:r,nullable:i}=s;e[t]={type:a,description:r,nullable:i}})),{name:"webrtcperf",description:"Starts a webrtcperf test.",parameters:{type:"object",properties:e,required:[]}}}const{GoogleGenAI:T}=s(72)},7174:e=>{e.exports=require("compression")},7191:e=>{e.exports=require("@vpalmisano/throttler")},7252:e=>{e.exports=require("express")},7564:function(e,t,s){var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.RtcStatsMetricNames=t.PageStatsNames=void 0,t.rtcStatKey=d,t.parseRtStatKey=function(e){const[t,s,a,r,i]=e.split(":",5);return{pageIndex:t?parseInt(t):void 0,trackId:i||void 0,hostName:a||"unknown",codec:r||void 0,participantName:s||void 0}},t.updateRtcStats=function(e,t,s,a,r,o){const{enabled:n,inboundRtp:c,outboundRtp:u,remoteAddress:h,videoSentActiveEncodings:p,sentMaxBitrate:f,isDisplay:g,codec:m,availableOutgoingBitrate:v}=a,w=d({pageIndex:t,trackId:s,hostName:r||h,codec:m,participantName:o});if(c){const t="video"===c.kind?g?"screen":"video":"audio";l(e,t+"RecvCodec",w,m),n&&(l(e,t+"RecvAvgJitterBufferDelay",w,c.jitterBuffer),l(e,t+"RecvBitrates",w,c.bitrate),l(e,t+"RecvBytes",w,c.bytesReceived),l(e,t+"RecvJitter",w,c.jitter),l(e,t+"RecvRoundTripTime",w,c.transportRoundTripTime),l(e,t+"RecvPackets",w,c.packetsReceived),l(e,t+"RecvRetransmittedPackets",w,c.retransmittedPacketsReceived),l(e,t+"RecvPacketsLost",w,c.packetsLossRate),l(e,t+"RecvPacketsLossRate",w,c.packetsLossRate),l(e,t+"RecvLostPackets",w,c.packetsLost),l(e,t+"RecvNackCountSent",w,c.nackCount),l(e,t+"RecvEndToEndDelay",w,c.endToEndDelay),"audio"===c.kind&&["audioLevel","totalSamplesReceived","concealedSamples","concealmentEvents","insertedSamplesForDeceleration","removedSamplesForAcceleration"].forEach((s=>{l(e,t+"Recv"+(0,i.toTitleCase)(s.replace("audio","")),w,c[s])})),"video"===c.kind&&c.keyFramesDecoded>0&&(l(e,t+"RecvFrames",w,c.framesReceived),l(e,t+"RecvFps",w,c.framesPerSecond),l(e,t+"RecvHeight",w,c.frameHeight),l(e,t+"RecvWidth",w,c.frameWidth),l(e,t+"RecvFrameRate",w,c.frameRate),l(e,t+"FirCountSent",w,c.firCount),l(e,t+"PliCountSent",w,c.pliCount),l(e,t+"DecodeLatency",w,c.decodeLatency),l(e,t+"TotalFreezesDuration",w,c.totalFreezesDuration)))}if(u){const t="video"===u.kind?g?"screen":"video":"audio";l(e,t+"SentCodec",w,m),n&&(l(e,t+"SentBitrates",w,u.bitrate),l(e,t+"SentBytes",w,u.bytesSent+u.headerBytesSent),l(e,t+"SentPackets",w,u.packetsSent),l(e,t+"SentPacketsLost",w,u.packetsLossRate),l(e,t+"SentNackCountRecv",w,u.nackCount),l(e,t+"SentRoundTripTime",w,u.roundTripTime),l(e,t+"SentJitter",w,u.jitter),l(e,t+"SentTransportRoundTripTime",w,u.transportRoundTripTime),l(e,"transportSentAvailableOutgoingBitrate",w,v),l(e,t+"SentMaxBitrate",w,f),l(e,t+"SentRetransmittedPackets",w,u.retransmittedPacketsSent),"video"===u.kind&&(l(e,t+"SentActiveEncodings",w,p),l(e,t+"QualityLimitationResolutionChanges",w,u.qualityLimitationResolutionChanges),l(e,t+"QualityLimitationCpu",w,u.qualityLimitationCpu),l(e,t+"QualityLimitationBandwidth",w,u.qualityLimitationBandwidth),l(e,t+"SentWidth",w,u.frameWidth),l(e,t+"SentHeight",w,u.frameHeight),l(e,t+"SentFrames",w,u.framesSent),l(e,t+"SentFps",w,u.framesPerSecond),l(e,t+"FirCountReceived",w,u.firCountReceived),l(e,t+"PliCountReceived",w,u.pliCountReceived),l(e,t+"EncodeLatency",w,u.encodeLatency),l(e,t+"SentLatency",w,u.sentLatency)))}};const r=a(s(2613)),i=s(6185);var o,n;!function(e){e.cpu="cpu",e.memory="memory",e.nodeCpu="nodeCpu",e.nodeMemory="nodeMemory",e.usedCpu="usedCpu",e.usedMemory="usedMemory",e.usedGpu="usedGpu",e.pageCpu="pageCpu",e.pageMemory="pageMemory",e.pages="pages",e.peerConnections="peerConnections",e.peerConnectionConnectionTime="peerConnectionConnectionTime",e.peerConnectionDisconnectionTime="peerConnectionDisconnectionTime",e.peerConnectionsCreated="peerConnectionsCreated",e.peerConnectionsConnected="peerConnectionsConnected",e.peerConnectionsClosed="peerConnectionsClosed",e.peerConnectionsDisconnected="peerConnectionsDisconnected",e.peerConnectionsFailed="peerConnectionsFailed",e.peerConnectionsDelay="peerConnectionsDelay",e.errors="errors",e.warnings="warnings",e.httpSentBytes="httpSentBytes",e.httpRecvBytes="httpRecvBytes",e.httpRecvLatency="httpRecvLatency",e.wsSentBytes="wsSentBytes",e.wsRecvBytes="wsRecvBytes",e.wsRecvLatency="wsRecvLatency",e.audioEndToEndDelay="audioEndToEndDelay",e.audioStartFrameDelay="audioStartFrameDelay",e.videoEndToEndDelay="videoEndToEndDelay",e.videoStartFrameDelay="videoStartFrameDelay",e.screenEndToEndDelay="screenEndToEndDelay",e.screenStartFrameDelay="screenStartFrameDelay",e.cpuPressure="cpuPressure",e.videoWidth="videoWidth",e.videoHeight="videoHeight",e.videoBufferedTime="videoBufferedTime",e.videoPlayingTime="videoPlayingTime",e.videoBufferingTime="videoBufferingTime",e.videoBufferingEvents="videoBufferingEvents",e.throttleUpRate="throttleUpRate",e.throttleUpDelay="throttleUpDelay",e.throttleUpLoss="throttleUpLoss",e.throttleUpQueue="throttleUpQueue",e.throttleDownRate="throttleDownRate",e.throttleDownDelay="throttleDownDelay",e.throttleDownLoss="throttleDownLoss",e.throttleDownQueue="throttleDownQueue"}(o||(t.PageStatsNames=o={})),function(e){e.audioSentCodec="audioSentCodec",e.audioSentBytes="audioSentBytes",e.audioSentPackets="audioSentPackets",e.audioSentBitrates="audioSentBitrates",e.audioSentPacketsLost="audioSentPacketsLost",e.audioSentNackCountRecv="audioSentNackCountRecv",e.audioSentRoundTripTime="audioSentRoundTripTime",e.audioSentJitter="audioSentJitter",e.audioSentTransportRoundTripTime="audioSentTransportRoundTripTime",e.audioSentMaxBitrate="audioSentMaxBitrate",e.audioSentRetransmittedPackets="audioSentRetransmittedPackets",e.videoSentCodec="videoSentCodec",e.videoFirCountReceived="videoFirCountReceived",e.videoPliCountReceived="videoPliCountReceived",e.videoEncodeLatency="videoEncodeLatency",e.videoSentLatency="videoSentLatency",e.videoQualityLimitationBandwidth="videoQualityLimitationBandwidth",e.videoQualityLimitationCpu="videoQualityLimitationCpu",e.videoQualityLimitationResolutionChanges="videoQualityLimitationResolutionChanges",e.videoSentActiveEncodings="videoSentActiveEncodings",e.videoSentBitrates="videoSentBitrates",e.videoSentBytes="videoSentBytes",e.videoSentPackets="videoSentPackets",e.videoSentFrames="videoSentFrames",e.videoSentFps="videoSentFps",e.videoSentWidth="videoSentWidth",e.videoSentHeight="videoSentHeight",e.videoSentMaxBitrate="videoSentMaxBitrate",e.videoSentPacketsLost="videoSentPacketsLost",e.videoSentNackCountRecv="videoSentNackCountRecv",e.videoSentRoundTripTime="videoSentRoundTripTime",e.videoSentTransportRoundTripTime="videoSentTransportRoundTripTime",e.videoSentJitter="videoSentJitter",e.videoSentRetransmittedPackets="videoSentRetransmittedPackets",e.screenSentCodec="screenSentCodec",e.screenFirCountReceived="screenFirCountReceived",e.screenPliCountReceived="screenPliCountReceived",e.screenEncodeLatency="screenEncodeLatency",e.screenSentLatency="screenSentLatency",e.screenQualityLimitationBandwidth="screenQualityLimitationBandwidth",e.screenQualityLimitationCpu="screenQualityLimitationCpu",e.screenQualityLimitationResolutionChanges="screenQualityLimitationResolutionChanges",e.screenSentActiveEncodings="screenSentActiveEncodings",e.screenSentBitrates="screenSentBitrates",e.screenSentBytes="screenSentBytes",e.screenSentPackets="screenSentPackets",e.screenSentFrames="screenSentFrames",e.screenSentFps="screenSentFps",e.screenSentWidth="screenSentWidth",e.screenSentHeight="screenSentHeight",e.screenSentMaxBitrate="screenSentMaxBitrate",e.screenSentPacketsLost="screenSentPacketsLost",e.screenSentNackCountRecv="screenSentNackCountRecv",e.screenSentRoundTripTime="screenSentRoundTripTime",e.screenSentTransportRoundTripTime="screenSentTransportRoundTripTime",e.screenSentJitter="screenSentJitter",e.screenSentRetransmittedPackets="screenSentRetransmittedPackets",e.audioRecvCodec="audioRecvCodec",e.audioRecvBytes="audioRecvBytes",e.audioRecvAvgJitterBufferDelay="audioRecvAvgJitterBufferDelay",e.audioRecvBitrates="audioRecvBitrates",e.audioRecvJitter="audioRecvJitter",e.audioRecvRoundTripTime="audioRecvRoundTripTime",e.audioRecvPackets="audioRecvPackets",e.audioRecvPacketsLost="audioRecvPacketsLost",e.audioRecvLostPackets="audioRecvLostPackets",e.audioRecvPacketsLossRate="audioRecvPacketsLossRate",e.audioRecvRetransmittedPackets="audioRecvRetransmittedPackets",e.audioRecvNackCountSent="audioRecvNackCountSent",e.audioRecvLevel="audioRecvLevel",e.audioRecvTotalSamplesReceived="audioRecvTotalSamplesReceived",e.audioRecvConcealedSamples="audioRecvConcealedSamples",e.audioRecvConcealmentEvents="audioRecvConcealmentEvents",e.audioRecvInsertedSamplesForDeceleration="audioRecvInsertedSamplesForDeceleration",e.audioRecvRemovedSamplesForAcceleration="audioRecvRemovedSamplesForAcceleration",e.audioRecvEndToEndDelay="audioRecvEndToEndDelay",e.videoRecvCodec="videoRecvCodec",e.videoFirCountSent="videoFirCountSent",e.videoPliCountSent="videoPliCountSent",e.videoDecodeLatency="videoDecodeLatency",e.videoRecvFrames="videoRecvFrames",e.videoRecvFps="videoRecvFps",e.videoRecvAvgJitterBufferDelay="videoRecvAvgJitterBufferDelay",e.videoRecvBitrates="videoRecvBitrates",e.videoRecvBytes="videoRecvBytes",e.videoRecvHeight="videoRecvHeight",e.videoRecvJitter="videoRecvJitter",e.videoRecvRoundTripTime="videoRecvRoundTripTime",e.videoRecvPackets="videoRecvPackets",e.videoRecvLostPackets="videoRecvLostPackets",e.videoRecvPacketsLost="videoRecvPacketsLost",e.videoRecvPacketsLossRate="videoRecvPacketsLossRate",e.videoRecvRetransmittedPackets="videoRecvRetransmittedPackets",e.videoRecvNackCountSent="videoRecvNackCountSent",e.videoRecvWidth="videoRecvWidth",e.videoRecvFrameRate="videoRecvFrameRate",e.videoTotalFreezesDuration="videoTotalFreezesDuration",e.videoRecvEndToEndDelay="videoRecvEndToEndDelay",e.screenRecvCodec="screenRecvCodec",e.screenFirCountSent="screenFirCountSent",e.screenPliCountSent="screenPliCountSent",e.screenDecodeLatency="screenDecodeLatency",e.screenRecvFrames="screenRecvFrames",e.screenRecvFps="screenRecvFps",e.screenRecvAvgJitterBufferDelay="screenRecvAvgJitterBufferDelay",e.screenRecvBitrates="screenRecvBitrates",e.screenRecvBytes="screenRecvBytes",e.screenRecvHeight="screenRecvHeight",e.screenRecvJitter="screenRecvJitter",e.screenRecvRoundTripTime="screenRecvRoundTripTime",e.screenRecvPackets="screenRecvPackets",e.screenRecvLostPackets="screenRecvLostPackets",e.screenRecvPacketsLost="screenRecvPacketsLost",e.screenRecvPacketsLossRate="screenRecvPacketsLossRate",e.screenRecvRetransmittedPackets="screenRecvRetransmittedPackets",e.screenRecvNackCountSent="screenRecvNackCountSent",e.screenRecvWidth="screenRecvWidth",e.screenRecvFrameRate="screenRecvFrameRate",e.screenTotalFreezesDuration="screenTotalFreezesDuration",e.screenRecvEndToEndDelay="screenRecvEndToEndDelay",e.transportSentAvailableOutgoingBitrate="transportSentAvailableOutgoingBitrate"}(n||(t.RtcStatsMetricNames=n={}));const c=Object.keys(n);function l(e,t,s,a){(0,r.default)(c.includes(t),`Unknown stat name: ${t}`),void 0!==a&&(e[t]||(e[t]={}),e[t][s]=a)}function d({pageIndex:e,trackId:t,hostName:s,codec:a,participantName:r}){return[e??"",r||"",s||"unknown",a||"",t||""].join(":")}},7618:e=>{e.exports=require("convict-format-with-validator")},7648:e=>{e.exports=require("pidusage")},8034:e=>{e.exports=require("puppeteer-core")},8219:e=>{e.exports=require("puppeteer-intercept-and-modify-requests")},8611:e=>{e.exports=require("http")},8938:e=>{e.exports=require("axios")},9026:e=>{e.exports=require("lorem-ipsum")},9278:e=>{e.exports=require("net")},9896:e=>{e.exports=require("fs")},9993:e=>{e.exports=require("puppeteer-extra-plugin-stealth/evasions/navigator.hardwareConcurrency")}},t={};function s(a){var r=t[a];if(void 0!==r)return r.exports;var i=t[a]={id:a,loaded:!1,exports:{}};return e[a].call(i.exports,i,i.exports,s),i.loaded=!0,i.exports}s.c=t,s.nmd=e=>(e.paths=[],e.children||(e.children=[]),e);s(s.s=1859)})();