| /* |
| * Copyright (C) 2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WI.CPUUsageCombinedView = class CPUUsageCombinedView extends WI.View |
| { |
| constructor(displayName) |
| { |
| super(); |
| |
| this.element.classList.add("cpu-usage-combined-view"); |
| |
| this._detailsElement = this.element.appendChild(document.createElement("div")); |
| this._detailsElement.classList.add("details"); |
| |
| let detailsNameElement = this._detailsElement.appendChild(document.createElement("span")); |
| detailsNameElement.classList.add("name"); |
| detailsNameElement.textContent = displayName; |
| |
| this._detailsElement.appendChild(document.createElement("br")); |
| this._detailsAverageElement = this._detailsElement.appendChild(document.createElement("span")); |
| this._detailsElement.appendChild(document.createElement("br")); |
| this._detailsMaxElement = this._detailsElement.appendChild(document.createElement("span")); |
| this._detailsElement.appendChild(document.createElement("br")); |
| this._detailsElement.appendChild(document.createElement("br")); |
| this._updateDetails(NaN, NaN); |
| |
| this._graphElement = this.element.appendChild(document.createElement("div")); |
| this._graphElement.classList.add("graph"); |
| |
| // Combined thread usage area chart. |
| this._chart = new WI.StackedAreaChart; |
| this._chart.initializeSections(["main-thread-usage", "worker-thread-usage", "total-usage"]); |
| this.addSubview(this._chart); |
| this._graphElement.appendChild(this._chart.element); |
| |
| // Main thread indicator strip. |
| this._rangeChart = new WI.RangeChart; |
| this.addSubview(this._rangeChart); |
| this._graphElement.appendChild(this._rangeChart.element); |
| |
| function appendLegendRow(legendElement, className) { |
| let rowElement = legendElement.appendChild(document.createElement("div")); |
| rowElement.classList.add("row"); |
| |
| let swatchElement = rowElement.appendChild(document.createElement("div")); |
| swatchElement.classList.add("swatch", className); |
| |
| let labelElement = rowElement.appendChild(document.createElement("div")); |
| labelElement.classList.add("label"); |
| |
| return labelElement; |
| } |
| |
| this._legendElement = this._detailsElement.appendChild(document.createElement("div")); |
| this._legendElement.classList.add("legend-container"); |
| |
| this._legendMainThreadElement = appendLegendRow(this._legendElement, "main-thread"); |
| this._legendWorkerThreadsElement = appendLegendRow(this._legendElement, "worker-threads"); |
| this._legendOtherThreadsElement = appendLegendRow(this._legendElement, "other-threads"); |
| this._legendTotalThreadsElement = appendLegendRow(this._legendElement, "total"); |
| |
| this.clearLegend(); |
| } |
| |
| // Public |
| |
| get graphElement() { return this._graphElement; } |
| get chart() { return this._chart; } |
| get rangeChart() { return this._rangeChart; } |
| |
| clear() |
| { |
| this._cachedAverageSize = undefined; |
| this._cachedMaxSize = undefined; |
| this._updateDetails(NaN, NaN); |
| |
| this.clearLegend(); |
| |
| this._chart.clear(); |
| this._chart.needsLayout(); |
| |
| this._rangeChart.clear(); |
| this._rangeChart.needsLayout(); |
| } |
| |
| updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale) |
| { |
| console.assert(size instanceof WI.Size); |
| console.assert(min >= 0); |
| console.assert(max >= 0); |
| console.assert(min <= max); |
| console.assert(min <= average && average <= max); |
| |
| this._updateDetails(max, average); |
| |
| this._chart.clearPoints(); |
| this._chart.size = size; |
| this._chart.needsLayout(); |
| |
| if (!dataPoints.length) |
| return; |
| |
| // Ensure an empty graph is empty. |
| if (!max) |
| return; |
| |
| // Extend the first data point to the start so it doesn't look like we originate at zero size. |
| let firstX = 0; |
| let firstY1 = yScale(dataPoints[0].mainThreadUsage); |
| let firstY2 = yScale(dataPoints[0].mainThreadUsage + dataPoints[0].workerThreadUsage); |
| let firstY3 = yScale(dataPoints[0].usage); |
| this._chart.addPointSet(firstX, [firstY1, firstY2, firstY3]); |
| |
| // Points for data points. |
| for (let dataPoint of dataPoints) { |
| let x = xScale(dataPoint.time); |
| let y1 = yScale(dataPoint.mainThreadUsage); |
| let y2 = yScale(dataPoint.mainThreadUsage + dataPoint.workerThreadUsage); |
| let y3 = yScale(dataPoint.usage); |
| this._chart.addPointSet(x, [y1, y2, y3]); |
| } |
| |
| // Extend the last data point to the end time. |
| let lastDataPoint = dataPoints.lastValue; |
| let lastX = Math.floor(xScale(visibleEndTime)); |
| let lastY1 = yScale(lastDataPoint.mainThreadUsage); |
| let lastY2 = yScale(lastDataPoint.mainThreadUsage + lastDataPoint.workerThreadUsage); |
| let lastY3 = yScale(lastDataPoint.usage); |
| this._chart.addPointSet(lastX, [lastY1, lastY2, lastY3]); |
| } |
| |
| updateMainThreadIndicator(samples, size, visibleEndTime, xScale) |
| { |
| console.assert(size instanceof WI.Size); |
| |
| this._rangeChart.clear(); |
| this._rangeChart.size = size; |
| this._rangeChart.needsLayout(); |
| |
| if (!samples.length) |
| return; |
| |
| // Coalesce ranges of samples. |
| let ranges = []; |
| let currentRange = null; |
| let currentSampleType = undefined; |
| for (let i = 0; i < samples.length; ++i) { |
| // Back to idle, close any current chunk. |
| let type = samples[i]; |
| if (!type) { |
| if (currentRange) { |
| ranges.push(currentRange); |
| currentRange = null; |
| currentSampleType = undefined; |
| } |
| continue; |
| } |
| |
| // Expand existing chunk. |
| if (type === currentSampleType) { |
| currentRange.endIndex = i; |
| continue; |
| } |
| |
| // If type changed, close current chunk. |
| if (currentSampleType) { |
| ranges.push(currentRange); |
| currentRange = null; |
| currentSampleType = undefined; |
| } |
| |
| // Start a new chunk. |
| console.assert(!currentRange); |
| console.assert(!currentSampleType); |
| currentRange = {type, startIndex: i, endIndex: i}; |
| currentSampleType = type; |
| } |
| |
| for (let {type, startIndex, endIndex} of ranges) { |
| let startX = xScale(startIndex); |
| let endX = xScale(endIndex + 1); |
| let width = endX - startX; |
| this._rangeChart.addRange(startX, width, type); |
| } |
| } |
| |
| clearLegend() |
| { |
| this._legendMainThreadElement.textContent = WI.UIString("Main Thread"); |
| this._legendWorkerThreadsElement.textContent = WI.UIString("Worker Threads"); |
| this._legendOtherThreadsElement.textContent = WI.UIString("Other Threads"); |
| this._legendTotalThreadsElement.textContent = ""; |
| } |
| |
| updateLegend(record) |
| { |
| if (!record) { |
| this.clearLegend(); |
| return; |
| } |
| |
| let {usage, mainThreadUsage, workerThreadUsage, webkitThreadUsage, unknownThreadUsage} = record; |
| |
| this._legendMainThreadElement.textContent = WI.UIString("Main: %s").format(Number.percentageString(mainThreadUsage / 100)); |
| this._legendWorkerThreadsElement.textContent = WI.UIString("Worker: %s").format(Number.percentageString(workerThreadUsage / 100)); |
| this._legendOtherThreadsElement.textContent = WI.UIString("Other: %s").format(Number.percentageString((webkitThreadUsage + unknownThreadUsage) / 100)); |
| this._legendTotalThreadsElement.textContent = WI.UIString("Total: %s").format(Number.percentageString(usage / 100)); |
| } |
| |
| // Private |
| |
| _updateDetails(maxSize, averageSize) |
| { |
| if (this._cachedMaxSize === maxSize && this._cachedAverageSize === averageSize) |
| return; |
| |
| this._cachedAverageSize = averageSize; |
| this._cachedMaxSize = maxSize; |
| |
| this._detailsAverageElement.textContent = WI.UIString("Average: %s").format(Number.isFinite(maxSize) ? Number.percentageString(averageSize / 100) : emDash); |
| this._detailsMaxElement.textContent = WI.UIString("Highest: %s").format(Number.isFinite(maxSize) ? Number.percentageString(maxSize / 100) : emDash); |
| } |
| }; |