• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17function generateHash (name) {
18  // Return a vector (0.0->1.0) that is a hash of the input string.
19  // The hash is computed to favor early characters over later ones, so
20  // that strings with similar starts have similar vectors. Only the first
21  // 6 characters are considered.
22  const MAX_CHAR = 6
23
24  var hash = 0
25  var maxHash = 0
26  var weight = 1
27  var mod = 10
28
29  if (name) {
30      for (var i = 0; i < name.length; i++) {
31          if (i > MAX_CHAR) { break }
32          hash += weight * (name.charCodeAt(i) % mod)
33          maxHash += weight * (mod - 1)
34          weight *= 0.70
35      }
36      if (maxHash > 0) { hash = hash / maxHash }
37  }
38  return hash
39}
40
41function offCpuColorMapper (d) {
42  if (d.highlight) return '#E600E6'
43
44  let name = d.data.n || d.data.name
45  let vector = 0
46  const nameArr = name.split('`')
47
48  if (nameArr.length > 1) {
49      name = nameArr[nameArr.length - 1] // drop module name if present
50  }
51  name = name.split('(')[0] // drop extra info
52  vector = generateHash(name)
53
54  const r = 0 + Math.round(55 * (1 - vector))
55  const g = 0 + Math.round(230 * (1 - vector))
56  const b = 200 + Math.round(55 * vector)
57
58  return 'rgb(' + r + ',' + g + ',' + b + ')'
59}
60
61var flame = flamegraph()
62  .cellHeight(18)
63  .width(window.innerWidth * 3 / 10 - 20) // 30% width
64  .transitionDuration(750)
65  .minFrameSize(5)
66  .transitionEase(d3.easeCubic)
67  .inverted(false)
68  .sort(true)
69  .title("")
70  //.differential(false)
71  //.elided(false)
72  .selfValue(false)
73  .setColorMapper(offCpuColorMapper);
74
75
76function update_table() {
77  let inverted = document.getElementById("inverted_checkbox").checked
78  let regex
79  let graph_source = Bokeh.documents[0].get_model_by_name('graph').renderers[0].data_source
80  let table_source = Bokeh.documents[0].get_model_by_name('table').source
81
82  let graph_selection = graph_source.selected.indices
83  let threads = graph_source.data.thread
84  let callchains = graph_source.data.callchain
85
86  let selection_len = graph_selection.length;
87
88  if (document.getElementById("regex").value) {
89    regex = new RegExp(document.getElementById("regex").value)
90  }
91
92  table_source.data.thread = []
93  table_source.data.count = []
94  table_source.data.index = []
95
96  for (let i = 0; i < selection_len; i ++) {
97    let entry = "<no callchain>"
98
99    if (regex !== undefined && !regex.test(callchains[graph_selection[i]])) {
100      continue;
101    }
102
103    if (inverted) {
104      let callchain = callchains[graph_selection[i]].split("<br>")
105
106      for (let e = 0; e < callchain.length; e ++) {
107        if (callchain[e] != "") { // last entry is apparently always an empty string
108          entry = callchain[e]
109          break
110        }
111      }
112    } else {
113      entry = threads[graph_selection[i]]
114    }
115
116    let pos = table_source.data.thread.indexOf(entry)
117
118    if(pos == -1) {
119      table_source.data.thread.push(entry)
120      table_source.data.count.push(1)
121      table_source.data.index.push(table_source.data.thread.length)
122    } else {
123      table_source.data.count[pos] ++
124    }
125  }
126
127  table_source.selected.indices = []
128  table_source.change.emit()
129}
130
131
132function should_insert_callchain(callchain, items, filter_index, inverted) {
133  for (t = 0; t < filter_index.length; t ++) {
134    if (callchain[0] === items[filter_index[t]]) {
135      return true
136    }
137  }
138
139  if (filter_index.length > 0) {
140    return false
141  }
142
143  return true
144}
145
146
147function insert_callchain(root, callchain, inverted) {
148  let root_pos = -1
149  let node = root
150
151  node.value ++
152
153  for (let e = 0; e < callchain.length; e ++) {
154    let entry = callchain[e].replace(/^\s+|\s+$/g, '')
155    let entry_pos = -1
156
157    for (let j = 0; j < node.children.length; j ++) {
158      if (node.children[j].name == entry) {
159        entry_pos = j
160        break
161      }
162    }
163
164    if (entry_pos == -1) {
165      node.children.push({name: entry, value:0, children:[]})
166      entry_pos = node.children.length - 1
167    }
168
169    node = node.children[entry_pos]
170    node.value ++
171  }
172}
173
174
175function update_flamegraph() {
176  let inverted = document.getElementById("inverted_checkbox").checked
177  let root = {name: inverted ? "samples" : "processes", value: 0, children: []}
178
179  let graph_source = Bokeh.documents[0].get_model_by_name('graph').renderers[0].data_source
180  let graph_selection = graph_source.selected.indices
181  let callchains = graph_source.data.callchain
182  let graph_threads = graph_source.data.thread
183
184  let table_source = Bokeh.documents[0].get_model_by_name('table').source
185  let table_selection = table_source.selected.indices
186  let table_threads = table_source.data.thread
187  let regex
188
189  if (document.getElementById("regex").value) {
190    regex = new RegExp(document.getElementById("regex").value)
191  }
192
193  for (let i = 0; i < graph_selection.length; i ++) {
194    let thread = graph_threads[graph_selection[i]]
195    let callchain = callchains[graph_selection[i]].split("<br>")
196    callchain = callchain.filter(function(e){return e != ""})
197
198    if (regex !== undefined && !regex.test(callchains[graph_selection[i]])) {
199      continue;
200    }
201
202    if (callchain.length == 0) {
203      callchain.push("<no callchain>")
204    }
205
206    callchain.push(thread)
207
208    if (!inverted){
209      callchain = callchain.reverse()
210    }
211
212    if (should_insert_callchain(callchain, table_threads, table_selection)) {
213      insert_callchain(root, callchain)
214    }
215  }
216
217  if (root.children.length == 1) {
218    root = root.children[0]
219  }
220
221  d3.select("#flame")
222      .datum(root)
223      .call(flame)
224}
225
226var help_dialog = document.getElementById("help_dialog");
227
228document.getElementById("help_button").onclick = function() {
229  help_dialog.style.display = "block";
230}
231
232window.onclick = function(event) {
233  if (event.target == help_dialog) {
234    help_dialog.style.display = "none";
235  }
236}
237
238document.getElementsByClassName("dialog_close")[0].onclick = function() {
239  help_dialog.style.display = "none";
240}
241
242function update_selections() {
243  update_flamegraph()
244  update_table()
245}
246