Skip to content

Commit 49b1535

Browse files
author
Martijn Coenen
committed
Initial import of headless systrace analysis.
Can dump the following on a per-process/activity basis: - Time the process was running / activity in the foreground - # of frames rendered, and their performance - # of direct reclaims - Amount of time spent on each CPU for key threads - CPU frequency distribution Change-Id: I449c5729a0cd4c27d1b810b669a86cd5ee7f4467
1 parent 9a9efec commit 49b1535

File tree

2 files changed

+268
-0
lines changed

2 files changed

+268
-0
lines changed

systrace_analysis/analysis.html

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
<!DOCTYPE html>
2+
<!--
3+
Copyright (C) 2015 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<link rel="import" href="/tracing/base/base.html">
18+
<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
19+
<link rel="import" href="/tracing/extras/importer/android/event_log_importer.html">
20+
<link rel="import" href="/tracing/extras/android/android_auditor.html">
21+
<link rel="import" href="/tracing/importer/import.html">
22+
<link rel="import" href="/tracing/model/model.html">
23+
<script>
24+
'use strict';
25+
26+
(function() {
27+
var FRAME_PERF_CLASS = tr.model.FRAME_PERF_CLASS;
28+
29+
if (!tr.isHeadless) {
30+
throw new Error('Can only run in headless mode.');
31+
}
32+
33+
function printFrameStats(process, range) {
34+
var goodFrames = 0;
35+
var badFrames = 0;
36+
var neutralFrames = 0;
37+
var terribleFrames = 0;
38+
39+
process.frames.forEach(function(frame) {
40+
// Check if frame belongs to any activity
41+
if (frame.start >= range.min && (frame.end <= range.max)) {
42+
if (frame.perfClass == FRAME_PERF_CLASS.NEUTRAL) neutralFrames++;
43+
if (frame.perfClass == FRAME_PERF_CLASS.GOOD) goodFrames++;
44+
if (frame.perfClass == FRAME_PERF_CLASS.BAD) badFrames++;
45+
if (frame.perfClass == FRAME_PERF_CLASS.TERRIBLE) terribleFrames++;
46+
}
47+
});
48+
49+
var totalFrames = goodFrames + badFrames + neutralFrames;
50+
if (totalFrames > 0) {
51+
console.log(" Frame stats:");
52+
console.log(" # Total frames: " + totalFrames);
53+
console.log(" # Terrible frames: " + terribleFrames);
54+
console.log(" # Bad frames: " + badFrames);
55+
console.log(" # Neutral frames: " + neutralFrames);
56+
console.log(" # Good frames: " + goodFrames);
57+
}
58+
};
59+
60+
function printMemoryStats(process, range) {
61+
var numDirectReclaim = 0;
62+
for (var tid in process.threads) {
63+
if (!process.threads[tid].timeSlices) continue;
64+
process.threads[tid].sliceGroup.slices.forEach(function(slice) {
65+
if (slice.title.startsWith('direct reclaim')) {
66+
if (slice.start >= range.min &&
67+
slice.start + slice.duration <= range.max) {
68+
numDirectReclaim++;
69+
}
70+
}
71+
});
72+
}
73+
console.log(" Memory stats:");
74+
console.log(" # of direct reclaims: " + numDirectReclaim);
75+
};
76+
77+
function printCpuStatsForThread(thread, range) {
78+
var stats = thread.getCpuStatsForRange(range);
79+
// Sum total CPU duration
80+
console.log(' ' + thread.name + ' CPU allocation: ');
81+
for (var cpu in stats) {
82+
var percentage = (stats[cpu] / stats.total * 100).toFixed(2);
83+
84+
console.log(" CPU " + cpu + ": " + percentage + "% (" +
85+
stats[cpu].toFixed(2) + " ms.)");
86+
}
87+
};
88+
89+
function printCpuStatsForProcess(process, range) {
90+
var total_runtime = 0;
91+
for (var tid in process.threads) {
92+
var stats = process.threads[tid].getCpuStatsForRange(range);
93+
total_runtime += stats.total;
94+
}
95+
console.log(" CPU stats:");
96+
console.log(" Total CPU runtime: " + total_runtime.toFixed(2) + " ms.");
97+
var uiThread = process.getThread(process.pid);
98+
var renderThreads = process.findAllThreadsNamed('RenderThread');
99+
var renderThread = renderThreads.length == 1 ? renderThreads[0] : undefined;
100+
if (uiThread)
101+
printCpuStatsForThread(uiThread, range);
102+
if (renderThread)
103+
printCpuStatsForThread(renderThread, range);
104+
printCpuFreqStats(range);
105+
};
106+
107+
function printCpuFreqStats(range) {
108+
for (var i = 0; i < 8; i++) {
109+
var cpu = model.kernel.getOrCreateCpu(i);
110+
if (cpu !== undefined) {
111+
var stats = cpu.getFreqStatsForRange(range);
112+
113+
console.log(' CPU ' + i + ' frequency distribution:');
114+
for (var freq in stats) {
115+
var percentage = (stats[freq] / range.duration * 100).toFixed(2);
116+
console.log(' ' + freq + ' ' + percentage + "% (" +
117+
stats[freq].toFixed(2) + ' ms.)');
118+
}
119+
}
120+
}
121+
};
122+
123+
function printBinderStats(process, range) {
124+
var outgoing_transactions = 0;
125+
var incoming_transactions = 0;
126+
for (var tid in process.threads) {
127+
var outgoing_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder transaction');
128+
var outgoing_async_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder transaction async');
129+
var incoming_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder reply');
130+
var incoming_async_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder Async recv');
131+
outgoing_transactions += outgoing_slices.length + outgoing_async_slices.length;
132+
incoming_transactions += incoming_slices.length + incoming_async_slices.length;
133+
}
134+
console.log(' Binder transaction stats:');
135+
console.log(' # Outgoing binder transactions: ' + outgoing_transactions);
136+
console.log(' # Incoming binder transactions: ' + incoming_transactions);
137+
};
138+
139+
function pagesInMBString(pages) {
140+
if (isNaN(pages))
141+
return '0 (0.00 MB)';
142+
return pages + ' (' + (pages * 4096 / 1024 / 1024).toFixed(2) + ' MB)';
143+
};
144+
145+
function printPageCacheStats(process) {
146+
console.log(' Page cache stats:');
147+
var totalAccess = 0;
148+
var totalMiss = 0;
149+
var totalAdd = 0;
150+
for (var file in process.pageCacheAccesses) {
151+
totalAccess += process.pageCacheAccesses[file];
152+
totalMiss += process.pageCacheMisses[file];
153+
totalAdd += process.pageCacheAdd[file];
154+
console.log(' File: ' + file);
155+
console.log(' # of pages accessed: ' + pagesInMBString(process.pageCacheAccesses[file]));
156+
console.log(' # of pages missed: ' + pagesInMBString(process.pageCacheMisses[file]));
157+
console.log(' # of pages added to cache: ' + pagesInMBString(process.pageCacheAdd[file]));
158+
}
159+
console.log(' TOTALS:');
160+
console.log(' # of pages accessed: ' + pagesInMBString(totalAccess));
161+
console.log(' # of pages missed: ' + pagesInMBString(totalMiss));
162+
console.log(' # of pages added to cache: ' + pagesInMBString(totalAdd));
163+
};
164+
165+
function printProcessStats(process, opt_range) {
166+
var range = opt_range;
167+
if (range === undefined) {
168+
// Use the process range
169+
range = process.bounds;
170+
}
171+
printCpuStatsForProcess(process, range);
172+
printPageCacheStats(process);
173+
printMemoryStats(process, range);
174+
printBinderStats(process, range);
175+
printFrameStats(process, range);
176+
};
177+
178+
if (sys.argv.length < 2)
179+
console.log('First argument needs to be a systrace file.');
180+
181+
// Import model
182+
var systrace = read(sys.argv[1]);
183+
var traces = [systrace];
184+
if (sys.argv.length >= 3) {
185+
// Add event log file if we got it
186+
var events = read(sys.argv[2]);
187+
traces.push(events);
188+
}
189+
var model = new tr.Model();
190+
var i = new tr.importer.Import(model);
191+
console.log("Starting import...");
192+
i.importTraces(traces);
193+
console.log("Done.");
194+
model.getAllProcesses().forEach(function(process) {
195+
if (process.name === undefined) return;
196+
console.log('Stats for process ' + process.name + ' (' + process.pid + ')');
197+
// Check if process has activity starts
198+
if (process.activities && process.activities.length > 0) {
199+
process.activities.forEach(function(activity) {
200+
console.log('Activity ' + activity.name + ' foreground from ' +
201+
activity.start + ' until ' + activity.end);
202+
var activityRange = tr.b.Range.fromExplicitRange(activity.start,
203+
activity.start + activity.duration);
204+
printProcessStats(process, activityRange);
205+
}, this);
206+
} else {
207+
printProcessStats(process);
208+
}
209+
console.log('');
210+
});
211+
})();
212+
</script>

systrace_analysis/analyze_trace.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright (C) 2015 The Android Open Source Project
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
import argparse
19+
import os
20+
import subprocess
21+
import sys
22+
23+
def main():
24+
# Create argument parser
25+
parser = argparse.ArgumentParser()
26+
parser.add_argument('systrace_file', metavar='SYSTRACE_FILE', help='systrace file to analyze')
27+
parser.add_argument('-e', metavar='EVENT_LOG', help='android event log file')
28+
args = parser.parse_args()
29+
30+
this_script_path = os.path.dirname(os.path.realpath(__file__))
31+
32+
# Find chromium-trace directory and vinn binary as offset from this script
33+
chromium_trace_path = os.path.normpath(this_script_path + '/../../../external/chromium-trace')
34+
if not os.path.exists(chromium_trace_path):
35+
sys.exit('Can\'t find chromium-trace in your source tree')
36+
37+
vinn_path = chromium_trace_path + '/catapult/third_party/vinn/'
38+
if not os.path.exists(vinn_path):
39+
sys.exit('Can\'t find vinn in your source tree')
40+
41+
sys.path.append(vinn_path)
42+
import vinn
43+
44+
# Find source paths and construct vinn launch arguments
45+
tracing_path = chromium_trace_path + '/catapult/tracing/'
46+
gldist_path = chromium_trace_path + '/catapult/tracing/third_party/gl-matrix/dist/'
47+
source_paths_arg = [tracing_path, gldist_path]
48+
js_args_arg = [args.systrace_file]
49+
if args.e is not None:
50+
js_args_arg += [args.e]
51+
res = vinn.RunFile(this_script_path + '/analysis.html', source_paths=source_paths_arg,
52+
js_args=js_args_arg, stdout=sys.stdout, stdin=sys.stdin);
53+
return res.returncode
54+
55+
if __name__ == '__main__':
56+
main()

0 commit comments

Comments
 (0)