| /* |
| * Copyright (C) 2014 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.ProfileNode = class ProfileNode |
| { |
| constructor(id, type, functionName, sourceCodeLocation, callInfo, calls, childNodes) |
| { |
| childNodes = childNodes || []; |
| |
| console.assert(id); |
| console.assert(!calls || calls instanceof Array); |
| console.assert(!calls || calls.length >= 1); |
| console.assert(!calls || calls.every((call) => call instanceof WI.ProfileNodeCall)); |
| console.assert(childNodes instanceof Array); |
| console.assert(childNodes.every((node) => node instanceof WI.ProfileNode)); |
| |
| this._id = id; |
| this._type = type || WI.ProfileNode.Type.Function; |
| this._functionName = functionName || null; |
| this._sourceCodeLocation = sourceCodeLocation || null; |
| this._calls = calls || null; |
| this._callInfo = callInfo || null; |
| this._childNodes = childNodes; |
| this._parentNode = null; |
| this._previousSibling = null; |
| this._nextSibling = null; |
| this._computedTotalTimes = false; |
| |
| if (this._callInfo) { |
| this._startTime = this._callInfo.startTime; |
| this._endTime = this._callInfo.endTime; |
| this._totalTime = this._callInfo.totalTime; |
| this._callCount = this._callInfo.callCount; |
| } |
| |
| for (var i = 0; i < this._childNodes.length; ++i) |
| this._childNodes[i].establishRelationships(this, this._childNodes[i - 1], this._childNodes[i + 1]); |
| |
| if (this._calls) { |
| for (var i = 0; i < this._calls.length; ++i) |
| this._calls[i].establishRelationships(this, this._calls[i - 1], this._calls[i + 1]); |
| } |
| } |
| |
| // Public |
| |
| get id() |
| { |
| return this._id; |
| } |
| |
| get type() |
| { |
| return this._type; |
| } |
| |
| get functionName() |
| { |
| return this._functionName; |
| } |
| |
| get sourceCodeLocation() |
| { |
| return this._sourceCodeLocation; |
| } |
| |
| get startTime() |
| { |
| if (this._startTime === undefined) |
| this._startTime = Math.max(0, this._calls[0].startTime); |
| return this._startTime; |
| } |
| |
| get endTime() |
| { |
| if (this._endTime === undefined) |
| this._endTime = Math.min(this._calls.lastValue.endTime, Infinity); |
| return this._endTime; |
| } |
| |
| get selfTime() |
| { |
| this._computeTotalTimesIfNeeded(); |
| return this._selfTime; |
| } |
| |
| get totalTime() |
| { |
| this._computeTotalTimesIfNeeded(); |
| return this._totalTime; |
| } |
| |
| get callInfo() |
| { |
| return this._callInfo; |
| } |
| |
| get calls() |
| { |
| return this._calls; |
| } |
| |
| get previousSibling() |
| { |
| return this._previousSibling; |
| } |
| |
| get nextSibling() |
| { |
| return this._nextSibling; |
| } |
| |
| get parentNode() |
| { |
| return this._parentNode; |
| } |
| |
| get childNodes() |
| { |
| return this._childNodes; |
| } |
| |
| computeCallInfoForTimeRange(rangeStartTime, rangeEndTime) |
| { |
| console.assert(typeof rangeStartTime === "number"); |
| console.assert(typeof rangeEndTime === "number"); |
| |
| // With aggregate call info we can't accurately partition self/total/average time |
| // in partial ranges because we don't know exactly when each call started. So we |
| // always return the entire range. |
| if (this._callInfo) { |
| if (this._selfTime === undefined) { |
| var childNodesTotalTime = 0; |
| for (var childNode of this._childNodes) |
| childNodesTotalTime += childNode.totalTime; |
| this._selfTime = this._totalTime - childNodesTotalTime; |
| // selfTime can negative or very close to zero due to floating point error. |
| // Since we show at most four decimal places, treat anything less as zero. |
| if (this._selfTime < 0.0001) |
| this._selfTime = 0.0; |
| } |
| |
| return { |
| callCount: this._callCount, |
| startTime: this._startTime, |
| endTime: this._endTime, |
| selfTime: this._selfTime, |
| totalTime: this._totalTime, |
| averageTime: this._selfTime / this._callCount, |
| }; |
| } |
| |
| // COMPATIBILITY (iOS 8): Profiles included per-call information and can be finely partitioned. |
| // Compute that below by iterating over all the calls / children for the time range. |
| |
| var recordCallCount = true; |
| var callCount = 0; |
| |
| function totalTimeInRange(previousValue, call) |
| { |
| if (rangeStartTime > call.endTime || rangeEndTime < call.startTime) |
| return previousValue; |
| |
| if (recordCallCount) |
| ++callCount; |
| |
| return previousValue + Math.min(call.endTime, rangeEndTime) - Math.max(rangeStartTime, call.startTime); |
| } |
| |
| var startTime = Math.max(rangeStartTime, this._calls[0].startTime); |
| var endTime = Math.min(this._calls.lastValue.endTime, rangeEndTime); |
| var totalTime = this._calls.reduce(totalTimeInRange, 0); |
| |
| recordCallCount = false; |
| |
| var childNodesTotalTime = 0; |
| for (var childNode of this._childNodes) |
| childNodesTotalTime += childNode.calls.reduce(totalTimeInRange, 0); |
| |
| var selfTime = totalTime - childNodesTotalTime; |
| var averageTime = selfTime / callCount; |
| |
| return {startTime, endTime, totalTime, selfTime, callCount, averageTime}; |
| } |
| |
| traverseNextProfileNode(stayWithin) |
| { |
| var profileNode = this._childNodes[0]; |
| if (profileNode) |
| return profileNode; |
| |
| if (this === stayWithin) |
| return null; |
| |
| profileNode = this._nextSibling; |
| if (profileNode) |
| return profileNode; |
| |
| profileNode = this; |
| while (profileNode && !profileNode.nextSibling && profileNode.parentNode !== stayWithin) |
| profileNode = profileNode.parentNode; |
| |
| if (!profileNode) |
| return null; |
| |
| return profileNode.nextSibling; |
| } |
| |
| saveIdentityToCookie(cookie) |
| { |
| cookie[WI.ProfileNode.TypeCookieKey] = this._type || null; |
| cookie[WI.ProfileNode.FunctionNameCookieKey] = this._functionName || null; |
| cookie[WI.ProfileNode.SourceCodeURLCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.sourceCode.url ? this._sourceCodeLocation.sourceCode.url.hash : null : null; |
| cookie[WI.ProfileNode.SourceCodeLocationLineCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.lineNumber : null; |
| cookie[WI.ProfileNode.SourceCodeLocationColumnCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.columnNumber : null; |
| } |
| |
| // Protected |
| |
| establishRelationships(parentNode, previousSibling, nextSibling) |
| { |
| this._parentNode = parentNode || null; |
| this._previousSibling = previousSibling || null; |
| this._nextSibling = nextSibling || null; |
| } |
| |
| // Private |
| |
| _computeTotalTimesIfNeeded() |
| { |
| if (this._computedTotalTimes) |
| return; |
| |
| this._computedTotalTimes = true; |
| |
| var info = this.computeCallInfoForTimeRange(0, Infinity); |
| this._startTime = info.startTime; |
| this._endTime = info.endTime; |
| this._selfTime = info.selfTime; |
| this._totalTime = info.totalTime; |
| } |
| }; |
| |
| WI.ProfileNode.Type = { |
| Function: "profile-node-type-function", |
| Program: "profile-node-type-program" |
| }; |
| |
| WI.ProfileNode.TypeIdentifier = "profile-node"; |
| WI.ProfileNode.TypeCookieKey = "profile-node-type"; |
| WI.ProfileNode.FunctionNameCookieKey = "profile-node-function-name"; |
| WI.ProfileNode.SourceCodeURLCookieKey = "profile-node-source-code-url"; |
| WI.ProfileNode.SourceCodeLocationLineCookieKey = "profile-node-source-code-location-line"; |
| WI.ProfileNode.SourceCodeLocationColumnCookieKey = "profile-node-source-code-location-column"; |