1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @param {WebInspector.TimelinePanel} timelinePanel
33 * @param {WebInspector.TimelineModel} model
34 * @param {number} sidebarWidth
35 * @constructor
36 * @extends {WebInspector.MemoryStatistics}
37 */
38WebInspector.NativeMemoryGraph = function(timelinePanel, model, sidebarWidth)
39{
40    WebInspector.MemoryStatistics.call(this, timelinePanel, model, sidebarWidth);
41}
42
43/**
44 * @constructor
45 * @extends {WebInspector.MemoryStatistics.Counter}
46 */
47WebInspector.NativeMemoryGraph.Counter = function(time, nativeCounters)
48{
49    WebInspector.MemoryStatistics.Counter.call(this, time);
50    this.nativeCounters = nativeCounters;
51}
52
53/**
54 * @constructor
55 * @extends {WebInspector.CounterUIBase}
56 * @param {WebInspector.NativeMemoryGraph} memoryCountersPane
57 * @param {string} title
58 * @param {Array.<number>} hsl
59 * @param {function(WebInspector.NativeMemoryGraph.Counter):number} valueGetter
60 */
61WebInspector.NativeMemoryCounterUI = function(memoryCountersPane, title, hsl, valueGetter)
62{
63    var swatchColor = this._hslToString(hsl);
64    WebInspector.CounterUIBase.call(this, memoryCountersPane, title, swatchColor, valueGetter);
65    this._value = this._swatch.element.createChild("span", "memory-category-value");
66
67    const borderLightnessDifference = 3;
68    hsl[2] -= borderLightnessDifference;
69    this.strokeColor = this._hslToString(hsl);
70    this.graphYValues = [];
71}
72
73WebInspector.NativeMemoryCounterUI.prototype = {
74    _hslToString: function(hsl)
75    {
76        return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)";
77    },
78
79    updateCurrentValue: function(countersEntry)
80    {
81        var bytes = this.valueGetter(countersEntry);
82        var megabytes =  bytes / (1024 * 1024);
83        this._value.textContent = WebInspector.UIString("%.1f\u2009MB", megabytes);
84    },
85
86    clearCurrentValueAndMarker: function(ctx)
87    {
88        this._value.textContent = "";
89    },
90
91    __proto__: WebInspector.CounterUIBase.prototype
92}
93
94
95WebInspector.NativeMemoryGraph.prototype = {
96    _createCurrentValuesBar: function()
97    {
98    },
99
100    _createCounterUIList: function()
101    {
102        var nativeCounters = [
103            "JSExternalResources",
104            "CSS",
105            "GlyphCache",
106            "Image",
107            "Resources",
108            "DOM",
109            "Rendering",
110            "Audio",
111            "WebInspector",
112            "JSHeap.Used",
113            "JSHeap.Unused",
114            "MallocWaste",
115            "Other",
116            "PrivateBytes",
117        ];
118
119        /**
120         * @param {string} name
121         * @return {number}
122         */
123        function getCounterValue(name, entry)
124        {
125            return (entry.nativeCounters && entry.nativeCounters[name]) || 0;
126        }
127
128        var list = [];
129        for (var i = nativeCounters.length - 1; i >= 0; i--) {
130            var name = nativeCounters[i];
131            if ("PrivateBytes" === name) {
132                var counterUI = new WebInspector.NativeMemoryCounterUI(this, "Total", [0, 0, 0], getCounterValue.bind(this, name))
133                this._privateBytesCounter = counterUI;
134            } else {
135                var counterUI = new WebInspector.NativeMemoryCounterUI(this, name, [i * 20, 65, 63], getCounterValue.bind(this, name))
136                list.push(counterUI);
137            }
138        }
139        return list.reverse();
140    },
141
142    _canvasHeight: function()
143    {
144        return this._canvasContainer.offsetHeight;
145    },
146
147    /**
148     * @param {WebInspector.Event} event
149     */
150    _onRecordAdded: function(event)
151    {
152        var statistics = this._counters;
153        function addStatistics(record)
154        {
155            var nativeCounters = record["nativeHeapStatistics"];
156            if (!nativeCounters)
157                return;
158
159            var knownSize = 0;
160            for (var name in nativeCounters) {
161                if (name === "PrivateBytes")
162                    continue;
163                knownSize += nativeCounters[name];
164            }
165            nativeCounters["Other"] = nativeCounters["PrivateBytes"] - knownSize;
166
167            statistics.push(new WebInspector.NativeMemoryGraph.Counter(
168                record.endTime || record.startTime,
169                nativeCounters
170            ));
171        }
172        WebInspector.TimelinePresentationModel.forAllRecords([event.data], null, addStatistics);
173    },
174
175    _draw: function()
176    {
177        WebInspector.MemoryStatistics.prototype._draw.call(this);
178
179        var maxValue = this._maxCounterValue();
180        this._resetTotalValues();
181
182        var previousCounterUI;
183        for (var i = 0; i < this._counterUI.length; i++) {
184            this._drawGraph(this._counterUI[i], previousCounterUI, maxValue);
185            if (this._counterUI[i].visible)
186                previousCounterUI = this._counterUI[i];
187        }
188    },
189
190    /**
191     * @param {CanvasRenderingContext2D} ctx
192     */
193    _clearCurrentValueAndMarker: function(ctx)
194    {
195        WebInspector.MemoryStatistics.prototype._clearCurrentValueAndMarker.call(this, ctx);
196        this._privateBytesCounter.clearCurrentValueAndMarker(ctx);
197    },
198
199    _updateCurrentValue: function(counterEntry)
200    {
201        WebInspector.MemoryStatistics.prototype._updateCurrentValue.call(this, counterEntry);
202        this._privateBytesCounter.updateCurrentValue(counterEntry);
203    },
204
205    /**
206     * @param {CanvasRenderingContext2D} ctx
207     */
208    _restoreImageUnderMarker: function(ctx)
209    {
210        if (this._imageUnderMarker)
211            ctx.putImageData(this._imageUnderMarker.imageData, this._imageUnderMarker.x, this._imageUnderMarker.y);
212        this._discardImageUnderMarker();
213    },
214
215    /**
216     * @param {CanvasRenderingContext2D} ctx
217     * @param {number} left
218     * @param {number} top
219     * @param {number} right
220     * @param {number} bottom
221     */
222    _saveImageUnderMarker: function(ctx, left, top, right, bottom)
223    {
224        var imageData = ctx.getImageData(left, top, right, bottom);
225        this._imageUnderMarker = {
226            x: left,
227            y: top,
228            imageData: imageData
229        };
230    },
231
232    /**
233     * @param {CanvasRenderingContext2D} ctx
234     * @param {number} x
235     * @param {number} index
236     */
237    _drawMarker: function(ctx, x, index)
238    {
239        var left = this._counters[index].x;
240        var right = index + 1 < this._counters.length ? this._counters[index + 1].x : left;
241        var top = this._originY;
242        top = 0;
243        var bottom = top + this._clippedHeight;
244        bottom += this._originY;
245
246        this._saveImageUnderMarker(ctx, left, top, right, bottom);
247
248        ctx.beginPath();
249        ctx.moveTo(left, top);
250        ctx.lineTo(right, top);
251        ctx.lineTo(right, bottom);
252        ctx.lineTo(left, bottom);
253        ctx.lineWidth = 1;
254        ctx.closePath();
255        ctx.fillStyle = "rgba(220,220,220,0.3)";
256        ctx.fill();
257    },
258
259    /**
260     * @return {number}
261     */
262    _maxCounterValue: function()
263    {
264        if (!this._counters.length)
265            return 0;
266
267        var valueGetter = this._privateBytesCounter.valueGetter;
268        var result = 0;
269        for (var i = this._minimumIndex; i < this._maximumIndex; i++) {
270            var counter = this._counters[i];
271            var value = valueGetter(counter);
272            if (value > result)
273                result = value;
274        }
275        return result;
276    },
277
278    _resetTotalValues: function()
279    {
280        for (var i = this._minimumIndex; i <= this._maximumIndex; i++) {
281            var counter = this._counters[i];
282            counter.total = 0;
283        }
284    },
285
286    /**
287     * @param {WebInspector.CounterUIBase} counterUI
288     * @param {WebInspector.CounterUIBase} previousCounterUI
289     * @param {number} maxTotalValue
290     */
291    _drawGraph: function(counterUI, previousCounterUI, maxTotalValue)
292    {
293        var canvas = this._canvas;
294        var ctx = canvas.getContext("2d");
295        var width = canvas.width;
296        var height = this._clippedHeight;
297        var originY = this._originY;
298        var valueGetter = counterUI.valueGetter;
299
300        if (!this._counters.length)
301            return;
302
303        if (!counterUI.visible)
304            return;
305
306        for (var i = this._minimumIndex; i <= this._maximumIndex; i++) {
307            var counter = this._counters[i];
308            var value = valueGetter(counter);
309            counter.total += value;
310        }
311
312        var yValues = counterUI.graphYValues;
313        yValues.length = this._counters.length;
314
315        var maxYRange =  maxTotalValue;
316        var yFactor = maxYRange ? height / (maxYRange) : 1;
317
318        ctx.beginPath();
319        if (previousCounterUI) {
320            var prevYValues = previousCounterUI.graphYValues;
321            var currentY = prevYValues[this._maximumIndex];
322            ctx.moveTo(width, currentY);
323            var currentX = width;
324            for (var i = this._maximumIndex - 1; i >= this._minimumIndex; i--) {
325                currentY = prevYValues[i];
326                currentX = this._counters[i].x;
327                ctx.lineTo(currentX, currentY);
328            }
329        } else {
330            var lastY = originY + height;
331            ctx.moveTo(width, lastY);
332            ctx.lineTo(0, lastY);
333        }
334
335        var currentY = originY + (height - this._counters[this._minimumIndex].total * yFactor);
336        ctx.lineTo(0, currentY);
337        for (var i = this._minimumIndex; i <= this._maximumIndex; i++) {
338             var counter = this._counters[i];
339             var x = counter.x;
340             currentY = originY + (height - counter.total * yFactor);
341             ctx.lineTo(x, currentY);
342
343             yValues[i] = currentY;
344        }
345        ctx.lineTo(width, currentY);
346        ctx.closePath();
347        ctx.lineWidth = 1;
348
349        ctx.strokeStyle = counterUI.strokeColor;
350        ctx.fillStyle = counterUI.graphColor;
351        ctx.fill();
352        ctx.stroke();
353    },
354
355    _discardImageUnderMarker: function()
356    {
357        delete this._imageUnderMarker;
358    },
359
360    __proto__: WebInspector.MemoryStatistics.prototype
361}
362
363