/*
* 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.
$(document).ready(function() {
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 toPercentageStr(percentage) {
return percentage.toFixed(2) + '%';
}
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');
}
class TabManager {
constructor(divContainer) {
this.div = $('
', {id: 'tabs'});
this.div.appendTo(divContainer);
this.div.append(getHtml('ul'));
this.tabs = [];
this.isDrawCalled = false;
}
addTab(title, tabObj) {
let id = 'tab_' + this.div.children().length;
let tabDiv = $('
', {id: id});
tabDiv.appendTo(this.div);
this.div.children().first().append(
getHtml('li', {text: getHtml('a', {href: '#' + id, text: title})}));
tabObj.init(tabDiv);
this.tabs.push(tabObj);
if (this.isDrawCalled) {
this.div.tabs('refresh');
}
return tabObj;
}
findTab(title) {
let links = this.div.find('li a');
for (let i = 0; i < links.length; ++i) {
if (links.eq(i).text() == title) {
return this.tabs[i];
}
}
return null;
}
draw() {
this.div.tabs({
active: 0,
});
this.tabs.forEach(function(tab) {
tab.draw();
});
this.isDrawCalled = true;
}
setActive(tabObj) {
for (let i = 0; i < this.tabs.length; ++i) {
if (this.tabs[i] == tabObj) {
this.div.tabs('option', 'active', i);
break;
}
}
}
}
// 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.id = divContainer.children().length;
this.div = $('
', {id: 'chartstat_' + this.id});
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) {
let button = $('