246 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
/*
 | 
						||
 * Copyright (C) 2021 The Android Open Source Project
 | 
						||
 *
 | 
						||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						||
 * you may not use this file except in compliance with the License.
 | 
						||
 * You may obtain a copy of the License at
 | 
						||
 *
 | 
						||
 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
						||
 *
 | 
						||
 * Unless required by applicable law or agreed to in writing, software
 | 
						||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						||
 * See the License for the specific language governing permissions and
 | 
						||
 * limitations under the License.
 | 
						||
 */
 | 
						||
 | 
						||
function generateHash (name) {
 | 
						||
  // Return a vector (0.0->1.0) that is a hash of the input string.
 | 
						||
  // The hash is computed to favor early characters over later ones, so
 | 
						||
  // that strings with similar starts have similar vectors. Only the first
 | 
						||
  // 6 characters are considered.
 | 
						||
  const MAX_CHAR = 6
 | 
						||
 | 
						||
  var hash = 0
 | 
						||
  var maxHash = 0
 | 
						||
  var weight = 1
 | 
						||
  var mod = 10
 | 
						||
 | 
						||
  if (name) {
 | 
						||
      for (var i = 0; i < name.length; i++) {
 | 
						||
          if (i > MAX_CHAR) { break }
 | 
						||
          hash += weight * (name.charCodeAt(i) % mod)
 | 
						||
          maxHash += weight * (mod - 1)
 | 
						||
          weight *= 0.70
 | 
						||
      }
 | 
						||
      if (maxHash > 0) { hash = hash / maxHash }
 | 
						||
  }
 | 
						||
  return hash
 | 
						||
}
 | 
						||
 | 
						||
function offCpuColorMapper (d) {
 | 
						||
  if (d.highlight) return '#E600E6'
 | 
						||
 | 
						||
  let name = d.data.n || d.data.name
 | 
						||
  let vector = 0
 | 
						||
  const nameArr = name.split('`')
 | 
						||
 | 
						||
  if (nameArr.length > 1) {
 | 
						||
      name = nameArr[nameArr.length - 1] // drop module name if present
 | 
						||
  }
 | 
						||
  name = name.split('(')[0] // drop extra info
 | 
						||
  vector = generateHash(name)
 | 
						||
 | 
						||
  const r = 0 + Math.round(55 * (1 - vector))
 | 
						||
  const g = 0 + Math.round(230 * (1 - vector))
 | 
						||
  const b = 200 + Math.round(55 * vector)
 | 
						||
 | 
						||
  return 'rgb(' + r + ',' + g + ',' + b + ')'
 | 
						||
}
 | 
						||
 | 
						||
var flame = flamegraph()
 | 
						||
  .cellHeight(18)
 | 
						||
  .width(window.innerWidth * 3 / 10 - 20) // 30% width
 | 
						||
  .transitionDuration(750)
 | 
						||
  .minFrameSize(5)
 | 
						||
  .transitionEase(d3.easeCubic)
 | 
						||
  .inverted(false)
 | 
						||
  .sort(true)
 | 
						||
  .title("")
 | 
						||
  //.differential(false)
 | 
						||
  //.elided(false)
 | 
						||
  .selfValue(false)
 | 
						||
  .setColorMapper(offCpuColorMapper);
 | 
						||
 | 
						||
 | 
						||
function update_table() {
 | 
						||
  let inverted = document.getElementById("inverted_checkbox").checked
 | 
						||
  let regex
 | 
						||
  let graph_source = Bokeh.documents[0].get_model_by_name('graph').renderers[0].data_source
 | 
						||
  let table_source = Bokeh.documents[0].get_model_by_name('table').source
 | 
						||
 | 
						||
  let graph_selection = graph_source.selected.indices
 | 
						||
  let threads = graph_source.data.thread
 | 
						||
  let callchains = graph_source.data.callchain
 | 
						||
 | 
						||
  let selection_len = graph_selection.length;
 | 
						||
 | 
						||
  if (document.getElementById("regex").value) {
 | 
						||
    regex = new RegExp(document.getElementById("regex").value)
 | 
						||
  }
 | 
						||
 | 
						||
  table_source.data.thread = []
 | 
						||
  table_source.data.count = []
 | 
						||
  table_source.data.index = []
 | 
						||
 | 
						||
  for (let i = 0; i < selection_len; i ++) {
 | 
						||
    let entry = "<no callchain>"
 | 
						||
 | 
						||
    if (regex !== undefined && !regex.test(callchains[graph_selection[i]])) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    if (inverted) {
 | 
						||
      let callchain = callchains[graph_selection[i]].split("<br>")
 | 
						||
 | 
						||
      for (let e = 0; e < callchain.length; e ++) {
 | 
						||
        if (callchain[e] != "") { // last entry is apparently always an empty string
 | 
						||
          entry = callchain[e]
 | 
						||
          break
 | 
						||
        }
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      entry = threads[graph_selection[i]]
 | 
						||
    }
 | 
						||
 | 
						||
    let pos = table_source.data.thread.indexOf(entry)
 | 
						||
 | 
						||
    if(pos == -1) {
 | 
						||
      table_source.data.thread.push(entry)
 | 
						||
      table_source.data.count.push(1)
 | 
						||
      table_source.data.index.push(table_source.data.thread.length)
 | 
						||
    } else {
 | 
						||
      table_source.data.count[pos] ++
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  table_source.selected.indices = []
 | 
						||
  table_source.change.emit()
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
function should_insert_callchain(callchain, items, filter_index, inverted) {
 | 
						||
  for (t = 0; t < filter_index.length; t ++) {
 | 
						||
    if (callchain[0] === items[filter_index[t]]) {
 | 
						||
      return true
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (filter_index.length > 0) {
 | 
						||
    return false
 | 
						||
  }
 | 
						||
 | 
						||
  return true
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
function insert_callchain(root, callchain, inverted) {
 | 
						||
  let root_pos = -1
 | 
						||
  let node = root
 | 
						||
 | 
						||
  node.value ++
 | 
						||
 | 
						||
  for (let e = 0; e < callchain.length; e ++) {
 | 
						||
    let entry = callchain[e].replace(/^\s+|\s+$/g, '')
 | 
						||
    let entry_pos = -1
 | 
						||
 | 
						||
    for (let j = 0; j < node.children.length; j ++) {
 | 
						||
      if (node.children[j].name == entry) {
 | 
						||
        entry_pos = j
 | 
						||
        break
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    if (entry_pos == -1) {
 | 
						||
      node.children.push({name: entry, value:0, children:[]})
 | 
						||
      entry_pos = node.children.length - 1
 | 
						||
    }
 | 
						||
 | 
						||
    node = node.children[entry_pos]
 | 
						||
    node.value ++
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
function update_flamegraph() {
 | 
						||
  let inverted = document.getElementById("inverted_checkbox").checked
 | 
						||
  let root = {name: inverted ? "samples" : "processes", value: 0, children: []}
 | 
						||
 | 
						||
  let graph_source = Bokeh.documents[0].get_model_by_name('graph').renderers[0].data_source
 | 
						||
  let graph_selection = graph_source.selected.indices
 | 
						||
  let callchains = graph_source.data.callchain
 | 
						||
  let graph_threads = graph_source.data.thread
 | 
						||
 | 
						||
  let table_source = Bokeh.documents[0].get_model_by_name('table').source
 | 
						||
  let table_selection = table_source.selected.indices
 | 
						||
  let table_threads = table_source.data.thread
 | 
						||
  let regex
 | 
						||
 | 
						||
  if (document.getElementById("regex").value) {
 | 
						||
    regex = new RegExp(document.getElementById("regex").value)
 | 
						||
  }
 | 
						||
 | 
						||
  for (let i = 0; i < graph_selection.length; i ++) {
 | 
						||
    let thread = graph_threads[graph_selection[i]]
 | 
						||
    let callchain = callchains[graph_selection[i]].split("<br>")
 | 
						||
    callchain = callchain.filter(function(e){return e != ""})
 | 
						||
 | 
						||
    if (regex !== undefined && !regex.test(callchains[graph_selection[i]])) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    if (callchain.length == 0) {
 | 
						||
      callchain.push("<no callchain>")
 | 
						||
    }
 | 
						||
 | 
						||
    callchain.push(thread)
 | 
						||
 | 
						||
    if (!inverted){
 | 
						||
      callchain = callchain.reverse()
 | 
						||
    }
 | 
						||
 | 
						||
    if (should_insert_callchain(callchain, table_threads, table_selection)) {
 | 
						||
      insert_callchain(root, callchain)
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (root.children.length == 1) {
 | 
						||
    root = root.children[0]
 | 
						||
  }
 | 
						||
 | 
						||
  d3.select("#flame")
 | 
						||
      .datum(root)
 | 
						||
      .call(flame)
 | 
						||
}
 | 
						||
 | 
						||
var help_dialog = document.getElementById("help_dialog");
 | 
						||
 | 
						||
document.getElementById("help_button").onclick = function() {
 | 
						||
  help_dialog.style.display = "block";
 | 
						||
}
 | 
						||
 | 
						||
window.onclick = function(event) {
 | 
						||
  if (event.target == help_dialog) {
 | 
						||
    help_dialog.style.display = "none";
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
document.getElementsByClassName("dialog_close")[0].onclick = function() {
 | 
						||
  help_dialog.style.display = "none";
 | 
						||
}
 | 
						||
 | 
						||
function update_selections() {
 | 
						||
  update_flamegraph()
 | 
						||
  update_table()
 | 
						||
}
 |