/**
* @constructor
- * @extends {WebInspector.SidebarView}
+ * @extends {WebInspector.SplitView}
* @param {!WebInspector.TimelineView} timelineView
* @param {!WebInspector.TimelineModel} model
*/
WebInspector.MemoryStatistics = function(timelineView, model)
{
- WebInspector.SidebarView.call(this, WebInspector.SidebarView.SidebarPosition.Start, undefined);
+ WebInspector.SplitView.call(this, true, false);
+
this.element.id = "memory-graphs-container";
this._timelineView = timelineView;
- this._counters = [];
-
model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
- model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);
- this._canvasContainer = this.mainElement();
- this._canvasContainer.id = "memory-graphs-canvas-container";
+ this._graphsContainer = this.mainElement();
this._createCurrentValuesBar();
- this._canvas = this._canvasContainer.createChild("canvas", "fill");
+ this._canvasView = new WebInspector.ViewWithResizeCallback(this._resize.bind(this));
+ this._canvasView.show(this._graphsContainer);
+ this._canvasContainer = this._canvasView.element;
+ this._canvasContainer.id = "memory-graphs-canvas-container";
+ this._canvas = this._canvasContainer.createChild("canvas");
this._canvas.id = "memory-counters-graph";
- this._lastMarkerXPosition = 0;
- this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true);
- this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true);
- this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true);
- this._canvas.addEventListener("click", this._onClick.bind(this), true);
+ this._canvasContainer.addEventListener("mouseover", this._onMouseMove.bind(this), true);
+ this._canvasContainer.addEventListener("mousemove", this._onMouseMove.bind(this), true);
+ this._canvasContainer.addEventListener("mouseout", this._onMouseOut.bind(this), true);
+ this._canvasContainer.addEventListener("click", this._onClick.bind(this), true);
// We create extra timeline grid here to reuse its event dividers.
this._timelineGrid = new WebInspector.TimelineGrid();
this._canvasContainer.appendChild(this._timelineGrid.dividersElement);
// Populate sidebar
this.sidebarElement().createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS");
- this._counterUI = this._createCounterUIList();
+ this._createAllCounters();
}
/**
* @constructor
- * @param {number} time
+ * @param {string} counterName
*/
-WebInspector.MemoryStatistics.Counter = function(time)
+WebInspector.MemoryStatistics.Counter = function(counterName)
{
- this.time = time;
+ this.counterName = counterName;
+ this.times = [];
+ this.values = [];
+}
+
+WebInspector.MemoryStatistics.Counter.prototype = {
+ /**
+ * @param {number} time
+ * @param {!TimelineAgent.Counters} counters
+ */
+ appendSample: function(time, counters)
+ {
+ var value = counters[this.counterName];
+ if (value === undefined)
+ return;
+ if (this.values.length && this.values.peekLast() === value)
+ return;
+ this.times.push(time);
+ this.values.push(value);
+ },
+
+ reset: function()
+ {
+ this.times = [];
+ this.values = [];
+ },
+
+ /**
+ * @param {!WebInspector.TimelineCalculator} calculator
+ */
+ _calculateVisibleIndexes: function(calculator)
+ {
+ // FIXME: these 1000 constants shouldn't be here.
+ var start = calculator.minimumBoundary() * 1000;
+ var end = calculator.maximumBoundary() * 1000;
+
+ // Maximum index of element whose time <= start.
+ this._minimumIndex = Number.constrain(this.times.upperBound(start) - 1, 0, this.times.length - 1);
+
+ // Minimum index of element whose time >= end.
+ this._maximumIndex = Number.constrain(this.times.lowerBound(end), 0, this.times.length - 1);
+
+ // Current window bounds.
+ this._minTime = start;
+ this._maxTime = end;
+ },
+
+ /**
+ * @param {number} width
+ */
+ _calculateXValues: function(width)
+ {
+ if (!this.values.length)
+ return;
+
+ var xFactor = width / (this._maxTime - this._minTime);
+
+ this.x = new Array(this.values.length);
+ this.x[this._minimumIndex] = 0;
+ for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++)
+ this.x[i] = xFactor * (this.times[i] - this._minTime);
+ this.x[this._maximumIndex] = width;
+ }
}
/**
/**
* @constructor
+ * @param {!WebInspector.MemoryStatistics} memoryCountersPane
+ * @param {string} title
+ * @param {string} graphColor
+ * @param {!WebInspector.MemoryStatistics.Counter} counter
*/
-WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter)
+WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, counter)
{
this._memoryCountersPane = memoryCountersPane;
- this.valueGetter = valueGetter;
+ this.counter = counter;
var container = memoryCountersPane.sidebarElement().createChild("div", "memory-counter-sidebar-info");
var swatchColor = graphColor;
this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor);
container.appendChild(this._swatch.element);
this._value = null;
- this.graphColor =graphColor;
+ this.graphColor = graphColor;
this.strokeColor = graphColor;
this.graphYValues = [];
}
WebInspector.CounterUIBase.prototype = {
_toggleCounterGraph: function(event)
{
- if (this._swatch.checked)
- this._value.classList.remove("hidden");
- else
- this._value.classList.add("hidden");
+ this._value.classList.toggle("hidden", !this._swatch.checked);
this._memoryCountersPane.refresh();
},
- updateCurrentValue: function(countersEntry)
+ /**
+ * @param {number} x
+ * @return {number}
+ */
+ _recordIndexAt: function(x)
{
- this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry));
+ return this.counter.x.upperBound(x, null, this.counter._minimumIndex + 1, this.counter._maximumIndex + 1) - 1;
},
- clearCurrentValueAndMarker: function(ctx)
+ /**
+ * @param {number} x
+ */
+ updateCurrentValue: function(x)
+ {
+ if (!this.visible || !this.counter.values.length)
+ return;
+ var index = this._recordIndexAt(x);
+ this._value.textContent = WebInspector.UIString(this._currentValueLabel, this.counter.values[index]);
+ var y = this.graphYValues[index];
+ this._marker.style.left = x + "px";
+ this._marker.style.top = y + "px";
+ this._marker.classList.remove("hidden");
+ },
+
+ clearCurrentValueAndMarker: function()
{
this._value.textContent = "";
+ this._marker.classList.add("hidden");
},
get visible()
throw new Error("Not implemented");
},
- _createCounterUIList: function()
+ _createAllCounters: function()
{
throw new Error("Not implemented");
},
- _onRecordsCleared: function()
- {
- this._counters = [];
- },
-
/**
- * @return {number}
+ * @param {!WebInspector.Event} event
*/
- height: function()
- {
- return this.element.offsetHeight;
- },
-
- _canvasHeight: function()
+ _onRecordAdded: function(event)
{
throw new Error("Not implemented");
},
- onResize: function()
+ reset: function()
{
- WebInspector.SidebarView.prototype.onResize.call(this);
+ for (var i = 0; i < this._counters.length; ++i)
+ this._counters[i].reset();
- var width = this._canvasContainer.offsetWidth + 1;
- this._canvas.style.width = width + "px";
- this._timelineGrid.dividersElement.style.width = width + "px";
- var parentElement = this._canvas.parentElement;
+ for (var i = 0; i < this._counterUI.length; ++i)
+ this._counterUI[i].reset();
- this._canvas.width = width;
- this._canvas.height = parentElement.clientHeight - 15;
this.refresh();
},
- /**
- * @param {!WebInspector.Event} event
- */
- _onRecordAdded: function(event)
+ _resize: function()
{
- throw new Error("Not implemented");
+ var parentElement = this._canvas.parentElement;
+ this._canvas.width = parentElement.clientWidth;
+ this._canvas.height = parentElement.clientHeight;
+ this.refresh();
},
- draw: function()
+ setWindowTimes: function()
{
- this._calculateVisibleIndexes();
- this._calculateXValues();
- this._clear();
-
- this._setVerticalClip(10, this._canvas.height - 20);
+ this.scheduleRefresh();
},
- _calculateVisibleIndexes: function()
+ scheduleRefresh: function()
{
- var calculator = this._timelineView.calculator;
- var start = calculator.minimumBoundary() * 1000;
- var end = calculator.maximumBoundary() * 1000;
- function comparator(value, sample)
- {
- return value - sample.time;
- }
-
- // Maximum index of element whose time <= start.
- this._minimumIndex = Number.constrain(this._counters.upperBound(start, comparator) - 1, 0, this._counters.length - 1);
-
- // Minimum index of element whose time >= end.
- this._maximumIndex = Number.constrain(this._counters.lowerBound(end, comparator), 0, this._counters.length - 1);
-
- // Current window bounds.
- this._minTime = start;
- this._maxTime = end;
+ if (this._refreshTimer)
+ return;
+ this._refreshTimer = setTimeout(this.refresh.bind(this), 300);
},
/**
- * @param {!MouseEvent} event
+ * @return {boolean}
*/
- _onClick: function(event)
+ supportsGlueParentMode: function()
{
- var x = event.x - event.target.offsetParent.offsetLeft;
- var i = this._recordIndexAt(x);
- var counter = this._counters[i];
- if (counter)
- this._timelineView.revealRecordAt(counter.time / 1000);
+ return true;
},
- /**
- * @param {!MouseEvent} event
- */
- _onMouseOut: function(event)
+ draw: function()
{
- delete this._markerXPosition;
-
- var ctx = this._canvas.getContext("2d");
- this._clearCurrentValueAndMarker(ctx);
+ for (var i = 0; i < this._counters.length; ++i) {
+ this._counters[i]._calculateVisibleIndexes(this._timelineView.calculator);
+ this._counters[i]._calculateXValues(this._canvas.width);
+ }
+ this._clear();
+ this._setVerticalClip(10, this._canvas.height - 20);
},
/**
- * @param {!CanvasRenderingContext2D} ctx
+ * @param {?Event} event
*/
- _clearCurrentValueAndMarker: function(ctx)
- {
- for (var i = 0; i < this._counterUI.length; i++)
- this._counterUI[i].clearCurrentValueAndMarker(ctx);
+ _onClick: function(event)
+ {
+ var x = event.x - this._canvasContainer.offsetParent.offsetLeft;
+ var minDistance = Infinity;
+ var bestTime;
+ for (var i = 0; i < this._counterUI.length; ++i) {
+ var counterUI = this._counterUI[i];
+ if (!counterUI.counter.times.length)
+ continue;
+ var index = counterUI._recordIndexAt(x);
+ var distance = Math.abs(x - counterUI.counter.x[index]);
+ if (distance < minDistance) {
+ minDistance = distance;
+ bestTime = counterUI.counter.times[index];
+ }
+ }
+ if (bestTime !== undefined)
+ this._timelineView.revealRecordAt(bestTime / 1000);
},
/**
- * @param {!MouseEvent} event
+ * @param {?Event} event
*/
- _onMouseOver: function(event)
+ _onMouseOut: function(event)
{
- this._onMouseMove(event);
+ delete this._markerXPosition;
+ this._clearCurrentValueAndMarker();
+ },
+
+ _clearCurrentValueAndMarker: function()
+ {
+ for (var i = 0; i < this._counterUI.length; i++)
+ this._counterUI[i].clearCurrentValueAndMarker();
},
/**
- * @param {!MouseEvent} event
+ * @param {?Event} event
*/
- _onMouseMove: function(event)
+ _onMouseMove: function(event)
{
- var x = event.x - event.target.offsetParent.offsetLeft
+ var x = event.x - this._canvasContainer.offsetParent.offsetLeft;
this._markerXPosition = x;
this._refreshCurrentValues();
},
_refreshCurrentValues: function()
{
- if (!this._counters.length)
- return;
if (this._markerXPosition === undefined)
return;
- if (this._maximumIndex === -1)
- return;
- var i = this._recordIndexAt(this._markerXPosition);
-
- this._updateCurrentValue(this._counters[i]);
-
- this._highlightCurrentPositionOnGraphs(this._markerXPosition, i);
- },
-
- _updateCurrentValue: function(counterEntry)
- {
- for (var j = 0; j < this._counterUI.length; j++)
- this._counterUI[j].updateCurrentValue(counterEntry);
- },
-
- _recordIndexAt: function(x)
- {
- var i;
- for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) {
- var statX = this._counters[i].x;
- if (x < statX)
- break;
- }
- i--;
- return i;
- },
-
- _highlightCurrentPositionOnGraphs: function(x, index)
- {
- var ctx = this._canvas.getContext("2d");
- this._restoreImageUnderMarker(ctx);
- this._drawMarker(ctx, x, index);
- },
-
- _restoreImageUnderMarker: function(ctx)
- {
- throw new Error("Not implemented");
- },
-
- _drawMarker: function(ctx, x, index)
- {
- throw new Error("Not implemented");
+ for (var i = 0; i < this._counterUI.length; ++i)
+ this._counterUI[i].updateCurrentValue(this._markerXPosition);
},
refresh: function()
{
+ delete this._refreshTimer;
this._timelineGrid.updateDividers(this._timelineView.calculator);
this.draw();
this._refreshCurrentValues();
},
+ /**
+ * @param {number} originY
+ * @param {number} height
+ */
_setVerticalClip: function(originY, height)
{
this._originY = originY;
this._clippedHeight = height;
},
- _calculateXValues: function()
- {
- if (!this._counters.length)
- return;
-
- var width = this._canvas.width;
- var xFactor = width / (this._maxTime - this._minTime);
-
- this._counters[this._minimumIndex].x = 0;
- for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++)
- this._counters[i].x = xFactor * (this._counters[i].time - this._minTime);
- this._counters[this._maximumIndex].x = width;
- },
-
_clear: function()
{
var ctx = this._canvas.getContext("2d");
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
- this._discardImageUnderMarker();
- },
-
- _discardImageUnderMarker: function()
- {
- throw new Error("Not implemented");
},
- __proto__: WebInspector.SidebarView.prototype
+ __proto__: WebInspector.SplitView.prototype
}