@react-native/debugger-frontend
Version:
Debugger frontend for React Native based on Chrome DevTools
2 lines (1 loc) • 16 kB
JavaScript
import*as t from"../core/core.js";import*as e from"../graph/graph.js";const i=1460;class o{_warmed;_ssl;_h2;_rtt;_throughput;_serverLatency;_congestionWindow;_h2OverflowBytesDownloaded;constructor(t,e,i=0,o=!0,s=!1){this._warmed=!1,this._ssl=o,this._h2=s,this._rtt=t,this._throughput=e,this._serverLatency=i,this._congestionWindow=10,this._h2OverflowBytesDownloaded=0}static maximumSaturatedConnections(t,e){const i=8*(1460*(1e3/t));return Math.floor(e/i)}_computeMaximumCongestionWindowInSegments(){const t=this._throughput/8*(this._rtt/1e3);return Math.floor(t/i)}setThroughput(t){this._throughput=t}setCongestionWindow(t){this._congestionWindow=t}setWarmed(t){this._warmed=t}isWarm(){return this._warmed}isH2(){return this._h2}get congestionWindow(){return this._congestionWindow}setH2OverflowBytesDownloaded(t){this._h2&&(this._h2OverflowBytesDownloaded=t)}clone(){return Object.assign(new o(this._rtt,this._throughput),this)}simulateDownloadUntil(t,e){const{timeAlreadyElapsed:o=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-o,0),c=s-m;let p=Math.min(this._congestionWindow,d),_=0;m>0?_=p*i:u=0;let g=0,w=t-_;for(;w>0&&g<=c;){u++,g+=r,p=Math.max(Math.min(d,2*p),1);const t=p*i;_+=t,w-=t}const T=m+g,y=this._h2?Math.max(_-t,0):0,N=Math.max(Math.min(_,t),0);let f;return f=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:T,bytesDownloaded:N,extraBytesDownloaded:y,congestionWindow:p,connectionTiming:f}}}const s=["https","wss"];class n{_options;_records;_connectionsByOrigin;_connectionsByRequest;_connectionsInUse;_connectionReusedByRequestId;constructor(e,i){this._options=i,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,i=this._options.additionalRttByOrigin,n=this._options.serverResponseTimeByOrigin,r=t.NetworkAnalyzer.groupByOrigin(this._records);for(const[a,d]of r.entries()){const r=[],h=i.get(a)||0,u=n.get(a)||30;for(const t of d){if(e.get(t.requestId))continue;const i=s.includes(t.parsedURL.scheme),n="h2"===t.protocol,a=new o(this._options.rtt+h,this._options.throughput,u,i,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 i=0;i<t.length;i++){const o=t[i];if(this._connectionsInUse.has(o))continue;const s=e?.congestionWindow||-1/0;o.congestionWindow>s&&(e=o)}return e}acquire(e){if(this._connectionsByRequest.has(e))throw new t.LanternError("Record already has a connection");const i=e.parsedURL.securityOrigin,o=this._connectionsByOrigin.get(i)||[],s=this._findAvailableConnectionWithLargestCongestionWindow(o);return s?(this._connectionsInUse.add(s),this._connectionsByRequest.set(e,s),s):null}acquireActiveConnectionFromRequest(e){const i=this._connectionsByRequest.get(e);if(!i)throw new t.LanternError("Could not find an active connection for request");return i}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:i=0,shouldUpdateCache:o=!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-i,0);r=Math.min(t,r)}const a=i+r;return o&&this._updateCacheResolvedAtIfNeeded(t,a),r}_updateCacheResolvedAtIfNeeded(t,e){const i=t.parsedURL.host,o=this._resolvedDomainNames.get(i)||{resolvedAt:e};o.resolvedAt=Math.min(o.resolvedAt,e),this._resolvedDomainNames.set(i,o)}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,i){const o={...this.getQueued(t),startTime:i.startTime,timeElapsed:0};this._nodeTimings.set(t,t.type===e.BaseNode.types.NETWORK?{...o,timeElapsedOvershoot:0,bytesDownloaded:0}:o)}setCompleted(t,e){const i={...this.getInProgress(t),endTime:e.endTime,connectionTiming:e.connectionTiming};this._nodeTimings.set(t,i)}setCpu(t,e){const i={...this.getCpuStarted(t),timeElapsed:e.timeElapsed};this._nodeTimings.set(t,i)}setCpuEstimated(t,e){const i={...this.getCpuStarted(t),estimatedTimeElapsed:e.estimatedTimeElapsed};this._nodeTimings.set(t,i)}setNetwork(t,e){const i={...this.getNetworkStarted(t),timeElapsed:e.timeElapsed,timeElapsedOvershoot:e.timeElapsedOvershoot,bytesDownloaded:e.bytesDownloaded};this._nodeTimings.set(t,i)}setNetworkEstimated(t,e){const i={...this.getNetworkStarted(t),estimatedTimeElapsed:e.estimatedTimeElapsed};this._nodeTimings.set(t,i)}getQueued(e){const i=this._nodeTimings.get(e);if(!i)throw new t.LanternError(`Node ${e.id} not yet queued`);return i}getCpuStarted(e){const i=this._nodeTimings.get(e);if(!i)throw new t.LanternError(`Node ${e.id} not yet queued`);if(!("startTime"in i))throw new t.LanternError(`Node ${e.id} not yet started`);if("bytesDownloaded"in i)throw new t.LanternError(`Node ${e.id} timing not valid`);return i}getNetworkStarted(e){const i=this._nodeTimings.get(e);if(!i)throw new t.LanternError(`Node ${e.id} not yet queued`);if(!("startTime"in i))throw new t.LanternError(`Node ${e.id} not yet started`);if(!("bytesDownloaded"in i))throw new t.LanternError(`Node ${e.id} timing not valid`);return i}getInProgress(e){const i=this._nodeTimings.get(e);if(!i)throw new t.LanternError(`Node ${e.id} not yet queued`);if(!("startTime"in i))throw new t.LanternError(`Node ${e.id} not yet started`);if(!("estimatedTimeElapsed"in i))throw new t.LanternError(`Node ${e.id} not yet in progress`);return i}getCompleted(e){const i=this._nodeTimings.get(e);if(!i)throw new t.LanternError(`Node ${e.id} not yet queued`);if(!("startTime"in i))throw new t.LanternError(`Node ${e.id} not yet started`);if(!("estimatedTimeElapsed"in i))throw new t.LanternError(`Node ${e.id} not yet in progress`);if(!("endTime"in i))throw new t.LanternError(`Node ${e.id} not yet completed`);return i}}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 _{static createSimulator(t){const{throttlingMethod:e,throttling:i,precomputedLanternData:o,networkAnalysis:s}=t,n={additionalRttByOrigin:s.additionalRttByOrigin,serverResponseTimeByOrigin:s.serverResponseTimeByOrigin,observedThroughput:s.throughput};switch(o&&(n.additionalRttByOrigin=new Map(Object.entries(o.additionalRttByOrigin)),n.serverResponseTimeByOrigin=new Map(Object.entries(o.serverResponseTimeByOrigin))),e){case"provided":n.rtt=s.rtt,n.throughput=s.throughput,n.cpuSlowdownMultiplier=1,n.layoutTaskMultiplier=1;break;case"devtools":i&&(n.rtt=i.requestLatencyMs/d.throttling.DEVTOOLS_RTT_ADJUSTMENT_FACTOR,n.throughput=1024*i.downloadThroughputKbps/d.throttling.DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR),n.cpuSlowdownMultiplier=1,n.layoutTaskMultiplier=1;break;case"simulate":i&&(n.rtt=i.rttMs,n.throughput=1024*i.throughputKbps,n.cpuSlowdownMultiplier=i.cpuSlowdownMultiplier)}return new _(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(o.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 rtt ${this._throughput}`)}get rtt(){return this._rtt}_initializeConnectionPool(t){const i=[];t.getRootNode().traverse((t=>{t.type===e.BaseNode.types.NETWORK&&i.push(t.request)})),this._connectionPool=new n(i,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 i=_._computeNodeStartPosition(t),o=this._cachedNodeListByStartPosition.findIndex((t=>_._computeNodeStartPosition(t)>i)),s=-1===o?this._cachedNodeListByStartPosition.length:o;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 i=this._cachedNodeListByStartPosition.indexOf(t);this._cachedNodeListByStartPosition.splice(i,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,i){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:i});for(const i of t.getDependents()){i.getDependencies().some((t=>!this._nodes[m.Complete].has(t)))||this._markNodeAsReadyToStart(i,e)}}_acquireConnection(t){return this._connectionPool.acquire(t)}_getNodesSortedByStartPosition(){return Array.from(this._cachedNodeListByStartPosition)}_startNodeIfPossible(i,o){if(i.type!==e.BaseNode.types.CPU){if(i.type!==e.BaseNode.types.NETWORK)throw new t.LanternError("Unsupported");if(!i.isConnectionless){if(this._numberInProgress(i.type)>=this._maximumConcurrentRequests)return;if(!this._acquireConnection(i.request))return}this._markNodeAsInProgress(i,o)}else 0===this._numberInProgress(i.type)&&this._markNodeAsInProgress(i,o)}_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(i){if(i.type===e.BaseNode.types.CPU)return this._estimateCPUTimeRemaining(i);if(i.type===e.BaseNode.types.NETWORK)return this._estimateNetworkTimeRemaining(i);throw new t.LanternError("Unsupported")}_estimateCPUTimeRemaining(t){const e=this._nodeTimings.getCpuStarted(t),i=t.didPerformLayout()?this._layoutTaskMultiplier:this._cpuSlowdownMultiplier,o=Math.min(Math.round(t.duration/1e3*i),1e4)-e.timeElapsed;return this._nodeTimings.setCpuEstimated(t,{estimatedTimeElapsed:o}),o}_estimateNetworkTimeRemaining(t){const e=t.request,i=this._nodeTimings.getNetworkStarted(t);let o=0;if(t.fromDiskCache){o=8+20*((e.resourceSize||0)/1024/1024)-i.timeElapsed}else if(t.isNonNetworkProtocol){o=2+10*((e.resourceSize||0)/1024/1024)-i.timeElapsed}else{const t=this._connectionPool.acquireActiveConnectionFromRequest(e),s=this._dns.getTimeUntilResolution(e,{requestedAt:i.startTime,shouldUpdateCache:!0}),n=i.timeElapsed;o=t.simulateDownloadUntil(e.transferSize-i.bytesDownloaded,{timeAlreadyElapsed:n,dnsResolutionTime:s,maximumTimeToElapse:1/0}).timeElapsed}const s=o+i.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(i,o,s){const n=this._nodeTimings.getInProgress(i),r=n.estimatedTimeElapsed===o;if(i.type===e.BaseNode.types.CPU||i.isConnectionless)return void(r?this._markNodeAsComplete(i,s):n.timeElapsed+=o);if(i.type!==e.BaseNode.types.NETWORK)throw new t.LanternError("Unsupported");if(!("bytesDownloaded"in n))throw new t.LanternError("Invalid timing data");const a=i.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:o-n.timeElapsedOvershoot});d.setCongestionWindow(u.congestionWindow),d.setH2OverflowBytesDownloaded(u.extraBytesDownloaded),r?(d.setWarmed(!0),this._connectionPool.release(a),this._markNodeAsComplete(i,s,u.connectionTiming)):(n.timeElapsed+=u.timeElapsed,n.timeElapsedOvershoot+=u.timeElapsed-o,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(i,o){if(e.BaseNode.hasCycle(i))throw new t.LanternError("Cannot simulate graph with cycle");o=Object.assign({label:void 0},o),this._dns=new h({rtt:this._rtt}),this._initializeConnectionPool(i),this._initializeAuxiliaryData();const s=this._nodes[m.NotReadyToStart],n=this._nodes[m.ReadyToStart],r=this._nodes[m.InProgress],a=i.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(o.label||"unlabeled",c),{timeInMs:d,nodeTimings:l}}computeWastedMsFromWastedBytes(t){const{throughput:e,observedThroughput:i}=this._options,o=0===e?i:e;if(0===o)return 0;const s=8*t/o*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,_ as Simulator,u as SimulatorTimingMap,o as TCPConnection};