diff options
Diffstat (limited to 'simpleperf/scripts/purgatorio/templates/main.js')
-rw-r--r-- | simpleperf/scripts/purgatorio/templates/main.js | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/simpleperf/scripts/purgatorio/templates/main.js b/simpleperf/scripts/purgatorio/templates/main.js new file mode 100644 index 00000000..ec8e7cbe --- /dev/null +++ b/simpleperf/scripts/purgatorio/templates/main.js @@ -0,0 +1,245 @@ +/* + * 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() +} |