Index: runtime/observatory/lib/src/cpu_profile/cpu_profile.dart |
diff --git a/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart b/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..99744f6029952257f37e8723dac377569ce17cba |
--- /dev/null |
+++ b/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart |
@@ -0,0 +1,312 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+part of cpu_profiler; |
+ |
+class CodeTrieNode { |
+ final ProfileCode profileCode; |
+ final int count; |
+ final children = new List<CodeTrieNode>(); |
+ CodeTrieNode(this.profileCode, this.count); |
+} |
+ |
+class FunctionTrieNodeCode { |
+ final ProfileCode code; |
+ final int ticks; |
+ FunctionTrieNodeCode(this.code, this.ticks); |
+} |
+ |
+class FunctionTrieNode { |
+ final ProfileFunction profileFunction; |
+ final int count; |
+ final children = new List<FunctionTrieNode>(); |
+ final codes = new List<FunctionTrieNodeCode>(); |
+ int _totalCodeTicks = 0; |
+ int get totalCodesTicks => _totalCodeTicks; |
+ FunctionTrieNode(this.profileFunction, this.count); |
+} |
+ |
+class CodeTick { |
+ final int exclusiveTicks; |
+ final int inclusiveTicks; |
+ CodeTick(this.exclusiveTicks, this.inclusiveTicks); |
+} |
+ |
+class InlineIntervalTick { |
+ final int startAddress; |
+ int _inclusiveTicks = 0; |
+ int get inclusiveTicks => _inclusiveTicks; |
+ int _exclusiveTicks = 0; |
+ int get exclusiveTicks => _exclusiveTicks; |
+ InlineIntervalTick(this.startAddress); |
+} |
+ |
+class ProfileCode { |
+ final CpuProfile profile; |
+ final Code code; |
+ int exclusiveTicks; |
+ int inclusiveTicks; |
+ final addressTicks = new Map<int, CodeTick>(); |
+ final intervalTicks = new Map<int, InlineIntervalTick>(); |
+ String formattedInclusiveTicks = ''; |
+ String formattedExclusiveTicks = ''; |
+ |
+ void _processTicks(List<String> profileTicks) { |
+ assert(profileTicks != null); |
+ assert((profileTicks.length % 3) == 0); |
+ for (var i = 0; i < profileTicks.length; i += 3) { |
+ var address = int.parse(profileTicks[i], radix:16); |
+ var exclusive = int.parse(profileTicks[i + 1]); |
+ var inclusive = int.parse(profileTicks[i + 2]); |
+ var tick = new CodeTick(exclusive, inclusive); |
+ addressTicks[address] = tick; |
+ |
+ var interval = code.findInterval(address); |
+ if (interval != null) { |
+ var intervalTick = intervalTicks[interval.start]; |
+ if (intervalTick == null) { |
+ // Insert into map. |
+ intervalTick = new InlineIntervalTick(interval.start); |
+ intervalTicks[interval.start] = intervalTick; |
+ } |
+ intervalTick._inclusiveTicks += inclusive; |
+ intervalTick._exclusiveTicks += exclusive; |
+ } |
+ } |
+ } |
+ |
+ ProfileCode.fromMap(this.profile, this.code, Map data) { |
+ assert(profile != null); |
+ assert(code != null); |
+ |
+ code.profile = this; |
+ |
+ inclusiveTicks = int.parse(data['inclusiveTicks']); |
+ exclusiveTicks = int.parse(data['exclusiveTicks']); |
+ var ticks = data['ticks']; |
+ if (ticks != null) { |
+ _processTicks(ticks); |
+ } |
+ |
+ formattedInclusiveTicks = |
+ '${Utils.formatPercent(inclusiveTicks, profile.sampleCount)} ' |
+ '($inclusiveTicks)'; |
+ |
+ formattedExclusiveTicks = |
+ '${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} ' |
+ '($exclusiveTicks)'; |
+ } |
+} |
+ |
+class ProfileFunction { |
+ final CpuProfile profile; |
+ final ServiceFunction function; |
+ // List of compiled code objects containing this function. |
+ final List<ProfileCode> profileCodes = new List<ProfileCode>(); |
+ |
+ // Absolute ticks: |
+ int exclusiveTicks = 0; |
+ int inclusiveTicks = 0; |
+ |
+ // Global percentages: |
+ double globalExclusiveTicks = 0.0; |
+ double globalInclusiveTicks = 0.0; |
+ |
+ int _sortCodes(ProfileCode a, ProfileCode b) { |
+ if (a.code.isOptimized == b.code.isOptimized) { |
+ return b.code.profile.exclusiveTicks - a.code.profile.exclusiveTicks; |
+ } |
+ if (a.code.isOptimized) { |
+ return -1; |
+ } |
+ return 1; |
+ } |
+ |
+ ProfileFunction.fromMap(this.profile, this.function, Map data) { |
+ for (var codeIndex in data['codes']) { |
+ var profileCode = profile.codes[codeIndex]; |
+ profileCodes.add(profileCode); |
+ } |
+ profileCodes.sort(_sortCodes); |
+ |
+ exclusiveTicks = int.parse(data['exclusiveTicks']); |
+ inclusiveTicks = int.parse(data['inclusiveTicks']); |
+ |
+ globalExclusiveTicks = exclusiveTicks / profile.sampleCount; |
+ globalInclusiveTicks = inclusiveTicks / profile.sampleCount; |
+ } |
+} |
+ |
+ |
+class CpuProfile { |
+ final double MICROSECONDS_PER_SECOND = 1000000.0; |
+ final double displayThreshold = 0.0002; // 0.02%. |
+ |
+ Isolate isolate; |
+ |
+ int sampleCount = 0; |
+ int samplePeriod = 0; |
+ double sampleRate = 0.0; |
+ |
+ int stackDepth = 0; |
+ |
+ double timeSpan = 0.0; |
+ |
+ CodeTrieNode codeTrieRoot; |
+ FunctionTrieNode functionTrieRoot; |
+ |
+ final List<ProfileCode> codes = new List<ProfileCode>(); |
+ final List<ProfileFunction> functions = new List<ProfileFunction>(); |
+ |
+ void clear() { |
+ sampleCount = 0; |
+ samplePeriod = 0; |
+ sampleRate = 0.0; |
+ stackDepth = 0; |
+ timeSpan = 0.0; |
+ codeTrieRoot = null; |
+ functionTrieRoot = null; |
+ codes.clear(); |
+ functions.clear(); |
+ } |
+ |
+ void load(Isolate isolate, ServiceMap profile) { |
+ if ((isolate == null) || (profile == null)) { |
+ return; |
+ } |
+ |
+ this.isolate = isolate; |
+ isolate.resetCachedProfileData(); |
+ |
+ clear(); |
+ |
+ sampleCount = profile['sampleCount']; |
+ samplePeriod = profile['samplePeriod']; |
+ sampleRate = (MICROSECONDS_PER_SECOND / samplePeriod); |
+ stackDepth = profile['stackDepth']; |
+ timeSpan = profile['timeSpan']; |
+ |
+ // Process code table. |
+ for (var codeRegion in profile['codes']) { |
+ Code code = codeRegion['code']; |
+ assert(code != null); |
+ codes.add(new ProfileCode.fromMap(this, code, codeRegion)); |
+ } |
+ |
+ // Process function table. |
+ for (var profileFunction in profile['functions']) { |
+ ServiceFunction function = profileFunction['function']; |
+ assert(function != null); |
+ functions.add( |
+ new ProfileFunction.fromMap(this, function, profileFunction)); |
+ } |
+ |
+ // Process code trie. |
+ var exclusiveCodeTrie = profile['exclusiveCodeTrie']; |
+ assert(exclusiveCodeTrie != null); |
+ codeTrieRoot = _processCodeTrie(exclusiveCodeTrie); |
+ |
+ // Process function trie. |
+ var exclusiveFunctionTrie = profile['exclusiveFunctionTrie']; |
+ assert(exclusiveFunctionTrie != null); |
+ functionTrieRoot = _processFunctionTrie(exclusiveFunctionTrie); |
+ } |
+ |
+ // Data shared across calls to _read*TrieNode. |
+ int _trieDataCursor; |
+ List<int> _trieData; |
+ |
+ // The code trie is serialized as a list of integers. Each node |
+ // is recreated by consuming some portion of the list. The format is as |
+ // follows: |
+ // [0] index into codeTable of code object. |
+ // [1] tick count (number of times this stack frame occured). |
+ // [2] child node count |
+ // Reading the trie is done by recursively reading the tree depth-first |
+ // pre-order. |
+ CodeTrieNode _processCodeTrie(List<int> data) { |
+ // Setup state shared across calls to _readTrieNode. |
+ _trieDataCursor = 0; |
+ _trieData = data; |
+ if (_trieData == null) { |
+ return null; |
+ } |
+ if (_trieData.length < 3) { |
+ // Not enough integers for 1 node. |
+ return null; |
+ } |
+ // Read the tree, returns the root node. |
+ return _readCodeTrieNode(); |
+ } |
+ |
+ CodeTrieNode _readCodeTrieNode() { |
+ // Read index into code table. |
+ var index = _trieData[_trieDataCursor++]; |
+ // Lookup code object. |
+ var code = codes[index]; |
+ // Frame counter. |
+ var count = _trieData[_trieDataCursor++]; |
+ // Create node. |
+ var node = new CodeTrieNode(code, count); |
+ // Number of children. |
+ var children = _trieData[_trieDataCursor++]; |
+ // Recursively read child nodes. |
+ for (var i = 0; i < children; i++) { |
+ var child = _readCodeTrieNode(); |
+ node.children.add(child); |
+ } |
+ return node; |
+ } |
+ |
+ FunctionTrieNode _processFunctionTrie(List<int> data) { |
+ // Setup state shared across calls to _readTrieNode. |
+ _trieDataCursor = 0; |
+ _trieData = data; |
+ if (_trieData == null) { |
+ return null; |
+ } |
+ if (_trieData.length < 3) { |
+ // Not enough integers for 1 node. |
+ return null; |
+ } |
+ // Read the tree, returns the root node. |
+ return _readFunctionTrieNode(); |
+ } |
+ |
+ FunctionTrieNode _readFunctionTrieNode() { |
+ // Read index into function table. |
+ var index = _trieData[_trieDataCursor++]; |
+ // Lookup function object. |
+ var function = functions[index]; |
+ // Frame counter. |
+ var count = _trieData[_trieDataCursor++]; |
+ // Create node. |
+ var node = new FunctionTrieNode(function, count); |
+ // Number of code index / count pairs. |
+ var codeCount = _trieData[_trieDataCursor++]; |
+ var totalCodeTicks = 0; |
+ for (var i = 0; i < codeCount; i++) { |
+ var codeIndex = _trieData[_trieDataCursor++]; |
+ var code = codes[codeIndex]; |
+ var codeTicks = _trieData[_trieDataCursor++]; |
+ totalCodeTicks += codeTicks; |
+ var nodeCode = new FunctionTrieNodeCode(code, codeTicks); |
+ node.codes.add(nodeCode); |
+ } |
+ node._totalCodeTicks = totalCodeTicks; |
+ // Number of children. |
+ var children = _trieData[_trieDataCursor++]; |
+ // Recursively read child nodes. |
+ for (var i = 0; i < children; i++) { |
+ var child = _readFunctionTrieNode(); |
+ node.children.add(child); |
+ } |
+ return node; |
+ } |
+ |
+ double approximateSecondsForCount(count) { |
+ var MICROSECONDS_PER_SECOND = 1000000.0; |
+ return (count * samplePeriod) / MICROSECONDS_PER_SECOND; |
+ } |
+} |