/*
* Copyright (C) 2017 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.
*/
'use strict';
// Use IIFE to avoid leaking names to other scripts.
(function () {
function getTimeInMs() {
return new Date().getTime();
}
class TimeLog {
constructor() {
this.start = getTimeInMs();
}
log(name) {
let end = getTimeInMs();
console.log(name, end - this.start, 'ms');
this.start = end;
}
}
class ProgressBar {
constructor() {
let str = `
Loading page...
0%
`;
this.modal = $(str).appendTo($('body'));
this.progress = 0;
this.shownCallback = null;
this.modal.on('shown.bs.modal', () => this._onShown());
// Shorten progress bar update time.
this.modal.find('.progress-bar').css('transition-duration', '0ms');
this.shown = false;
}
// progress is [0-100]. Return a Promise resolved when the update is shown.
updateAsync(text, progress) {
progress = parseInt(progress); // Truncate float number to integer.
return this.showAsync().then(() => {
if (text) {
this.modal.find('.modal-title').text(text);
}
this.progress = progress;
this.modal.find('.progress-bar').css('width', progress + '%')
.attr('aria-valuenow', progress).text(progress + '%');
// Leave 100ms for the progess bar to update.
return createPromise((resolve) => setTimeout(resolve, 100));
});
}
showAsync() {
if (this.shown) {
return createPromise();
}
return createPromise((resolve) => {
this.shownCallback = resolve;
this.modal.modal({
show: true,
keyboard: false,
backdrop: false,
});
});
}
_onShown() {
this.shown = true;
if (this.shownCallback) {
let callback = this.shownCallback;
this.shownCallback = null;
callback();
}
}
hide() {
this.shown = false;
this.modal.modal('hide');
}
}
function openHtml(name, attrs={}) {
let s = `<${name} `;
for (let key in attrs) {
s += `${key}="${attrs[key]}" `;
}
s += '>';
return s;
}
function closeHtml(name) {
return `${name}>`;
}
function getHtml(name, attrs={}) {
let text;
if ('text' in attrs) {
text = attrs.text;
delete attrs.text;
}
let s = openHtml(name, attrs);
if (text) {
s += text;
}
s += closeHtml(name);
return s;
}
function getTableRow(cols, colName, attrs={}) {
let s = openHtml('tr', attrs);
for (let col of cols) {
s += `<${colName}>${col}${colName}>`;
}
s += '';
return s;
}
function getProcessName(pid) {
let name = gProcesses[pid];
return name ? `${pid} (${name})`: pid.toString();
}
function getThreadName(tid) {
let name = gThreads[tid];
return name ? `${tid} (${name})`: tid.toString();
}
function getLibName(libId) {
return gLibList[libId];
}
function getFuncName(funcId) {
return gFunctionMap[funcId].f;
}
function getLibNameOfFunction(funcId) {
return getLibName(gFunctionMap[funcId].l);
}
function getFuncSourceRange(funcId) {
let func = gFunctionMap[funcId];
if (func.hasOwnProperty('s')) {
return {fileId: func.s[0], startLine: func.s[1], endLine: func.s[2]};
}
return null;
}
function getFuncDisassembly(funcId) {
let func = gFunctionMap[funcId];
return func.hasOwnProperty('d') ? func.d : null;
}
function getSourceFilePath(sourceFileId) {
return gSourceFiles[sourceFileId].path;
}
function getSourceCode(sourceFileId) {
return gSourceFiles[sourceFileId].code;
}
function isClockEvent(eventInfo) {
return eventInfo.eventName.includes('task-clock') ||
eventInfo.eventName.includes('cpu-clock');
}
let createId = function() {
let currentId = 0;
return () => `id${++currentId}`;
}();
class TabManager {
constructor(divContainer) {
let id = createId();
divContainer.append(`
`);
this.ul = divContainer.find(`#${id}`);
this.content = divContainer.find(`#${id}Content`);
// Map from title to [tabObj, drawn=false|true].
this.tabs = new Map();
this.tabActiveCallback = null;
}
addTab(title, tabObj) {
let id = createId();
this.content.append(``);
this.ul.append(`
';
return ul + content;
}
function createViewsForEvents(div, createViewCallback) {
let views = [];
if (gSampleInfo.length == 1) {
views.push(createViewCallback(div, gSampleInfo[0]));
} else if (gSampleInfo.length > 1) {
// If more than one event, draw them in tabs.
let id = createId();
div.append(createEventTabs(id));
for (let i = 0; i < gSampleInfo.length; ++i) {
let subId = id + '_' + i;
views.push(createViewCallback(div.find(`#${subId}`), gSampleInfo[i]));
}
div.find(`#${id}_0-tab`).tab('show');
}
return views;
}
// Return a promise to draw views.
function drawViewsAsync(views, totalProgress, drawViewCallback) {
if (views.length == 0) {
return createPromise();
}
let drawPos = 0;
let eachProgress = totalProgress / views.length;
function drawAsync() {
if (drawPos == views.length) {
return createPromise();
}
return drawViewCallback(views[drawPos++], eachProgress).then(drawAsync);
}
return drawAsync();
}
// Show global information retrieved from the record file, including:
// record time
// machine type
// Android version
// record cmdline
// total samples
class RecordFileView {
constructor(divContainer) {
this.div = $('
');
this.div.appendTo(divContainer);
}
draw() {
google.charts.setOnLoadCallback(() => this.realDraw());
}
realDraw() {
this.div.empty();
// Draw a table of 'Name', 'Value'.
let rows = [];
if (gRecordInfo.recordTime) {
rows.push(['Record Time', gRecordInfo.recordTime]);
}
if (gRecordInfo.machineType) {
rows.push(['Machine Type', gRecordInfo.machineType]);
}
if (gRecordInfo.androidVersion) {
rows.push(['Android Version', gRecordInfo.androidVersion]);
}
if (gRecordInfo.recordCmdline) {
rows.push(['Record cmdline', gRecordInfo.recordCmdline]);
}
rows.push(['Total Samples', '' + gRecordInfo.totalSamples]);
let data = new google.visualization.DataTable();
data.addColumn('string', '');
data.addColumn('string', '');
data.addRows(rows);
for (let i = 0; i < rows.length; ++i) {
data.setProperty(i, 0, 'className', 'boldTableCell');
}
let table = new google.visualization.Table(this.div.get(0));
table.draw(data, {
width: '100%',
sort: 'disable',
allowHtml: true,
cssClassNames: {
'tableCell': 'tableCell',
},
});
}
}
// Show pieChart of event count percentage of each process, thread, library and function.
class ChartView {
constructor(divContainer, eventInfo) {
this.div = $('
').appendTo(divContainer);
this.eventInfo = eventInfo;
this.processInfo = null;
this.threadInfo = null;
this.libInfo = null;
this.states = {
SHOW_EVENT_INFO: 1,
SHOW_PROCESS_INFO: 2,
SHOW_THREAD_INFO: 3,
SHOW_LIB_INFO: 4,
};
if (isClockEvent(this.eventInfo)) {
this.getSampleWeight = function (eventCount) {
return (eventCount / 1000000.0).toFixed(3) + ' ms';
};
} else {
this.getSampleWeight = (eventCount) => '' + eventCount;
}
}
_getState() {
if (this.libInfo) {
return this.states.SHOW_LIB_INFO;
}
if (this.threadInfo) {
return this.states.SHOW_THREAD_INFO;
}
if (this.processInfo) {
return this.states.SHOW_PROCESS_INFO;
}
return this.states.SHOW_EVENT_INFO;
}
_goBack() {
let state = this._getState();
if (state == this.states.SHOW_PROCESS_INFO) {
this.processInfo = null;
} else if (state == this.states.SHOW_THREAD_INFO) {
this.threadInfo = null;
} else if (state == this.states.SHOW_LIB_INFO) {
this.libInfo = null;
}
this.draw();
}
_selectHandler(chart) {
let selectedItem = chart.getSelection()[0];
if (selectedItem) {
let state = this._getState();
if (state == this.states.SHOW_EVENT_INFO) {
this.processInfo = this.eventInfo.processes[selectedItem.row];
} else if (state == this.states.SHOW_PROCESS_INFO) {
this.threadInfo = this.processInfo.threads[selectedItem.row];
} else if (state == this.states.SHOW_THREAD_INFO) {
this.libInfo = this.threadInfo.libs[selectedItem.row];
}
this.draw();
}
}
draw() {
google.charts.setOnLoadCallback(() => this.realDraw());
}
realDraw() {
this.div.empty();
this._drawTitle();
this._drawPieChart();
}
_drawTitle() {
// Draw a table of 'Name', 'Event Count'.
let rows = [];
rows.push(['Event Type: ' + this.eventInfo.eventName,
this.getSampleWeight(this.eventInfo.eventCount)]);
if (this.processInfo) {
rows.push(['Process: ' + getProcessName(this.processInfo.pid),
this.getSampleWeight(this.processInfo.eventCount)]);
}
if (this.threadInfo) {
rows.push(['Thread: ' + getThreadName(this.threadInfo.tid),
this.getSampleWeight(this.threadInfo.eventCount)]);
}
if (this.libInfo) {
rows.push(['Library: ' + getLibName(this.libInfo.libId),
this.getSampleWeight(this.libInfo.eventCount)]);
}
let data = new google.visualization.DataTable();
data.addColumn('string', '');
data.addColumn('string', '');
data.addRows(rows);
for (let i = 0; i < rows.length; ++i) {
data.setProperty(i, 0, 'className', 'boldTableCell');
}
let wrapperDiv = $('
');
wrapperDiv.appendTo(this.div);
let table = new google.visualization.Table(wrapperDiv.get(0));
table.draw(data, {
width: '100%',
sort: 'disable',
allowHtml: true,
cssClassNames: {
'tableCell': 'tableCell',
},
});
if (this._getState() != this.states.SHOW_EVENT_INFO) {
$('').appendTo(this.div)
.click(() => this._goBack());
}
}
_drawPieChart() {
let state = this._getState();
let title = null;
let firstColumn = null;
let rows = [];
let thisObj = this;
function getItem(name, eventCount, totalEventCount) {
let sampleWeight = thisObj.getSampleWeight(eventCount);
let percent = (eventCount * 100.0 / totalEventCount).toFixed(2) + '%';
return [name, eventCount, getHtml('pre', {text: name}) +
getHtml('b', {text: `${sampleWeight} (${percent})`})];
}
if (state == this.states.SHOW_EVENT_INFO) {
title = 'Processes in event type ' + this.eventInfo.eventName;
firstColumn = 'Process';
for (let process of this.eventInfo.processes) {
rows.push(getItem('Process: ' + getProcessName(process.pid), process.eventCount,
this.eventInfo.eventCount));
}
} else if (state == this.states.SHOW_PROCESS_INFO) {
title = 'Threads in process ' + getProcessName(this.processInfo.pid);
firstColumn = 'Thread';
for (let thread of this.processInfo.threads) {
rows.push(getItem('Thread: ' + getThreadName(thread.tid), thread.eventCount,
this.processInfo.eventCount));
}
} else if (state == this.states.SHOW_THREAD_INFO) {
title = 'Libraries in thread ' + getThreadName(this.threadInfo.tid);
firstColumn = 'Library';
for (let lib of this.threadInfo.libs) {
rows.push(getItem('Library: ' + getLibName(lib.libId), lib.eventCount,
this.threadInfo.eventCount));
}
} else if (state == this.states.SHOW_LIB_INFO) {
title = 'Functions in library ' + getLibName(this.libInfo.libId);
firstColumn = 'Function';
for (let func of this.libInfo.functions) {
rows.push(getItem('Function: ' + getFuncName(func.f), func.c[1],
this.libInfo.eventCount));
}
}
let data = new google.visualization.DataTable();
data.addColumn('string', firstColumn);
data.addColumn('number', 'EventCount');
data.addColumn({type: 'string', role: 'tooltip', p: {html: true}});
data.addRows(rows);
let wrapperDiv = $('
');
wrapperDiv.appendTo(this.div);
let chart = new google.visualization.PieChart(wrapperDiv.get(0));
chart.draw(data, {
title: title,
width: 1000,
height: 600,
tooltip: {isHtml: true},
});
google.visualization.events.addListener(chart, 'select', () => this._selectHandler(chart));
}
}
class ChartStatTab {
init(div) {
this.div = div;
}
draw() {
new RecordFileView(this.div).draw();
let views = createViewsForEvents(this.div, (div, eventInfo) => {
return new ChartView(div, eventInfo);
});
for (let view of views) {
view.draw();
}
}
}
class SampleTableTab {
init(div) {
this.div = div;
}
draw() {
let views = [];
createPromise()
.then(updateProgress('Draw SampleTable...', 0))
.then(wait(() => {
this.div.empty();
views = createViewsForEvents(this.div, (div, eventInfo) => {
return new SampleTableView(div, eventInfo);
});
}))
.then(() => drawViewsAsync(views, 100, (view, progress) => view.drawAsync(progress)))
.then(hideProgress());
}
}
// Select the way to show sample weight in SampleTableTab.
// 1. Show percentage of event count.
// 2. Show event count (For cpu-clock and task-clock events, it is time in ms).
class SampleTableWeightSelectorView {
constructor(divContainer, eventInfo, onSelectChange) {
let options = new Map();
options.set('percent', 'Show percentage of event count');
options.set('event_count', 'Show event count');
if (isClockEvent(eventInfo)) {
options.set('event_count_in_ms', 'Show event count in milliseconds');
}
let buttons = [];
options.forEach((value, key) => {
buttons.push(``);
});
this.curOption = 'percent';
this.eventCount = eventInfo.eventCount;
let id = createId();
let str = `
`);
for (let [i, process] of eventInfo.processes.entries()) {
let processName = getProcessName(process.pid);
for (let [j, thread] of process.threads.entries()) {
let threadName = getThreadName(thread.tid);
for (let [k, lib] of thread.libs.entries()) {
let libName = getLibName(lib.libId);
for (let [t, func] of lib.functions.entries()) {
let totalValue = getSampleWeight(func.c[2]);
let selfValue = getSampleWeight(func.c[1]);
let key = [i, j, k, t].join('_');
data.push([totalValue, selfValue, func.c[0], processName,
threadName, libName, getFuncName(func.f), key])
}
}
}
}
}))
.then(addProgress(totalProgress / 2))
.then(wait(() => {
let table = this.tableDiv.find('table');
let dataTable = table.DataTable({
lengthMenu: [10, 20, 50, 100, -1],
order: [0, 'desc'],
data: data,
responsive: true,
});
dataTable.column(7).visible(false);
table.find('tr').css('cursor', 'pointer');
table.on('click', 'tr', function() {
let data = dataTable.row(this).data();
if (!data) {
// A row in header or footer.
return;
}
let key = data[7];
if (!key) {
return;
}
let indexes = key.split('_');
let processInfo = eventInfo.processes[indexes[0]];
let threadInfo = processInfo.threads[indexes[1]];
let lib = threadInfo.libs[indexes[2]];
let func = lib.functions[indexes[3]];
FunctionTab.showFunction(eventInfo, processInfo, threadInfo, lib, func);
});
}));
}
onSampleWeightChange() {
createPromise()
.then(updateProgress('Draw SampleTable...', 0))
.then(() => this._drawSampleTable(100))
.then(hideProgress());
}
}
// Show embedded flamegraph generated by inferno.
class FlameGraphTab {
init(div) {
this.div = div;
}
draw() {
let views = [];
createPromise()
.then(updateProgress('Draw Flamegraph...', 0))
.then(wait(() => {
this.div.empty();
views = createViewsForEvents(this.div, (div, eventInfo) => {
return new FlameGraphViewList(div, eventInfo);
});
}))
.then(() => drawViewsAsync(views, 100, (view, progress) => view.drawAsync(progress)))
.then(hideProgress());
}
}
// Show FlameGraphs for samples in an event type, used in FlameGraphTab.
// 1. Draw 10 FlameGraphs at one time, and use a "More" button to show more FlameGraphs.
// 2. First draw background of Flamegraphs, then draw details in idle time.
class FlameGraphViewList {
constructor(div, eventInfo) {
this.div = div;
this.eventInfo = eventInfo;
this.selectorView = null;
this.flamegraphDiv = null;
this.flamegraphs = [];
this.moreButton = null;
}
drawAsync(totalProgress) {
this.div.empty();
this.selectorView = new SampleWeightSelectorView(this.div, this.eventInfo,
() => this.onSampleWeightChange());
this.flamegraphDiv = $('
').appendTo(this.div);
return this._drawMoreFlameGraphs(10, totalProgress);
}
// Return a promise to draw flamegraphs.
_drawMoreFlameGraphs(moreCount, progress) {
let initProgress = progress / (1 + moreCount);
let newFlamegraphs = [];
return createPromise()
.then(wait(() => {
if (this.moreButton) {
this.moreButton.hide();
}
let pId = 0;
let tId = 0;
let newCount = this.flamegraphs.length + moreCount;
for (let i = 0; i < newCount; ++i) {
if (pId == this.eventInfo.processes.length) {
break;
}
let process = this.eventInfo.processes[pId];
let thread = process.threads[tId];
if (i >= this.flamegraphs.length) {
let title = `Process ${getProcessName(process.pid)} ` +
`Thread ${getThreadName(thread.tid)} ` +
`(Samples: ${thread.sampleCount})`;
let totalCount = {countForProcess: process.eventCount,
countForThread: thread.eventCount};
let flamegraph = new FlameGraphView(this.flamegraphDiv, title, totalCount,
thread.g.c, false);
flamegraph.draw();
newFlamegraphs.push(flamegraph);
}
tId++;
if (tId == process.threads.length) {
pId++;
tId = 0;
}
}
if (pId < this.eventInfo.processes.length) {
// Show "More" Button.
if (!this.moreButton) {
this.div.append(`
`);
this.moreButton = this.div.children().last().find('button');
this.moreButton.click(() => {
createPromise().then(updateProgress('Draw FlameGraph...', 0))
.then(() => this._drawMoreFlameGraphs(10, 100))
.then(hideProgress());
});
this.moreButton.hide();
}
} else if (this.moreButton) {
this.moreButton.remove();
this.moreButton = null;
}
for (let flamegraph of newFlamegraphs) {
this.flamegraphs.push(flamegraph);
}
}))
.then(addProgress(initProgress))
.then(() => this.drawDetails(newFlamegraphs, progress - initProgress));
}
drawDetails(flamegraphs, totalProgress) {
return createPromise()
.then(() => drawViewsAsync(flamegraphs, totalProgress, (view, progress) => {
return createPromise()
.then(wait(() => view.drawDetails(this.selectorView.getSampleWeightFunction())))
.then(addProgress(progress));
}))
.then(wait(() => {
if (this.moreButton) {
this.moreButton.show();
}
}));
}
onSampleWeightChange() {
createPromise().then(updateProgress('Draw FlameGraph...', 0))
.then(() => this.drawDetails(this.flamegraphs, 100))
.then(hideProgress());
}
}
// FunctionTab: show information of a function.
// 1. Show the callgrpah and reverse callgraph of a function as flamegraphs.
// 2. Show the annotated source code of the function.
class FunctionTab {
static showFunction(eventInfo, processInfo, threadInfo, lib, func) {
let title = 'Function';
let tab = gTabs.findTab(title);
if (!tab) {
tab = gTabs.addTab(title, new FunctionTab());
}
gTabs.setActiveAsync(title)
.then(() => tab.setFunction(eventInfo, processInfo, threadInfo, lib, func));
}
constructor() {
this.func = null;
this.selectPercent = 'thread';
}
init(div) {
this.div = div;
}
setFunction(eventInfo, processInfo, threadInfo, lib, func) {
this.eventInfo = eventInfo;
this.processInfo = processInfo;
this.threadInfo = threadInfo;
this.lib = lib;
this.func = func;
this.selectorView = null;
this.views = [];
this.redraw();
}
redraw() {
if (!this.func) {
return;
}
createPromise()
.then(updateProgress("Draw Function...", 0))
.then(wait(() => {
this.div.empty();
this._drawTitle();
this.selectorView = new SampleWeightSelectorView(this.div, this.eventInfo,
() => this.onSampleWeightChange());
let funcId = this.func.f;
let funcName = getFuncName(funcId);
function getNodesMatchingFuncId(root) {
let nodes = [];
function recursiveFn(node) {
if (node.f == funcId) {
nodes.push(node);
} else {
for (let child of node.c) {
recursiveFn(child);
}
}
}
recursiveFn(root);
return nodes;
}
let totalCount = {countForProcess: this.processInfo.eventCount,
countForThread: this.threadInfo.eventCount};
let callgraphView = new FlameGraphView(
this.div, `Functions called by ${funcName}`, totalCount,
getNodesMatchingFuncId(this.threadInfo.g), false);
callgraphView.draw();
this.views.push(callgraphView);
let reverseCallgraphView = new FlameGraphView(
this.div, `Functions calling ${funcName}`, totalCount,
getNodesMatchingFuncId(this.threadInfo.rg), true);
reverseCallgraphView.draw();
this.views.push(reverseCallgraphView);
let sourceFiles = collectSourceFilesForFunction(this.func);
if (sourceFiles) {
this.div.append(getHtml('hr'));
this.div.append(getHtml('b', {text: 'SourceCode:'}) + ' ');
this.views.push(new SourceCodeView(this.div, sourceFiles, totalCount));
}
let disassembly = collectDisassemblyForFunction(this.func);
if (disassembly) {
this.div.append(getHtml('hr'));
this.div.append(getHtml('b', {text: 'Disassembly:'}) + ' ');
this.views.push(new DisassemblyView(this.div, disassembly, totalCount));
}
}))
.then(addProgress(25))
.then(() => this.drawDetails(75))
.then(hideProgress());
}
draw() {}
_drawTitle() {
let eventName = this.eventInfo.eventName;
let processName = getProcessName(this.processInfo.pid);
let threadName = getThreadName(this.threadInfo.tid);
let libName = getLibName(this.lib.libId);
let funcName = getFuncName(this.func.f);
// Draw a table of 'Name', 'Value'.
let rows = [];
rows.push(['Event Type', eventName]);
rows.push(['Process', processName]);
rows.push(['Thread', threadName]);
rows.push(['Library', libName]);
rows.push(['Function', getHtml('pre', {text: funcName})]);
let data = new google.visualization.DataTable();
data.addColumn('string', '');
data.addColumn('string', '');
data.addRows(rows);
for (let i = 0; i < rows.length; ++i) {
data.setProperty(i, 0, 'className', 'boldTableCell');
}
let wrapperDiv = $('
');
wrapperDiv.appendTo(this.div);
let table = new google.visualization.Table(wrapperDiv.get(0));
table.draw(data, {
width: '100%',
sort: 'disable',
allowHtml: true,
cssClassNames: {
'tableCell': 'tableCell',
},
});
}
onSampleWeightChange() {
createPromise()
.then(updateProgress("Draw Function...", 0))
.then(() => this.drawDetails(100))
.then(hideProgress());
}
drawDetails(totalProgress) {
let sampleWeightFunction = this.selectorView.getSampleWeightFunction();
return drawViewsAsync(this.views, totalProgress, (view, progress) => {
return createPromise()
.then(wait(() => view.drawDetails(sampleWeightFunction)))
.then(addProgress(progress));
});
}
}
// Select the way to show sample weight in FlamegraphTab and FunctionTab.
// 1. Show percentage of event count relative to all processes.
// 2. Show percentage of event count relative to the current process.
// 3. Show percentage of event count relative to the current thread.
// 4. Show absolute event count.
// 5. Show event count in milliseconds, only possible for cpu-clock or task-clock events.
class SampleWeightSelectorView {
constructor(divContainer, eventInfo, onSelectChange) {
let options = new Map();
options.set('percent_to_all', 'Show percentage of event count relative to all processes');
options.set('percent_to_process',
'Show percentage of event count relative to the current process');
options.set('percent_to_thread',
'Show percentage of event count relative to the current thread');
options.set('event_count', 'Show event count');
if (isClockEvent(eventInfo)) {
options.set('event_count_in_ms', 'Show event count in milliseconds');
}
let buttons = [];
options.forEach((value, key) => {
buttons.push(``);
});
this.curOption = 'percent_to_all';
let id = createId();
let str = `
${buttons.join('')}
`;
divContainer.append(str);
divContainer.children().last().on('hidden.bs.dropdown', (e) => {
if (e.clickEvent) {
let button = $(e.clickEvent.target);
let newOption = button.attr('key');
if (newOption && this.curOption != newOption) {
this.curOption = newOption;
divContainer.find(`#${id}`).text(options.get(this.curOption));
onSelectChange();
}
}
});
this.countForAllProcesses = eventInfo.eventCount;
}
getSampleWeightFunction() {
if (this.curOption == 'percent_to_all') {
let countForAllProcesses = this.countForAllProcesses;
return function(eventCount, _) {
let percent = eventCount * 100.0 / countForAllProcesses;
return percent.toFixed(2) + '%';
};
}
if (this.curOption == 'percent_to_process') {
return function(eventCount, totalCount) {
let percent = eventCount * 100.0 / totalCount.countForProcess;
return percent.toFixed(2) + '%';
};
}
if (this.curOption == 'percent_to_thread') {
return function(eventCount, totalCount) {
let percent = eventCount * 100.0 / totalCount.countForThread;
return percent.toFixed(2) + '%';
};
}
if (this.curOption == 'event_count') {
return function(eventCount, _) {
return '' + eventCount;
};
}
if (this.curOption == 'event_count_in_ms') {
return function(eventCount, _) {
let timeInMs = eventCount / 1000000.0;
return timeInMs.toFixed(3) + ' ms';
};
}
}
}
// Given a callgraph, show the flamegraph.
class FlameGraphView {
constructor(divContainer, title, totalCount, initNodes, reverseOrder) {
this.id = createId();
this.div = $('