import{UIString,Color,ObjectWrapper,Settings}from'../common/common.js';import{Platform}from'../host/host.js';import{NumberUtilities}from'../platform/platform.js';import{SDKModel,PerformanceMetricsModel}from'../sdk/sdk.js';import{Widget,UIUtils,ARIAUtils,Icon}from'../ui/ui.js';class PerformanceMonitorImpl extends Widget.HBox{constructor(){super(true);this.registerRequiredCSS('performance_monitor/performanceMonitor.css');this.contentElement.classList.add('perfmon-pane');this._metricsBuffer=[];this._pixelsPerMs=10/1000;this._pollIntervalMs=500;this._scaleHeight=16;this._graphHeight=90;this._gridColor=self.UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.08)',UIUtils.ThemeSupport.ColorUsage.Foreground);this._controlPane=new ControlPane(this.contentElement);const chartContainer=this.contentElement.createChild('div','perfmon-chart-container');this._canvas=(chartContainer.createChild('canvas'));this._canvas.tabIndex=-1;ARIAUtils.setAccessibleName(this._canvas,UIString.UIString('Graphs displaying a real-time view of performance metrics'));this.contentElement.createChild('div','perfmon-chart-suspend-overlay fill').createChild('div').textContent=UIString.UIString('Paused');this._controlPane.addEventListener(Events.MetricChanged,this._recalcChartHeight,this);SDKModel.TargetManager.instance().observeModels(PerformanceMetricsModel.PerformanceMetricsModel,this);}
wasShown(){if(!this._model){return;}
SDKModel.TargetManager.instance().addEventListener(SDKModel.Events.SuspendStateChanged,this._suspendStateChanged,this);this._model.enable();this._suspendStateChanged();}
willHide(){if(!this._model){return;}
SDKModel.TargetManager.instance().removeEventListener(SDKModel.Events.SuspendStateChanged,this._suspendStateChanged,this);this._stopPolling();this._model.disable();}
modelAdded(model){if(this._model){return;}
this._model=model;if(this.isShowing()){this.wasShown();}}
modelRemoved(model){if(this._model!==model){return;}
if(this.isShowing()){this.willHide();}
this._model=null;}
_suspendStateChanged(){const suspended=SDKModel.TargetManager.instance().allTargetsSuspended();if(suspended){this._stopPolling();}else{this._startPolling();}
this.contentElement.classList.toggle('suspended',suspended);}
_startPolling(){this._startTimestamp=0;this._pollTimer=setInterval(()=>this._poll(),this._pollIntervalMs);this.onResize();animate.call(this);function animate(){this._draw();this._animationId=this.contentElement.window().requestAnimationFrame(animate.bind(this));}}
_stopPolling(){clearInterval(this._pollTimer);this.contentElement.window().cancelAnimationFrame(this._animationId);this._metricsBuffer=[];}
async _poll(){const data=await this._model.requestMetrics();const timestamp=data.timestamp;const metrics=data.metrics;this._metricsBuffer.push({timestamp,metrics:metrics});const millisPerWidth=this._width/this._pixelsPerMs;const maxCount=Math.ceil(millisPerWidth/this._pollIntervalMs*2);if(this._metricsBuffer.length>maxCount*2)
{this._metricsBuffer.splice(0,this._metricsBuffer.length-maxCount);}
this._controlPane.updateMetrics(metrics);}
_draw(){const ctx=(this._canvas.getContext('2d'));ctx.save();ctx.scale(window.devicePixelRatio,window.devicePixelRatio);ctx.clearRect(0,0,this._width,this._height);ctx.save();ctx.translate(0,this._scaleHeight);for(const chartInfo of this._controlPane.charts()){if(!this._controlPane.isActive(chartInfo.metrics[0].name)){continue;}
this._drawChart(ctx,chartInfo,this._graphHeight);ctx.translate(0,this._graphHeight);}
ctx.restore();this._drawHorizontalGrid(ctx);ctx.restore();}
_drawHorizontalGrid(ctx){const labelDistanceSeconds=10;const lightGray=self.UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.02)',UIUtils.ThemeSupport.ColorUsage.Foreground);ctx.font='10px '+Platform.fontFamily();ctx.fillStyle=self.UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.55)',UIUtils.ThemeSupport.ColorUsage.Foreground);const currentTime=Date.now()/1000;for(let sec=Math.ceil(currentTime);;--sec){const x=this._width-((currentTime-sec)*1000-this._pollIntervalMs)*this._pixelsPerMs;if(x<-50){break;}
ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,this._height);if(sec>=0&&sec%labelDistanceSeconds===0){ctx.fillText(new Date(sec*1000).toLocaleTimeString(),x+4,12);}
ctx.strokeStyle=sec%labelDistanceSeconds?lightGray:this._gridColor;ctx.stroke();}}
_drawChart(ctx,chartInfo,height){ctx.save();ctx.rect(0,0,this._width,height);ctx.clip();const bottomPadding=8;const extraSpace=1.05;const max=this._calcMax(chartInfo)*extraSpace;const stackedChartBaseLandscape=chartInfo.stacked?new Map():null;const paths=[];for(let i=chartInfo.metrics.length-1;i>=0;--i){const metricInfo=chartInfo.metrics[i];paths.push({path:this._buildMetricPath(chartInfo,metricInfo,height-bottomPadding,max,i?stackedChartBaseLandscape:null),color:metricInfo.color});}
const backgroundColor=Color.Color.parse(self.UI.themeSupport.patchColorText('white',UIUtils.ThemeSupport.ColorUsage.Background));for(const path of paths.reverse()){const color=path.color;ctx.save();ctx.fillStyle=backgroundColor.blendWith(Color.Color.parse(color).setAlpha(0.2)).asString(null);ctx.fill(path.path);ctx.strokeStyle=color;ctx.lineWidth=0.5;ctx.stroke(path.path);ctx.restore();}
ctx.fillStyle=self.UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.55)',UIUtils.ThemeSupport.ColorUsage.Foreground);ctx.font=`10px  ${Platform.fontFamily()}`;ctx.fillText(chartInfo.title,8,10);this._drawVerticalGrid(ctx,height-bottomPadding,max,chartInfo);ctx.restore();}
_calcMax(chartInfo){if(chartInfo.max){return chartInfo.max;}
const width=this._width;const startTime=performance.now()-this._pollIntervalMs-width/this._pixelsPerMs;let max=-Infinity;for(const metricInfo of chartInfo.metrics){for(let i=this._metricsBuffer.length-1;i>=0;--i){const metrics=this._metricsBuffer[i];const value=metrics.metrics.get(metricInfo.name);max=Math.max(max,value);if(metrics.timestamp<startTime){break;}}}
if(!this._metricsBuffer.length){return 10;}
const base10=Math.pow(10,Math.floor(Math.log10(max)));max=Math.ceil(max/base10/2)*base10*2;const alpha=0.2;chartInfo.currentMax=max*alpha+(chartInfo.currentMax||max)*(1-alpha);return chartInfo.currentMax;}
_drawVerticalGrid(ctx,height,max,info){let base=Math.pow(10,Math.floor(Math.log10(max)));const firstDigit=Math.floor(max/base);if(firstDigit!==1&&firstDigit%2===1){base*=2;}
let scaleValue=Math.floor(max/base)*base;const span=max;const topPadding=18;const visibleHeight=height-topPadding;ctx.fillStyle=self.UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.55)',UIUtils.ThemeSupport.ColorUsage.Foreground);ctx.strokeStyle=this._gridColor;ctx.beginPath();for(let i=0;i<2;++i){const y=calcY(scaleValue);const labelText=MetricIndicator._formatNumber(scaleValue,info);ctx.moveTo(0,y);ctx.lineTo(4,y);ctx.moveTo(ctx.measureText(labelText).width+12,y);ctx.lineTo(this._width,y);ctx.fillText(labelText,8,calcY(scaleValue)+3);scaleValue/=2;}
ctx.stroke();ctx.beginPath();ctx.moveTo(0,height+0.5);ctx.lineTo(this._width,height+0.5);ctx.strokeStyle=self.UI.themeSupport.patchColorText('rgba(0, 0, 0, 0.2)',UIUtils.ThemeSupport.ColorUsage.Foreground);ctx.stroke();function calcY(value){return Math.round(height-visibleHeight*value/span)+0.5;}}
_buildMetricPath(chartInfo,metricInfo,height,scaleMax,stackedChartBaseLandscape){const path=new Path2D();const topPadding=18;const visibleHeight=height-topPadding;if(visibleHeight<1){return path;}
const span=scaleMax;const metricName=metricInfo.name;const pixelsPerMs=this._pixelsPerMs;const startTime=performance.now()-this._pollIntervalMs-this._width/pixelsPerMs;const smooth=chartInfo.smooth;let x=0;let lastY=0;let lastX=0;if(this._metricsBuffer.length){x=(this._metricsBuffer[0].timestamp-startTime)*pixelsPerMs;path.moveTo(x,calcY(0));path.lineTo(this._width+5,calcY(0));lastY=calcY(this._metricsBuffer.peekLast().metrics.get(metricName));lastX=this._width+5;path.lineTo(lastX,lastY);}
for(let i=this._metricsBuffer.length-1;i>=0;--i){const metrics=this._metricsBuffer[i];const timestamp=metrics.timestamp;let value=metrics.metrics.get(metricName);if(stackedChartBaseLandscape){value+=stackedChartBaseLandscape.get(timestamp)||0;value=NumberUtilities.clamp(value,0,1);stackedChartBaseLandscape.set(timestamp,value);}
const y=calcY(value);x=(timestamp-startTime)*pixelsPerMs;if(smooth){const midX=(lastX+x)/2;path.bezierCurveTo(midX,lastY,midX,y,x,y);}else{path.lineTo(x,lastY);path.lineTo(x,y);}
lastX=x;lastY=y;if(timestamp<startTime){break;}}
return path;function calcY(value){return Math.round(height-visibleHeight*value/span)+0.5;}}
onResize(){super.onResize();this._width=this._canvas.offsetWidth;this._canvas.width=Math.round(this._width*window.devicePixelRatio);this._recalcChartHeight();}
_recalcChartHeight(){let height=this._scaleHeight;for(const chartInfo of this._controlPane.charts()){if(this._controlPane.isActive(chartInfo.metrics[0].name)){height+=this._graphHeight;}}
this._height=Math.ceil(height*window.devicePixelRatio);this._canvas.height=this._height;this._canvas.style.height=`${this._height / window.devicePixelRatio}px`;}}
const Format={Percent:Symbol('Percent'),Bytes:Symbol('Bytes'),};class ControlPane extends ObjectWrapper.ObjectWrapper{constructor(parent){super();this.element=parent.createChild('div','perfmon-control-pane');this._enabledChartsSetting=Settings.Settings.instance().createSetting('perfmonActiveIndicators2',['TaskDuration','JSHeapTotalSize','Nodes']);this._enabledCharts=new Set(this._enabledChartsSetting.get());const format=Format;this._chartsInfo=[{title:UIString.UIString('CPU usage'),metrics:[{name:'TaskDuration',color:'#999'},{name:'ScriptDuration',color:'orange'},{name:'LayoutDuration',color:'blueviolet'},{name:'RecalcStyleDuration',color:'violet'}],format:format.Percent,smooth:true,stacked:true,color:'red',max:1},{title:UIString.UIString('JS heap size'),metrics:[{name:'JSHeapTotalSize',color:'#99f'},{name:'JSHeapUsedSize',color:'blue'}],format:format.Bytes,color:'blue'},{title:UIString.UIString('DOM Nodes'),metrics:[{name:'Nodes',color:'green'}]},{title:UIString.UIString('JS event listeners'),metrics:[{name:'JSEventListeners',color:'yellowgreen'}]},{title:UIString.UIString('Documents'),metrics:[{name:'Documents',color:'darkblue'}]},{title:UIString.UIString('Document Frames'),metrics:[{name:'Frames',color:'darkcyan'}]},{title:UIString.UIString('Layouts / sec'),metrics:[{name:'LayoutCount',color:'hotpink'}]},{title:UIString.UIString('Style recalcs / sec'),metrics:[{name:'RecalcStyleCount',color:'deeppink'}]}];for(const info of this._chartsInfo){for(const metric of info.metrics){metric.color=self.UI.themeSupport.patchColorText(metric.color,UIUtils.ThemeSupport.ColorUsage.Foreground);}}
this._indicators=new Map();for(const chartInfo of this._chartsInfo){const chartName=chartInfo.metrics[0].name;const active=this._enabledCharts.has(chartName);const indicator=new MetricIndicator(this.element,chartInfo,active,this._onToggle.bind(this,chartName));this._indicators.set(chartName,indicator);}}
_onToggle(chartName,active){if(active){this._enabledCharts.add(chartName);}else{this._enabledCharts.delete(chartName);}
this._enabledChartsSetting.set(Array.from(this._enabledCharts));this.dispatchEventToListeners(Events.MetricChanged);}
charts(){return this._chartsInfo;}
isActive(metricName){return this._enabledCharts.has(metricName);}
updateMetrics(metrics){for(const name of this._indicators.keys()){if(metrics.has(name)){this._indicators.get(name).setValue(metrics.get(name));}}}}
const Events={MetricChanged:Symbol('MetricChanged')};class MetricIndicator{constructor(parent,info,active,onToggle){const color=info.color||info.metrics[0].color;this._info=info;this._active=active;this._onToggle=onToggle;this.element=parent.createChild('div','perfmon-indicator');this._swatchElement=Icon.Icon.create('smallicon-checkmark-square','perfmon-indicator-swatch');this._swatchElement.style.backgroundColor=color;this.element.appendChild(this._swatchElement);this.element.createChild('div','perfmon-indicator-title').textContent=info.title;this._valueElement=this.element.createChild('div','perfmon-indicator-value');this._valueElement.style.color=color;this.element.addEventListener('click',()=>this._toggleIndicator());this.element.addEventListener('keypress',event=>this._handleKeypress(event));this.element.classList.toggle('active',active);ARIAUtils.markAsCheckbox(this.element);ARIAUtils.setChecked(this.element,this._active);this.element.tabIndex=0;}
static _formatNumber(value,info){if(!MetricIndicator._numberFormatter){MetricIndicator._numberFormatter=new Intl.NumberFormat('en-US',{maximumFractionDigits:1});MetricIndicator._percentFormatter=new Intl.NumberFormat('en-US',{maximumFractionDigits:1,style:'percent'});}
switch(info.format){case Format.Percent:return MetricIndicator._percentFormatter.format(value);case Format.Bytes:return NumberUtilities.bytesToString(value);default:return MetricIndicator._numberFormatter.format(value);}}
setValue(value){this._valueElement.textContent=MetricIndicator._formatNumber(value,this._info);}
_toggleIndicator(){this._active=!this._active;this.element.classList.toggle('active',this._active);ARIAUtils.setChecked(this.element,this._active);this._onToggle(this._active);}
_handleKeypress(event){const keyboardEvent=(event);if(keyboardEvent.key===' '||keyboardEvent.key==='Enter'){this._toggleIndicator();}}}
const format=new Intl.NumberFormat('en-US',{maximumFractionDigits:1});let MetricInfo;let ChartInfo;var PerformanceMonitor=Object.freeze({__proto__:null,PerformanceMonitorImpl:PerformanceMonitorImpl,Format:Format,ControlPane:ControlPane,Events:Events,MetricIndicator:MetricIndicator,format:format,MetricInfo:MetricInfo,ChartInfo:ChartInfo});export{PerformanceMonitor};