android13/system/extras/simpleperf/scripts/purgatorio/templates/main.js

246 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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()
}