summaryrefslogtreecommitdiff
path: root/systrace_analysis
diff options
context:
space:
mode:
authorMartijn Coenen <maco@google.com>2015-09-28 16:18:39 +0200
committerMartijn Coenen <maco@google.com>2016-01-18 12:41:01 -0800
commit49b1535fdf6cd9ab74047d54c9028525c04adda8 (patch)
tree5598d8d3025809abf0eeced9cd03e13cb1570f77 /systrace_analysis
parent9a9efec5634b318266dc823252ebde3c6f88986f (diff)
downloadextras-49b1535fdf6cd9ab74047d54c9028525c04adda8.tar.gz
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
Diffstat (limited to 'systrace_analysis')
-rw-r--r--systrace_analysis/analysis.html212
-rwxr-xr-xsystrace_analysis/analyze_trace.py56
2 files changed, 268 insertions, 0 deletions
diff --git a/systrace_analysis/analysis.html b/systrace_analysis/analysis.html
new file mode 100644
index 00000000..df8e6e69
--- /dev/null
+++ b/systrace_analysis/analysis.html
@@ -0,0 +1,212 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2015 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.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+<link rel="import" href="/tracing/extras/importer/android/event_log_importer.html">
+<link rel="import" href="/tracing/extras/android/android_auditor.html">
+<link rel="import" href="/tracing/importer/import.html">
+<link rel="import" href="/tracing/model/model.html">
+<script>
+'use strict';
+
+(function() {
+ var FRAME_PERF_CLASS = tr.model.FRAME_PERF_CLASS;
+
+ if (!tr.isHeadless) {
+ throw new Error('Can only run in headless mode.');
+ }
+
+ function printFrameStats(process, range) {
+ var goodFrames = 0;
+ var badFrames = 0;
+ var neutralFrames = 0;
+ var terribleFrames = 0;
+
+ process.frames.forEach(function(frame) {
+ // Check if frame belongs to any activity
+ if (frame.start >= range.min && (frame.end <= range.max)) {
+ if (frame.perfClass == FRAME_PERF_CLASS.NEUTRAL) neutralFrames++;
+ if (frame.perfClass == FRAME_PERF_CLASS.GOOD) goodFrames++;
+ if (frame.perfClass == FRAME_PERF_CLASS.BAD) badFrames++;
+ if (frame.perfClass == FRAME_PERF_CLASS.TERRIBLE) terribleFrames++;
+ }
+ });
+
+ var totalFrames = goodFrames + badFrames + neutralFrames;
+ if (totalFrames > 0) {
+ console.log(" Frame stats:");
+ console.log(" # Total frames: " + totalFrames);
+ console.log(" # Terrible frames: " + terribleFrames);
+ console.log(" # Bad frames: " + badFrames);
+ console.log(" # Neutral frames: " + neutralFrames);
+ console.log(" # Good frames: " + goodFrames);
+ }
+ };
+
+ function printMemoryStats(process, range) {
+ var numDirectReclaim = 0;
+ for (var tid in process.threads) {
+ if (!process.threads[tid].timeSlices) continue;
+ process.threads[tid].sliceGroup.slices.forEach(function(slice) {
+ if (slice.title.startsWith('direct reclaim')) {
+ if (slice.start >= range.min &&
+ slice.start + slice.duration <= range.max) {
+ numDirectReclaim++;
+ }
+ }
+ });
+ }
+ console.log(" Memory stats:");
+ console.log(" # of direct reclaims: " + numDirectReclaim);
+ };
+
+ function printCpuStatsForThread(thread, range) {
+ var stats = thread.getCpuStatsForRange(range);
+ // Sum total CPU duration
+ console.log(' ' + thread.name + ' CPU allocation: ');
+ for (var cpu in stats) {
+ var percentage = (stats[cpu] / stats.total * 100).toFixed(2);
+
+ console.log(" CPU " + cpu + ": " + percentage + "% (" +
+ stats[cpu].toFixed(2) + " ms.)");
+ }
+ };
+
+ function printCpuStatsForProcess(process, range) {
+ var total_runtime = 0;
+ for (var tid in process.threads) {
+ var stats = process.threads[tid].getCpuStatsForRange(range);
+ total_runtime += stats.total;
+ }
+ console.log(" CPU stats:");
+ console.log(" Total CPU runtime: " + total_runtime.toFixed(2) + " ms.");
+ var uiThread = process.getThread(process.pid);
+ var renderThreads = process.findAllThreadsNamed('RenderThread');
+ var renderThread = renderThreads.length == 1 ? renderThreads[0] : undefined;
+ if (uiThread)
+ printCpuStatsForThread(uiThread, range);
+ if (renderThread)
+ printCpuStatsForThread(renderThread, range);
+ printCpuFreqStats(range);
+ };
+
+ function printCpuFreqStats(range) {
+ for (var i = 0; i < 8; i++) {
+ var cpu = model.kernel.getOrCreateCpu(i);
+ if (cpu !== undefined) {
+ var stats = cpu.getFreqStatsForRange(range);
+
+ console.log(' CPU ' + i + ' frequency distribution:');
+ for (var freq in stats) {
+ var percentage = (stats[freq] / range.duration * 100).toFixed(2);
+ console.log(' ' + freq + ' ' + percentage + "% (" +
+ stats[freq].toFixed(2) + ' ms.)');
+ }
+ }
+ }
+ };
+
+ function printBinderStats(process, range) {
+ var outgoing_transactions = 0;
+ var incoming_transactions = 0;
+ for (var tid in process.threads) {
+ var outgoing_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder transaction');
+ var outgoing_async_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder transaction async');
+ var incoming_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder reply');
+ var incoming_async_slices = process.threads[tid].sliceGroup.getSlicesOfName('binder Async recv');
+ outgoing_transactions += outgoing_slices.length + outgoing_async_slices.length;
+ incoming_transactions += incoming_slices.length + incoming_async_slices.length;
+ }
+ console.log(' Binder transaction stats:');
+ console.log(' # Outgoing binder transactions: ' + outgoing_transactions);
+ console.log(' # Incoming binder transactions: ' + incoming_transactions);
+ };
+
+ function pagesInMBString(pages) {
+ if (isNaN(pages))
+ return '0 (0.00 MB)';
+ return pages + ' (' + (pages * 4096 / 1024 / 1024).toFixed(2) + ' MB)';
+ };
+
+ function printPageCacheStats(process) {
+ console.log(' Page cache stats:');
+ var totalAccess = 0;
+ var totalMiss = 0;
+ var totalAdd = 0;
+ for (var file in process.pageCacheAccesses) {
+ totalAccess += process.pageCacheAccesses[file];
+ totalMiss += process.pageCacheMisses[file];
+ totalAdd += process.pageCacheAdd[file];
+ console.log(' File: ' + file);
+ console.log(' # of pages accessed: ' + pagesInMBString(process.pageCacheAccesses[file]));
+ console.log(' # of pages missed: ' + pagesInMBString(process.pageCacheMisses[file]));
+ console.log(' # of pages added to cache: ' + pagesInMBString(process.pageCacheAdd[file]));
+ }
+ console.log(' TOTALS:');
+ console.log(' # of pages accessed: ' + pagesInMBString(totalAccess));
+ console.log(' # of pages missed: ' + pagesInMBString(totalMiss));
+ console.log(' # of pages added to cache: ' + pagesInMBString(totalAdd));
+ };
+
+ function printProcessStats(process, opt_range) {
+ var range = opt_range;
+ if (range === undefined) {
+ // Use the process range
+ range = process.bounds;
+ }
+ printCpuStatsForProcess(process, range);
+ printPageCacheStats(process);
+ printMemoryStats(process, range);
+ printBinderStats(process, range);
+ printFrameStats(process, range);
+ };
+
+ if (sys.argv.length < 2)
+ console.log('First argument needs to be a systrace file.');
+
+ // Import model
+ var systrace = read(sys.argv[1]);
+ var traces = [systrace];
+ if (sys.argv.length >= 3) {
+ // Add event log file if we got it
+ var events = read(sys.argv[2]);
+ traces.push(events);
+ }
+ var model = new tr.Model();
+ var i = new tr.importer.Import(model);
+ console.log("Starting import...");
+ i.importTraces(traces);
+ console.log("Done.");
+ model.getAllProcesses().forEach(function(process) {
+ if (process.name === undefined) return;
+ console.log('Stats for process ' + process.name + ' (' + process.pid + ')');
+ // Check if process has activity starts
+ if (process.activities && process.activities.length > 0) {
+ process.activities.forEach(function(activity) {
+ console.log('Activity ' + activity.name + ' foreground from ' +
+ activity.start + ' until ' + activity.end);
+ var activityRange = tr.b.Range.fromExplicitRange(activity.start,
+ activity.start + activity.duration);
+ printProcessStats(process, activityRange);
+ }, this);
+ } else {
+ printProcessStats(process);
+ }
+ console.log('');
+ });
+})();
+</script>
diff --git a/systrace_analysis/analyze_trace.py b/systrace_analysis/analyze_trace.py
new file mode 100755
index 00000000..ddc99274
--- /dev/null
+++ b/systrace_analysis/analyze_trace.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2015 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.
+#
+
+import argparse
+import os
+import subprocess
+import sys
+
+def main():
+ # Create argument parser
+ parser = argparse.ArgumentParser()
+ parser.add_argument('systrace_file', metavar='SYSTRACE_FILE', help='systrace file to analyze')
+ parser.add_argument('-e', metavar='EVENT_LOG', help='android event log file')
+ args = parser.parse_args()
+
+ this_script_path = os.path.dirname(os.path.realpath(__file__))
+
+ # Find chromium-trace directory and vinn binary as offset from this script
+ chromium_trace_path = os.path.normpath(this_script_path + '/../../../external/chromium-trace')
+ if not os.path.exists(chromium_trace_path):
+ sys.exit('Can\'t find chromium-trace in your source tree')
+
+ vinn_path = chromium_trace_path + '/catapult/third_party/vinn/'
+ if not os.path.exists(vinn_path):
+ sys.exit('Can\'t find vinn in your source tree')
+
+ sys.path.append(vinn_path)
+ import vinn
+
+ # Find source paths and construct vinn launch arguments
+ tracing_path = chromium_trace_path + '/catapult/tracing/'
+ gldist_path = chromium_trace_path + '/catapult/tracing/third_party/gl-matrix/dist/'
+ source_paths_arg = [tracing_path, gldist_path]
+ js_args_arg = [args.systrace_file]
+ if args.e is not None:
+ js_args_arg += [args.e]
+ res = vinn.RunFile(this_script_path + '/analysis.html', source_paths=source_paths_arg,
+ js_args=js_args_arg, stdout=sys.stdout, stdin=sys.stdin);
+ return res.returncode
+
+if __name__ == '__main__':
+ main()