Merge "simpleperf: fix parsing perf.data generated by --trace-offcpu option."
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index 3a8f0b5..e261900 100644
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -426,8 +426,10 @@
parser.add_argument('--arch', help=
"""Select which arch the app is running on, possible values are:
arm, arm64, x86, x86_64. If not set, the script will try to detect it.""")
- parser.add_argument('-r', '--record_options', default="-e cpu-cycles:u -g -f 1000 --duration 10", help=
-"""Set options for `simpleperf record` command. Default is '-e cpu-cycles:u -g -f 1000 --duration 10'.""")
+ parser.add_argument('-r', '--record_options',
+ default='-e task-clock:u -g -f 1000 --duration 10', help="""
+ Set options for `simpleperf record` command.
+ Default is "-e task-clock:u -g -f 1000 --duration 10".""")
parser.add_argument('-o', '--perf_data_path', default="perf.data", help=
"""The path to store profiling data.""")
parser.add_argument('-nb', '--skip_collect_binaries', action='store_true', help=
diff --git a/simpleperf/scripts/report_html.js b/simpleperf/scripts/report_html.js
index ac79568..5f6b96b 100644
--- a/simpleperf/scripts/report_html.js
+++ b/simpleperf/scripts/report_html.js
@@ -101,6 +101,11 @@
return gSourceFiles[sourceFileId].code;
}
+function isClockEvent(eventInfo) {
+ return eventInfo.eventName.includes('task-clock') ||
+ eventInfo.eventName.includes('cpu-clock');
+}
+
class TabManager {
constructor(divContainer) {
this.div = $('<div>', {id: 'tabs'});
@@ -167,19 +172,43 @@
}
draw() {
+ google.charts.setOnLoadCallback(() => this.realDraw());
+ }
+
+ realDraw() {
+ this.div.empty();
+ // Draw a table of 'Name', 'Value'.
+ let rows = [];
if (gRecordInfo.recordTime) {
- this.div.append(getHtml('p', {text: 'Record Time: ' + gRecordInfo.recordTime}));
+ rows.push(['Record Time', gRecordInfo.recordTime]);
}
if (gRecordInfo.machineType) {
- this.div.append(getHtml('p', {text: 'Machine Type: ' + gRecordInfo.machineType}));
+ rows.push(['Machine Type', gRecordInfo.machineType]);
}
if (gRecordInfo.androidVersion) {
- this.div.append(getHtml('p', {text: 'Android Version: ' + gRecordInfo.androidVersion}));
+ rows.push(['Android Version', gRecordInfo.androidVersion]);
}
if (gRecordInfo.recordCmdline) {
- this.div.append(getHtml('p', {text: 'Record Cmdline: ' + gRecordInfo.recordCmdline}));
+ rows.push(['Record cmdline', gRecordInfo.recordCmdline]);
}
- this.div.append(getHtml('p', {text: 'Total Samples: ' + gRecordInfo.totalSamples}));
+ 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',
+ },
+ });
}
}
@@ -199,6 +228,13 @@
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() {
@@ -214,29 +250,6 @@
return this.states.SHOW_EVENT_INFO;
}
- _drawTitle() {
- if (this.eventInfo) {
- this.div.append(getHtml('p', {text: `Event Type: ${this.eventInfo.eventName}`}));
- }
- if (this.processInfo) {
- this.div.append(getHtml('p',
- {text: `Process: ${getProcessName(this.processInfo.pid)}`}));
- }
- if (this.threadInfo) {
- this.div.append(getHtml('p',
- {text: `Thread: ${getThreadName(this.threadInfo.tid)}`}));
- }
- if (this.libInfo) {
- this.div.append(getHtml('p',
- {text: `Library: ${getLibName(this.libInfo.libId)}`}));
- }
- if (this.processInfo) {
- let button = $('<button>', {text: 'Back'});
- button.appendTo(this.div);
- button.button().click(() => this._goBack());
- }
- }
-
_goBack() {
let state = this._getState();
if (state == this.states.SHOW_PROCESS_INFO) {
@@ -271,56 +284,108 @@
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();
- let title;
+ 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 = $('<div>');
+ 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 = $('<button>', {text: 'Back'});
+ button.appendTo(this.div);
+ button.button().click(() => this._goBack());
+ }
+ }
+
+ _drawPieChart() {
let state = this._getState();
- if (state == this.states.SHOW_EVENT_INFO) {
- title = 'Processes in event type ' + this.eventInfo.eventName;
- data.addColumn('string', 'Process');
- data.addColumn('number', 'EventCount');
- let rows = [];
- for (let process of this.eventInfo.processes) {
- rows.push([getProcessName(process.pid), process.eventCount]);
- }
- data.addRows(rows);
- } else if (state == this.states.SHOW_PROCESS_INFO) {
- title = 'Threads in process ' + getProcessName(this.processInfo.pid);
- data.addColumn('string', 'Thread');
- data.addColumn('number', 'EventCount');
- let rows = [];
- for (let thread of this.processInfo.threads) {
- rows.push([getThreadName(thread.tid), thread.eventCount]);
- }
- data.addRows(rows);
- } else if (state == this.states.SHOW_THREAD_INFO) {
- title = 'Libraries in thread ' + getThreadName(this.threadInfo.tid);
- data.addColumn('string', 'Lib');
- data.addColumn('number', 'EventCount');
- let rows = [];
- for (let lib of this.threadInfo.libs) {
- rows.push([getLibName(lib.libId), lib.eventCount]);
- }
- data.addRows(rows);
- } else if (state == this.states.SHOW_LIB_INFO) {
- title = 'Functions in library ' + getLibName(this.libInfo.libId);
- data.addColumn('string', 'Function');
- data.addColumn('number', 'EventCount');
- let rows = [];
- for (let func of this.libInfo.functions) {
- rows.push([getFuncName(func.g.f), func.g.e]);
- }
- data.addRows(rows);
+ 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})`})];
}
- let options = {
- title: title,
- width: 1000,
- height: 600,
- };
+ 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.g.f), func.g.e,
+ 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 = $('<div>');
wrapperDiv.appendTo(this.div);
let chart = new google.visualization.PieChart(wrapperDiv.get(0));
- chart.draw(data, options);
+ chart.draw(data, {
+ title: title,
+ width: 1000,
+ height: 600,
+ tooltip: {isHtml: true},
+ });
google.visualization.events.addListener(chart, 'select', () => this._selectHandler(chart));
}
}
@@ -354,64 +419,155 @@
init(div) {
this.div = div;
+ this.selectorView = null;
+ this.sampleTableViews = [];
}
draw() {
- for (let tId = 0; tId < gSampleInfo.length; tId++) {
- let eventInfo = gSampleInfo[tId];
- let eventName = eventInfo.eventName;
- this.div.append(getHtml('p', {text: 'Sample table for event ' + eventName}));
- let percentMul = 100.0 / eventInfo.eventCount;
- let tableId = 'reportTable_' + tId;
- let titles = ['Total', 'Self', 'SampleCount', 'Process', 'Thread', 'Lib', 'Function'];
- let tableStr = openHtml('table', {id: tableId, cellspacing: '0', width: '100%'}) +
- getHtml('thead', {text: getTableRow(titles, 'th')}) +
- getHtml('tfoot', {text: getTableRow(titles, 'th')}) +
- openHtml('tbody');
+ this.selectorView = new SampleTableWeightSelectorView(this.div, gSampleInfo[0],
+ () => this.onSampleWeightChange());
+ this.selectorView.draw();
+ for (let eventInfo of gSampleInfo) {
+ this.div.append(getHtml('hr'));
+ this.sampleTableViews.push(new SampleTableView(this.div, eventInfo));
+ }
+ this.onSampleWeightChange();
+ }
- for (let i = 0; i < eventInfo.processes.length; ++i) {
- let processInfo = eventInfo.processes[i];
- let processName = getProcessName(processInfo.pid);
- for (let j = 0; j < processInfo.threads.length; ++j) {
- let threadInfo = processInfo.threads[j];
- let threadName = getThreadName(threadInfo.tid);
- for (let k = 0; k < threadInfo.libs.length; ++k) {
- let lib = threadInfo.libs[k];
- for (let t = 0; t < lib.functions.length; ++t) {
- let func = lib.functions[t];
- let key = [i, j, k, t].join('_');
- let treePercentage = toPercentageStr(func.g.s * percentMul);
- let selfPercenetage = toPercentageStr(func.g.e * percentMul);
- tableStr += getTableRow([treePercentage, selfPercenetage, func.c,
- processName, threadName, getLibName(lib.libId),
- getFuncName(func.g.f)], 'td', {key: key});
- }
+ onSampleWeightChange() {
+ for (let i = 0; i < gSampleInfo.length; ++i) {
+ let sampleWeightFunction = this.selectorView.getSampleWeightFunction(gSampleInfo[i]);
+ let sampleWeightSuffix = this.selectorView.getSampleWeightSuffix(gSampleInfo[i]);
+ this.sampleTableViews[i].draw(sampleWeightFunction, sampleWeightSuffix);
+ }
+ }
+}
+
+// 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, firstEventInfo, onSelectChange) {
+ this.div = $('<div>');
+ this.div.appendTo(divContainer);
+ this.onSelectChange = onSelectChange;
+ this.options = {
+ SHOW_PERCENT: 0,
+ SHOW_EVENT_COUNT: 1,
+ };
+ if (isClockEvent(firstEventInfo)) {
+ this.curOption = this.options.SHOW_EVENT_COUNT;
+ } else {
+ this.curOption = this.options.SHOW_PERCENT;
+ }
+ }
+
+ draw() {
+ let options = ['Show percentage of event count', 'Show event count'];
+ let optionStr = '';
+ for (let i = 0; i < options.length; ++i) {
+ optionStr += getHtml('option', {value: i, text: options[i]});
+ }
+ this.div.append(getHtml('select', {text: optionStr}));
+ let selectMenu = this.div.children().last();
+ selectMenu.children().eq(this.curOption).attr('selected', 'selected');
+ let thisObj = this;
+ selectMenu.selectmenu({
+ change: function() {
+ thisObj.curOption = this.value;
+ thisObj.onSelectChange();
+ },
+ width: '100%',
+ });
+ }
+
+ getSampleWeightFunction(eventInfo) {
+ if (this.curOption == this.options.SHOW_PERCENT) {
+ return function(eventCount) {
+ return (eventCount * 100.0 / eventInfo.eventCount).toFixed(2) + '%';
+ }
+ }
+ if (isClockEvent(eventInfo)) {
+ return (eventCount) => (eventCount / 1000000.0).toFixed(3);
+ }
+ return (eventCount) => '' + eventCount;
+ }
+
+ getSampleWeightSuffix(eventInfo) {
+ if (this.curOption == this.options.SHOW_EVENT_COUNT && isClockEvent(eventInfo)) {
+ return ' ms';
+ }
+ return '';
+ }
+}
+
+
+class SampleTableView {
+ constructor(divContainer, eventInfo) {
+ this.id = divContainer.children().length;
+ this.div = $('<div>');
+ this.div.appendTo(divContainer);
+ this.eventInfo = eventInfo;
+ }
+
+ draw(getSampleWeight, sampleWeightSuffix) {
+ // Draw a table of 'Total', 'Self', 'Samples', 'Process', 'Thread', 'Library', 'Function'.
+ this.div.empty();
+ let eventInfo = this.eventInfo;
+ let sampleWeight = getSampleWeight(eventInfo.eventCount);
+ this.div.append(getHtml('p', {text: `Sample table for event ${eventInfo.eventName}, ` +
+ `total count ${sampleWeight}${sampleWeightSuffix}`}));
+ let tableId = 'sampleTable_' + this.id;
+ let valueSuffix = sampleWeightSuffix.length > 0 ? `(in${sampleWeightSuffix})` : '';
+ let titles = ['Total' + valueSuffix, 'Self' + valueSuffix, 'Samples',
+ 'Process', 'Thread', 'Library', 'Function'];
+ let tableStr = openHtml('table', {id: tableId, cellspacing: '0', width: '100%'}) +
+ getHtml('thead', {text: getTableRow(titles, 'th')}) +
+ getHtml('tfoot', {text: getTableRow(titles, 'th')}) +
+ openHtml('tbody');
+ for (let i = 0; i < eventInfo.processes.length; ++i) {
+ let processInfo = eventInfo.processes[i];
+ let processName = getProcessName(processInfo.pid);
+ for (let j = 0; j < processInfo.threads.length; ++j) {
+ let threadInfo = processInfo.threads[j];
+ let threadName = getThreadName(threadInfo.tid);
+ for (let k = 0; k < threadInfo.libs.length; ++k) {
+ let lib = threadInfo.libs[k];
+ let libName = getLibName(lib.libId);
+ for (let t = 0; t < lib.functions.length; ++t) {
+ let func = lib.functions[t];
+ let key = [i, j, k, t].join('_');
+ let totalValue = getSampleWeight(func.g.s);
+ let selfValue = getSampleWeight(func.g.e);
+ tableStr += getTableRow([totalValue, selfValue, func.c,
+ processName, threadName, libName,
+ getFuncName(func.g.f)], 'td', {key: key});
}
}
}
- tableStr += closeHtml('tbody') + closeHtml('table');
- this.div.append(tableStr);
- let table = this.div.find(`table#${tableId}`).dataTable({
- lengthMenu: [10, 20, 50, 100, -1],
- processing: true,
- order: [0, 'desc'],
- responsive: true,
- });
-
- table.find('tr').css('cursor', 'pointer');
- table.on('click', 'tr', function() {
- let key = this.getAttribute('key');
- 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);
- });
}
+ tableStr += closeHtml('tbody') + closeHtml('table');
+ this.div.append(tableStr);
+ let table = this.div.find(`table#${tableId}`).dataTable({
+ lengthMenu: [10, 20, 50, 100, -1],
+ processing: true,
+ order: [0, 'desc'],
+ responsive: true,
+ });
+
+ table.find('tr').css('cursor', 'pointer');
+ table.on('click', 'tr', function() {
+ let key = this.getAttribute('key');
+ 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);
+ });
}
}
@@ -474,23 +630,14 @@
return;
}
this.div.empty();
- 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.g.f);
- let title = getHtml('p', {text: `Event ${eventName}`}) +
- getHtml('p', {text: `Process ${processName}`}) +
- getHtml('p', {text: `Thread ${threadName}`}) +
- getHtml('p', {text: `Library ${libName}`}) +
- getHtml('p', {text: `Function ${funcName}`});
- this.div.append(title);
+ this._drawTitle();
this.selectorView = new FunctionSampleWeightSelectorView(this.div, this.eventInfo,
this.processInfo, this.threadInfo, () => this.onSampleWeightChange());
this.selectorView.draw();
this.div.append(getHtml('hr'));
+ let funcName = getFuncName(this.func.g.f);
this.div.append(getHtml('b', {text: `Functions called by ${funcName}`}) + '<br/>');
this.callgraphView = new FlameGraphView(this.div, this.func.g, false);
@@ -515,6 +662,39 @@
this.onSampleWeightChange(); // Manually set sample weight function for the first time.
}
+ _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.g.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 = $('<div>');
+ 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() {
let sampleWeightFunction = this.selectorView.getSampleWeightFunction();
if (this.callgraphView) {
@@ -555,7 +735,7 @@
EVENT_COUNT_IN_TIME: 4,
};
let name = eventInfo.eventName;
- this.supportEventCountInTime = name.includes('task-clock') || name.includes('cpu-clock');
+ this.supportEventCountInTime = isClockEvent(eventInfo);
if (this.supportEventCountInTime) {
this.curOption = this.options.EVENT_COUNT_IN_TIME;
} else {
@@ -1011,7 +1191,7 @@
data.addColumn('string', 'Self');
data.addColumn('string', 'Code');
data.addRows(rows);
- for (let i = 0; i < lineNumbers.length; ++i) {
+ for (let i = 0; i < rows.length; ++i) {
data.setProperty(i, 0, 'className', 'colForLine');
for (let j = 1; j <= 2; ++j) {
data.setProperty(i, j, 'className', 'colForCount');
@@ -1107,7 +1287,7 @@
data.addColumn('string', 'Self');
data.addColumn('string', 'Code');
data.addRows(rows);
- for (let i = 0; i < this.disassembly.length; ++i) {
+ for (let i = 0; i < rows.length; ++i) {
for (let j = 0; j < 2; ++j) {
data.setProperty(i, j, 'className', 'colForCount');
}
diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py
index e30d07a..52c7459 100644
--- a/simpleperf/scripts/report_html.py
+++ b/simpleperf/scripts/report_html.py
@@ -554,6 +554,9 @@
def load_record_file(self, record_file):
lib = ReportLib()
lib.SetRecordFile(record_file)
+ # If not showing ip for unknown symbols, the percent of the unknown symbol may be
+ # accumulated to very big, and ranks first in the sample table.
+ lib.ShowIpForUnknownSymbol()
if self.binary_cache_path:
lib.SetSymfs(self.binary_cache_path)
self.meta_info = lib.MetaInfo()
@@ -808,6 +811,8 @@
self.hw.open_tag('style', type='text/css').add("""
.colForLine { width: 50px; }
.colForCount { width: 100px; }
+ .tableCell { font-size: 17px; }
+ .boldTableCell { font-weight: bold; font-size: 17px; }
""").close_tag()
self.hw.close_tag('head')
self.hw.open_tag('body')
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
index 938d626..9924e99 100644
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -323,7 +323,7 @@
def common_test_report_html(self):
self.run_cmd(['report_html.py', '-h'])
- self.run_app_profiler()
+ self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u')
self.run_cmd(['report_html.py'])
self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata'])
self.run_cmd(['report_html.py', '--add_disassembly'])