UNPKG

@react-native/debugger-frontend

Version:
2 lines (1 loc) • 15.8 kB
import*as t from"../core/core.js";import*as e from"../graph/graph.js";const o=1460;class i{warmed;ssl;h2;rtt;throughput;serverLatency;_congestionWindow;h2OverflowBytesDownloaded;constructor(t,e,o=0,i=!0,s=!1){this.warmed=!1,this.ssl=i,this.h2=s,this.rtt=t,this.throughput=e,this.serverLatency=o,this._congestionWindow=10,this.h2OverflowBytesDownloaded=0}static maximumSaturatedConnections(t,e){const o=8*(1460*(1e3/t));return Math.floor(e/o)}computeMaximumCongestionWindowInSegments(){const t=this.throughput/8*(this.rtt/1e3);return Math.floor(t/o)}setThroughput(t){this.throughput=t}setCongestionWindow(t){this._congestionWindow=t}setWarmed(t){this.warmed=t}isH2(){return this.h2}get congestionWindow(){return this._congestionWindow}setH2OverflowBytesDownloaded(t){this.h2&&(this.h2OverflowBytesDownloaded=t)}clone(){return Object.assign(new i(this.rtt,this.throughput),this)}simulateDownloadUntil(t,e){const{timeAlreadyElapsed:i=0,maximumTimeToElapse:s=1/0,dnsResolutionTime:n=0}=e||{};this.warmed&&this.h2&&(t-=this.h2OverflowBytesDownloaded);const r=this.rtt,a=r/2,d=this.computeMaximumCongestionWindowInSegments();let h=a;this.warmed||(h=n+a+a+a+(this.ssl?r:0));let u=Math.ceil(h/r),l=h+this.serverLatency+a;this.warmed&&this.h2&&(l=0);const m=Math.max(l-i,0),c=s-m;let p=Math.min(this._congestionWindow,d),g=0;m>0?g=p*o:u=0;let w=0,T=t-g;for(;T>0&&w<=c;){u++,w+=r,p=Math.max(Math.min(d,2*p),1);const t=p*o;g+=t,T-=t}const y=m+w,N=this.h2?Math.max(g-t,0):0,f=Math.max(Math.min(g,t),0);let R;return R=this.warmed?this.h2?{timeToFirstByte:l}:{connectionTime:h,timeToFirstByte:l}:{dnsResolutionTime:n,connectionTime:h-n,sslTime:this.ssl?r:void 0,timeToFirstByte:l},{roundTrips:u,timeElapsed:y,bytesDownloaded:f,extraBytesDownloaded:N,congestionWindow:p,connectionTiming:R}}}const s=["https","wss"];class n{options;records;connectionsByOrigin;connectionsByRequest;_connectionsInUse;connectionReusedByRequestId;constructor(e,o){this.options=o,this.records=e,this.connectionsByOrigin=new Map,this.connectionsByRequest=new Map,this._connectionsInUse=new Set,this.connectionReusedByRequestId=t.NetworkAnalyzer.estimateIfConnectionWasReused(e,{forceCoarseEstimates:!0}),this.initializeConnections()}connectionsInUse(){return Array.from(this._connectionsInUse)}initializeConnections(){const e=this.connectionReusedByRequestId,o=this.options.additionalRttByOrigin,n=this.options.serverResponseTimeByOrigin,r=t.NetworkAnalyzer.groupByOrigin(this.records);for(const[a,d]of r.entries()){const r=[],h=o.get(a)||0,u=n.get(a)||30;for(const t of d){if(e.get(t.requestId))continue;const o=s.includes(t.parsedURL.scheme),n="h2"===t.protocol,a=new i(this.options.rtt+h,this.options.throughput,u,o,n);r.push(a)}if(!r.length)throw new t.LanternError(`Could not find a connection for origin: ${a}`);const l=r[0].isH2()?1:6;for(;r.length<l;)r.push(r[0].clone());this.connectionsByOrigin.set(a,r)}}findAvailableConnectionWithLargestCongestionWindow(t){let e=null;for(let o=0;o<t.length;o++){const i=t[o];if(this._connectionsInUse.has(i))continue;const s=e?.congestionWindow||-1/0;i.congestionWindow>s&&(e=i)}return e}acquire(e){if(this.connectionsByRequest.has(e))throw new t.LanternError("Record already has a connection");const o=e.parsedURL.securityOrigin,i=this.connectionsByOrigin.get(o)||[],s=this.findAvailableConnectionWithLargestCongestionWindow(i);return s?(this._connectionsInUse.add(s),this.connectionsByRequest.set(e,s),s):null}acquireActiveConnectionFromRequest(e){const o=this.connectionsByRequest.get(e);if(!o)throw new t.LanternError("Could not find an active connection for request");return o}release(t){const e=this.connectionsByRequest.get(t);this.connectionsByRequest.delete(t),e&&this._connectionsInUse.delete(e)}}const r=3.75,a=.9,d={throttling:{DEVTOOLS_RTT_ADJUSTMENT_FACTOR:r,DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR:a,mobileSlow4G:{rttMs:150,throughputKbps:1638.4,requestLatencyMs:562.5,downloadThroughputKbps:1474.5600000000002,uploadThroughputKbps:675,cpuSlowdownMultiplier:4},mobileRegular3G:{rttMs:300,throughputKbps:700,requestLatencyMs:1125,downloadThroughputKbps:630,uploadThroughputKbps:630,cpuSlowdownMultiplier:4},desktopDense4G:{rttMs:40,throughputKbps:10240,cpuSlowdownMultiplier:1,requestLatencyMs:0,downloadThroughputKbps:0,uploadThroughputKbps:0}}};class h{static rttMultiplier=2;rtt;resolvedDomainNames;constructor({rtt:t}){this.rtt=t,this.resolvedDomainNames=new Map}getTimeUntilResolution(t,e){const{requestedAt:o=0,shouldUpdateCache:i=!1}=e||{},s=t.parsedURL.host,n=this.resolvedDomainNames.get(s);let r=this.rtt*h.rttMultiplier;if(n){const t=Math.max(n.resolvedAt-o,0);r=Math.min(t,r)}const a=o+r;return i&&this.updateCacheResolvedAtIfNeeded(t,a),r}updateCacheResolvedAtIfNeeded(t,e){const o=t.parsedURL.host,i=this.resolvedDomainNames.get(o)||{resolvedAt:e};i.resolvedAt=Math.min(i.resolvedAt,e),this.resolvedDomainNames.set(o,i)}setResolvedAt(t,e){this.resolvedDomainNames.set(t,{resolvedAt:e})}}class u{nodeTimings;constructor(){this.nodeTimings=new Map}getNodes(){return Array.from(this.nodeTimings.keys())}setReadyToStart(t,e){this.nodeTimings.set(t,e)}setInProgress(t,o){const i={...this.getQueued(t),startTime:o.startTime,timeElapsed:0};this.nodeTimings.set(t,t.type===e.BaseNode.types.NETWORK?{...i,timeElapsedOvershoot:0,bytesDownloaded:0}:i)}setCompleted(t,e){const o={...this.getInProgress(t),endTime:e.endTime,connectionTiming:e.connectionTiming};this.nodeTimings.set(t,o)}setCpu(t,e){const o={...this.getCpuStarted(t),timeElapsed:e.timeElapsed};this.nodeTimings.set(t,o)}setCpuEstimated(t,e){const o={...this.getCpuStarted(t),estimatedTimeElapsed:e.estimatedTimeElapsed};this.nodeTimings.set(t,o)}setNetwork(t,e){const o={...this.getNetworkStarted(t),timeElapsed:e.timeElapsed,timeElapsedOvershoot:e.timeElapsedOvershoot,bytesDownloaded:e.bytesDownloaded};this.nodeTimings.set(t,o)}setNetworkEstimated(t,e){const o={...this.getNetworkStarted(t),estimatedTimeElapsed:e.estimatedTimeElapsed};this.nodeTimings.set(t,o)}getQueued(e){const o=this.nodeTimings.get(e);if(!o)throw new t.LanternError(`Node ${e.id} not yet queued`);return o}getCpuStarted(e){const o=this.nodeTimings.get(e);if(!o)throw new t.LanternError(`Node ${e.id} not yet queued`);if(!("startTime"in o))throw new t.LanternError(`Node ${e.id} not yet started`);if("bytesDownloaded"in o)throw new t.LanternError(`Node ${e.id} timing not valid`);return o}getNetworkStarted(e){const o=this.nodeTimings.get(e);if(!o)throw new t.LanternError(`Node ${e.id} not yet queued`);if(!("startTime"in o))throw new t.LanternError(`Node ${e.id} not yet started`);if(!("bytesDownloaded"in o))throw new t.LanternError(`Node ${e.id} timing not valid`);return o}getInProgress(e){const o=this.nodeTimings.get(e);if(!o)throw new t.LanternError(`Node ${e.id} not yet queued`);if(!("startTime"in o))throw new t.LanternError(`Node ${e.id} not yet started`);if(!("estimatedTimeElapsed"in o))throw new t.LanternError(`Node ${e.id} not yet in progress`);return o}getCompleted(e){const o=this.nodeTimings.get(e);if(!o)throw new t.LanternError(`Node ${e.id} not yet queued`);if(!("startTime"in o))throw new t.LanternError(`Node ${e.id} not yet started`);if(!("estimatedTimeElapsed"in o))throw new t.LanternError(`Node ${e.id} not yet in progress`);if(!("endTime"in o))throw new t.LanternError(`Node ${e.id} not yet completed`);return o}}const l=d.throttling.mobileSlow4G,m={NotReadyToStart:0,ReadyToStart:1,InProgress:2,Complete:3},c={VeryHigh:0,High:.25,Medium:.5,Low:1,VeryLow:2},p=new Map;class g{static createSimulator(t){const{throttlingMethod:e,throttling:o,precomputedLanternData:i,networkAnalysis:s}=t,n={additionalRttByOrigin:s.additionalRttByOrigin,serverResponseTimeByOrigin:s.serverResponseTimeByOrigin,observedThroughput:s.throughput};switch(i&&(n.additionalRttByOrigin=new Map(Object.entries(i.additionalRttByOrigin)),n.serverResponseTimeByOrigin=new Map(Object.entries(i.serverResponseTimeByOrigin))),e){case"provided":n.rtt=s.rtt,n.throughput=s.throughput,n.cpuSlowdownMultiplier=1,n.layoutTaskMultiplier=1;break;case"devtools":o&&(n.rtt=o.requestLatencyMs/d.throttling.DEVTOOLS_RTT_ADJUSTMENT_FACTOR,n.throughput=1024*o.downloadThroughputKbps/d.throttling.DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR),n.cpuSlowdownMultiplier=1,n.layoutTaskMultiplier=1;break;case"simulate":o&&(n.rtt=o.rttMs,n.throughput=1024*o.throughputKbps,n.cpuSlowdownMultiplier=o.cpuSlowdownMultiplier)}return new g(n)}options;_rtt;throughput;maximumConcurrentRequests;cpuSlowdownMultiplier;layoutTaskMultiplier;cachedNodeListByStartPosition;nodeTimings;numberInProgressByType;nodes;dns;connectionPool;constructor(e){if(this.options=Object.assign({rtt:l.rttMs,throughput:1024*l.throughputKbps,maximumConcurrentRequests:10,cpuSlowdownMultiplier:l.cpuSlowdownMultiplier,layoutTaskMultiplier:.5,additionalRttByOrigin:new Map,serverResponseTimeByOrigin:new Map},e),this._rtt=this.options.rtt,this.throughput=this.options.throughput,this.maximumConcurrentRequests=Math.max(Math.min(i.maximumSaturatedConnections(this._rtt,this.throughput),this.options.maximumConcurrentRequests),1),this.cpuSlowdownMultiplier=this.options.cpuSlowdownMultiplier,this.layoutTaskMultiplier=this.cpuSlowdownMultiplier*this.options.layoutTaskMultiplier,this.cachedNodeListByStartPosition=[],this.nodeTimings=new u,this.numberInProgressByType=new Map,this.nodes={},this.dns=new h({rtt:this._rtt}),this.connectionPool=null,!Number.isFinite(this._rtt))throw new t.LanternError(`Invalid rtt ${this._rtt}`);if(!Number.isFinite(this.throughput))throw new t.LanternError(`Invalid throughput ${this.throughput}`)}get rtt(){return this._rtt}initializeConnectionPool(t){const o=[];t.getRootNode().traverse((t=>{t.type===e.BaseNode.types.NETWORK&&o.push(t.request)})),this.connectionPool=new n(o,this.options)}initializeAuxiliaryData(){this.nodeTimings=new u,this.numberInProgressByType=new Map,this.nodes={},this.cachedNodeListByStartPosition=[];for(const t of Object.values(m))this.nodes[t]=new Set}numberInProgress(t){return this.numberInProgressByType.get(t)||0}markNodeAsReadyToStart(t,e){const o=g.computeNodeStartPosition(t),i=this.cachedNodeListByStartPosition.findIndex((t=>g.computeNodeStartPosition(t)>o)),s=-1===i?this.cachedNodeListByStartPosition.length:i;this.cachedNodeListByStartPosition.splice(s,0,t),this.nodes[m.ReadyToStart].add(t),this.nodes[m.NotReadyToStart].delete(t),this.nodeTimings.setReadyToStart(t,{queuedTime:e})}markNodeAsInProgress(t,e){const o=this.cachedNodeListByStartPosition.indexOf(t);this.cachedNodeListByStartPosition.splice(o,1),this.nodes[m.InProgress].add(t),this.nodes[m.ReadyToStart].delete(t),this.numberInProgressByType.set(t.type,this.numberInProgress(t.type)+1),this.nodeTimings.setInProgress(t,{startTime:e})}markNodeAsComplete(t,e,o){this.nodes[m.Complete].add(t),this.nodes[m.InProgress].delete(t),this.numberInProgressByType.set(t.type,this.numberInProgress(t.type)-1),this.nodeTimings.setCompleted(t,{endTime:e,connectionTiming:o});for(const o of t.getDependents()){o.getDependencies().some((t=>!this.nodes[m.Complete].has(t)))||this.markNodeAsReadyToStart(o,e)}}acquireConnection(t){return this.connectionPool.acquire(t)}getNodesSortedByStartPosition(){return Array.from(this.cachedNodeListByStartPosition)}startNodeIfPossible(o,i){if(o.type!==e.BaseNode.types.CPU){if(o.type!==e.BaseNode.types.NETWORK)throw new t.LanternError("Unsupported");if(!o.isConnectionless){if(this.numberInProgress(o.type)>=this.maximumConcurrentRequests)return;if(!this.acquireConnection(o.request))return}this.markNodeAsInProgress(o,i)}else 0===this.numberInProgress(o.type)&&this.markNodeAsInProgress(o,i)}updateNetworkCapacity(){const t=this.numberInProgress(e.BaseNode.types.NETWORK);if(0!==t)for(const e of this.connectionPool.connectionsInUse())e.setThroughput(this.throughput/t)}estimateTimeRemaining(o){if(o.type===e.BaseNode.types.CPU)return this.estimateCPUTimeRemaining(o);if(o.type===e.BaseNode.types.NETWORK)return this.estimateNetworkTimeRemaining(o);throw new t.LanternError("Unsupported")}estimateCPUTimeRemaining(t){const e=this.nodeTimings.getCpuStarted(t),o=t.didPerformLayout()?this.layoutTaskMultiplier:this.cpuSlowdownMultiplier,i=Math.min(Math.round(t.duration/1e3*o),1e4)-e.timeElapsed;return this.nodeTimings.setCpuEstimated(t,{estimatedTimeElapsed:i}),i}estimateNetworkTimeRemaining(t){const e=t.request,o=this.nodeTimings.getNetworkStarted(t);let i=0;if(t.fromDiskCache){i=8+20*((e.resourceSize||0)/1024/1024)-o.timeElapsed}else if(t.isNonNetworkProtocol){i=2+10*((e.resourceSize||0)/1024/1024)-o.timeElapsed}else{const t=this.connectionPool.acquireActiveConnectionFromRequest(e),s=this.dns.getTimeUntilResolution(e,{requestedAt:o.startTime,shouldUpdateCache:!0}),n=o.timeElapsed;i=t.simulateDownloadUntil(e.transferSize-o.bytesDownloaded,{timeAlreadyElapsed:n,dnsResolutionTime:s,maximumTimeToElapse:1/0}).timeElapsed}const s=i+o.timeElapsedOvershoot;return this.nodeTimings.setNetworkEstimated(t,{estimatedTimeElapsed:s}),s}findNextNodeCompletionTime(){let t=1/0;for(const e of this.nodes[m.InProgress])t=Math.min(t,this.estimateTimeRemaining(e));return t}updateProgressMadeInTimePeriod(o,i,s){const n=this.nodeTimings.getInProgress(o),r=n.estimatedTimeElapsed===i;if(o.type===e.BaseNode.types.CPU||o.isConnectionless)return void(r?this.markNodeAsComplete(o,s):n.timeElapsed+=i);if(o.type!==e.BaseNode.types.NETWORK)throw new t.LanternError("Unsupported");if(!("bytesDownloaded"in n))throw new t.LanternError("Invalid timing data");const a=o.request,d=this.connectionPool.acquireActiveConnectionFromRequest(a),h=this.dns.getTimeUntilResolution(a,{requestedAt:n.startTime,shouldUpdateCache:!0}),u=d.simulateDownloadUntil(a.transferSize-n.bytesDownloaded,{dnsResolutionTime:h,timeAlreadyElapsed:n.timeElapsed,maximumTimeToElapse:i-n.timeElapsedOvershoot});d.setCongestionWindow(u.congestionWindow),d.setH2OverflowBytesDownloaded(u.extraBytesDownloaded),r?(d.setWarmed(!0),this.connectionPool.release(a),this.markNodeAsComplete(o,s,u.connectionTiming)):(n.timeElapsed+=u.timeElapsed,n.timeElapsedOvershoot+=u.timeElapsed-i,n.bytesDownloaded+=u.bytesDownloaded)}computeFinalNodeTimings(){const t=this.nodeTimings.getNodes().map((t=>[t,this.nodeTimings.getCompleted(t)]));t.sort(((t,e)=>t[1].startTime-e[1].startTime));const e=t.map((([t,e])=>[t,{startTime:e.startTime,endTime:e.endTime,duration:e.endTime-e.startTime}]));return{nodeTimings:new Map(e),completeNodeTimings:new Map(t)}}getOptions(){return this.options}simulate(o,i){if(e.BaseNode.hasCycle(o))throw new t.LanternError("Cannot simulate graph with cycle");i=Object.assign({label:void 0},i),this.dns=new h({rtt:this._rtt}),this.initializeConnectionPool(o),this.initializeAuxiliaryData();const s=this.nodes[m.NotReadyToStart],n=this.nodes[m.ReadyToStart],r=this.nodes[m.InProgress],a=o.getRootNode();a.traverse((t=>s.add(t)));let d=0,u=0;for(this.markNodeAsReadyToStart(a,d);n.size||r.size;){for(const t of this.getNodesSortedByStartPosition())this.startNodeIfPossible(t,d);if(!r.size)throw new t.LanternError("Failed to start a node");this.updateNetworkCapacity();const e=this.findNextNodeCompletionTime();if(d+=e,!Number.isFinite(e)||u>1e5)throw new t.LanternError("Simulation failed, depth exceeded");u++;for(const t of r)this.updateProgressMadeInTimePeriod(t,e,d)}const{nodeTimings:l,completeNodeTimings:c}=this.computeFinalNodeTimings();return p.set(i.label||"unlabeled",c),{timeInMs:d,nodeTimings:l}}computeWastedMsFromWastedBytes(t){const{throughput:e,observedThroughput:o}=this.options,i=0===e?o:e;if(0===i)return 0;const s=8*t/i*1e3;return 10*Math.round(s/10)}static get allNodeTimings(){return p}static computeNodeStartPosition(t){return"cpu"===t.type?t.startTime:t.startTime+(1e3*c[t.request.priority]*1e3||0)}}export{n as ConnectionPool,d as Constants,h as DNSCache,g as Simulator,u as SimulatorTimingMap,i as TCPConnection};