1/*
2 * Copyright (C) 2014 Apple 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
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26WebInspector.ProfileNode = function(id, type, functionName, sourceCodeLocation, calls, childNodes)
27{
28    WebInspector.Object.call(this);
29
30    childNodes = childNodes || [];
31
32    console.assert(id);
33    console.assert(calls instanceof Array);
34    console.assert(calls.length >= 1);
35    console.assert(calls.reduce(function(previousValue, call) { return previousValue && call instanceof WebInspector.ProfileNodeCall; }, true));
36    console.assert(childNodes instanceof Array);
37    console.assert(childNodes.reduce(function(previousValue, node) { return previousValue && node instanceof WebInspector.ProfileNode; }, true));
38
39    this._id = id;
40    this._type = type || WebInspector.ProfileNode.Type.Function;
41    this._functionName = functionName || null;
42    this._sourceCodeLocation = sourceCodeLocation || null;
43    this._calls = calls;
44    this._childNodes = childNodes;
45    this._parentNode = null;
46    this._previousSibling = null;
47    this._nextSibling = null;
48    this._computedTotalTimes = false;
49
50    for (var i = 0; i < this._childNodes.length; ++i)
51        this._childNodes[i].establishRelationships(this, this._childNodes[i - 1], this._childNodes[i + 1]);
52
53    for (var i = 0; i < this._calls.length; ++i)
54        this._calls[i].establishRelationships(this, this._calls[i - 1], this._calls[i + 1]);
55};
56
57WebInspector.ProfileNode.Type = {
58    Function: "profile-node-type-function",
59    Program: "profile-node-type-program"
60};
61
62WebInspector.ProfileNode.TypeIdentifier = "profile-node";
63WebInspector.ProfileNode.TypeCookieKey = "profile-node-type";
64WebInspector.ProfileNode.FunctionNameCookieKey = "profile-node-function-name";
65WebInspector.ProfileNode.SourceCodeURLCookieKey = "profile-node-source-code-url";
66WebInspector.ProfileNode.SourceCodeLocationLineCookieKey = "profile-node-source-code-location-line";
67WebInspector.ProfileNode.SourceCodeLocationColumnCookieKey = "profile-node-source-code-location-column";
68
69WebInspector.ProfileNode.prototype = {
70    constructor: WebInspector.ProfileNode,
71    __proto__: WebInspector.Object.prototype,
72
73    // Public
74
75    get id()
76    {
77        return this._id;
78    },
79
80    get type()
81    {
82        return this._type;
83    },
84
85    get functionName()
86    {
87        return this._functionName;
88    },
89
90    get sourceCodeLocation()
91    {
92        return this._sourceCodeLocation;
93    },
94
95    get startTime()
96    {
97        if (this._startTime === undefined)
98            this._startTime =  Math.max(0, this._calls[0].startTime);
99        return this._startTime;
100    },
101
102    get endTime()
103    {
104        if (this._endTime === undefined)
105            this._endTime = Math.min(this._calls.lastValue.endTime, Infinity);
106        return this._endTime;
107    },
108
109    get selfTime()
110    {
111        this._computeTotalTimesIfNeeded();
112        return this._selfTime;
113    },
114
115    get totalTime()
116    {
117        this._computeTotalTimesIfNeeded();
118        return this._totalTime;
119    },
120
121    get calls()
122    {
123        return this._calls;
124    },
125
126    get previousSibling()
127    {
128        return this._previousSibling;
129    },
130
131    get nextSibling()
132    {
133        return this._nextSibling;
134    },
135
136    get parentNode()
137    {
138        return this._parentNode;
139    },
140
141    get childNodes()
142    {
143        return this._childNodes;
144    },
145
146    computeCallInfoForTimeRange: function(rangeStartTime, rangeEndTime)
147    {
148        console.assert(typeof rangeStartTime === "number");
149        console.assert(typeof rangeEndTime === "number");
150
151        var recordCallCount = true;
152        var callCount = 0;
153
154        function totalTimeInRange(previousValue, call)
155        {
156            if (rangeStartTime > call.endTime || rangeEndTime < call.startTime)
157                return previousValue;
158
159            if (recordCallCount)
160                ++callCount;
161
162            return previousValue + Math.min(call.endTime, rangeEndTime) - Math.max(rangeStartTime, call.startTime);
163        }
164
165        var startTime = Math.max(rangeStartTime, this._calls[0].startTime);
166        var endTime = Math.min(this._calls.lastValue.endTime, rangeEndTime);
167        var totalTime = this._calls.reduce(totalTimeInRange, 0);
168
169        recordCallCount = false;
170
171        var childNodesTotalTime = 0;
172        for (var childNode of this._childNodes)
173            childNodesTotalTime += childNode.calls.reduce(totalTimeInRange, 0);
174
175        var selfTime = totalTime - childNodesTotalTime;
176        var averageTime = selfTime / callCount;
177
178        return {startTime: startTime, endTime: endTime, totalTime: totalTime, selfTime: selfTime, callCount: callCount, averageTime: averageTime};
179    },
180
181    traverseNextProfileNode: function(stayWithin)
182    {
183        var profileNode = this._childNodes[0];
184        if (profileNode)
185            return profileNode;
186
187        if (this === stayWithin)
188            return null;
189
190        profileNode = this._nextSibling;
191        if (profileNode)
192            return profileNode;
193
194        profileNode = this;
195        while (profileNode && !profileNode.nextSibling && profileNode.parentNode !== stayWithin)
196            profileNode = profileNode.parentNode;
197
198        if (!profileNode)
199            return null;
200
201        return profileNode.nextSibling;
202    },
203
204    saveIdentityToCookie: function(cookie)
205    {
206        cookie[WebInspector.ProfileNode.TypeCookieKey] = this._type || null;
207        cookie[WebInspector.ProfileNode.FunctionNameCookieKey] = this._functionName || null;
208        cookie[WebInspector.ProfileNode.SourceCodeURLCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.sourceCode.url ? this._sourceCodeLocation.sourceCode.url.hash : null : null;
209        cookie[WebInspector.ProfileNode.SourceCodeLocationLineCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.lineNumber : null;
210        cookie[WebInspector.ProfileNode.SourceCodeLocationColumnCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.columnNumber : null;
211    },
212
213    // Protected
214
215    establishRelationships: function(parentNode, previousSibling, nextSibling)
216    {
217        this._parentNode = parentNode || null;
218        this._previousSibling = previousSibling || null;
219        this._nextSibling = nextSibling || null;
220    },
221
222    // Private
223
224    _computeTotalTimes: function()
225    {
226        if (this._computedTotalTimes)
227            return;
228
229        this._computedTotalTimes = true;
230
231        var info = this.computeCallInfoForTimeRange(0, Infinity);
232        this._startTime = info.startTime;
233        this._endTime = info.endTime;
234        this._selfTime = info.selfTime;
235        this._totalTime = info.totalTime;
236    }
237};
238