diff options
author | Martijn Coenen <maco@google.com> | 2015-09-28 16:18:39 +0200 |
---|---|---|
committer | Martijn Coenen <maco@google.com> | 2016-01-18 12:41:01 -0800 |
commit | 49b1535fdf6cd9ab74047d54c9028525c04adda8 (patch) | |
tree | 5598d8d3025809abf0eeced9cd03e13cb1570f77 /systrace_analysis | |
parent | 9a9efec5634b318266dc823252ebde3c6f88986f (diff) | |
download | extras-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.html | 212 | ||||
-rwxr-xr-x | systrace_analysis/analyze_trace.py | 56 |
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() |