/* * Copyright (C) 2016 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. */ WebInspector.MemoryTimelineOverviewGraph = class MemoryTimelineOverviewGraph extends WebInspector.TimelineOverviewGraph { constructor(timeline, timelineOverview) { super(timelineOverview); this.element.classList.add("memory"); console.assert(timeline instanceof WebInspector.MemoryTimeline); this._memoryTimeline = timeline; this._memoryTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._memoryTimelineRecordAdded, this); this._memoryTimeline.addEventListener(WebInspector.MemoryTimeline.Event.MemoryPressureEventAdded, this._memoryTimelineMemoryPressureEventAdded, this); this._didInitializeCategories = false; let size = new WebInspector.Size(0, this.height); this._chart = new WebInspector.StackedLineChart(size); this.element.appendChild(this._chart.element); this._legendElement = this.element.appendChild(document.createElement("div")); this._legendElement.classList.add("legend"); this._memoryPressureMarkersContainerElement = this.element.appendChild(document.createElement("div")); this._memoryPressureMarkersContainerElement.classList.add("memory-pressure-markers-container"); this._memoryPressureMarkerElements = []; this.reset(); } // Protected get height() { return 108; } reset() { super.reset(); this._maxSize = 0; this._cachedMaxSize = undefined; this._updateLegend(); this._chart.clear(); this._chart.needsLayout(); this._memoryPressureMarkersContainerElement.removeChildren(); this._memoryPressureMarkerElements = []; } layout() { if (!this.visible) return; this._updateLegend(); this._chart.clear(); if (!this._didInitializeCategories) return; let graphWidth = this.timelineOverview.scrollContainerWidth; if (isNaN(graphWidth)) return; if (this._chart.size.width !== graphWidth || this._chart.size.height !== this.height) this._chart.size = new WebInspector.Size(graphWidth, this.height); let graphStartTime = this.startTime; let visibleEndTime = Math.min(this.endTime, this.currentTime); let secondsPerPixel = this.timelineOverview.secondsPerPixel; let maxCapacity = this._maxSize * 1.05; // Add 5% for padding. function xScale(time) { return (time - graphStartTime) / secondsPerPixel; } let height = this.height; function yScale(size) { return height - ((size / maxCapacity) * height); } let visibleMemoryPressureEventMarkers = this._visibleMemoryPressureEvents(graphStartTime, visibleEndTime); // Reuse existing marker elements. for (let i = 0; i < visibleMemoryPressureEventMarkers.length; ++i) { let markerElement = this._memoryPressureMarkerElements[i]; if (!markerElement) { markerElement = this._memoryPressureMarkersContainerElement.appendChild(document.createElement("div")); markerElement.classList.add("memory-pressure-event"); this._memoryPressureMarkerElements[i] = markerElement; } let memoryPressureEvent = visibleMemoryPressureEventMarkers[i]; markerElement.style.left = xScale(memoryPressureEvent.timestamp) + "px"; } // Remove excess marker elements. let excess = this._memoryPressureMarkerElements.length - visibleMemoryPressureEventMarkers.length; if (excess) { let elementsToRemove = this._memoryPressureMarkerElements.splice(visibleMemoryPressureEventMarkers.length); for (let element of elementsToRemove) element.remove(); } let discontinuities = this.timelineOverview.discontinuitiesInTimeRange(graphStartTime, visibleEndTime); // Don't include the record before the graph start if the graph start is within a gap. let includeRecordBeforeStart = !discontinuities.length || discontinuities[0].startTime > graphStartTime; // FIXME: Web Inspector: Memory Timelines should better extend to future data let visibleRecords = this._memoryTimeline.recordsInTimeRange(graphStartTime, visibleEndTime, includeRecordBeforeStart); if (!visibleRecords.length) return; function pointSetForRecord(record) { let size = 0; let ys = []; for (let i = 0; i < record.categories.length; ++i) { size += record.categories[i].size; ys[i] = yScale(size); } return ys; } // Extend the first record to the start so it doesn't look like we originate at zero size. if (visibleRecords[0] === this._memoryTimeline.records[0] && (!discontinuities.length || discontinuities[0].startTime > visibleRecords[0].startTime)) this._chart.addPointSet(0, pointSetForRecord(visibleRecords[0])); function insertDiscontinuity(previousRecord, discontinuity, nextRecord) { console.assert(previousRecord || nextRecord); if (!(previousRecord || nextRecord)) return; let xStart = xScale(discontinuity.startTime); let xEnd = xScale(discontinuity.endTime); // Extend the previous record to the start of the discontinuity. if (previousRecord) this._chart.addPointSet(xStart, pointSetForRecord(previousRecord)); let zeroValues = Array((previousRecord || nextRecord).categories.length).fill(yScale(0)); this._chart.addPointSet(xStart, zeroValues); if (nextRecord) { this._chart.addPointSet(xEnd, zeroValues); this._chart.addPointSet(xEnd, pointSetForRecord(nextRecord)); } else { // Extend the discontinuity to the visible end time to prevent // drawing artifacts when the next record arrives. this._chart.addPointSet(xScale(visibleEndTime), zeroValues); } } // Points for visible records. let previousRecord = null; for (let record of visibleRecords) { if (discontinuities.length && discontinuities[0].endTime < record.startTime) { let discontinuity = discontinuities.shift(); insertDiscontinuity.call(this, previousRecord, discontinuity, record); } let x = xScale(record.startTime); this._chart.addPointSet(x, pointSetForRecord(record)); previousRecord = record; } if (discontinuities.length) insertDiscontinuity.call(this, previousRecord, discontinuities[0], null); else { // Extend the last value to current / end time. let lastRecord = visibleRecords.lastValue; if (lastRecord.startTime <= visibleEndTime) { let x = Math.floor(xScale(visibleEndTime)); this._chart.addPointSet(x, pointSetForRecord(lastRecord)); } } this._chart.updateLayout(); } // Private _updateLegend() { if (this._cachedMaxSize === this._maxSize) return; this._cachedMaxSize = this._maxSize; if (!this._maxSize) { this._legendElement.hidden = true; this._legendElement.textContent = ""; } else { this._legendElement.hidden = false; this._legendElement.textContent = WebInspector.UIString("Maximum Size: %s").format(Number.bytesToString(this._maxSize)); } } _visibleMemoryPressureEvents(startTime, endTime) { let events = this._memoryTimeline.memoryPressureEvents; if (!events.length) return []; let lowerIndex = events.lowerBound(startTime, (time, event) => time - event.timestamp); let upperIndex = events.upperBound(endTime, (time, event) => time - event.timestamp); return events.slice(lowerIndex, upperIndex); } _memoryTimelineRecordAdded(event) { let memoryTimelineRecord = event.data.record; this._maxSize = Math.max(this._maxSize, memoryTimelineRecord.totalSize); if (!this._didInitializeCategories) { this._didInitializeCategories = true; let types = []; for (let category of memoryTimelineRecord.categories) types.push(category.type); this._chart.initializeSections(types); } this.needsLayout(); } _memoryTimelineMemoryPressureEventAdded(event) { this.needsLayout(); } };