Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(881)

Unified Diff: tools/convert_perf_script_to_tracing_json.py

Issue 226933002: Convert 'perf script' output to about:tracing json. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tools/convert_perf_script_to_tracing_json_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/convert_perf_script_to_tracing_json.py
diff --git a/tools/convert_perf_script_to_tracing_json.py b/tools/convert_perf_script_to_tracing_json.py
new file mode 100755
index 0000000000000000000000000000000000000000..3bb468008d830e816f025baa8b3f524dc1d1cd21
--- /dev/null
+++ b/tools/convert_perf_script_to_tracing_json.py
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import re
+import sys
+
+"""Converts the output of "perf script" into json for about:tracing.
+
+Usage: perf script | convert_perf_script_to_tracing_json.py > trace.json
+"""
+
+# FIXME: Signal to the traceviewer that this is a CPU profile so it shows
+# samples instead of ms as the units.
eseidel 2014/04/05 00:03:13 The samples themselves can sorta represent 0.1ms o
ojan 2014/04/05 00:13:50 The perf script output doesn't say what the sampli
vmiura 2014/04/08 19:33:50 In my experience the perf sampling interval is qui
+
+def strip_to_last_paren(line):
eseidel 2014/04/05 00:03:13 I'm surprised you didn't just use a regexp to matc
+ last_paren_index = line.rfind('(')
+ if last_paren_index == -1:
+ return line
+ return line[:last_paren_index]
+
+def extract_function_name(line):
+ # This information from the stack doesn't seem terribly useful.
+ line = line.replace('(anonymous namespace)::', '')
+ line = line.replace('non-virtual thunk to ', '')
+
+ # Strip executable name.
+ line = strip_to_last_paren(line)
+ # Strip function arguments.
eseidel 2014/04/05 00:03:13 Yeah, I think a regexp would be more readable, may
ojan 2014/04/05 00:13:50 I had a lot of trouble coming up with a regexp tha
+ line = strip_to_last_paren(line)
+
+ line = line.strip()
+ line = re.sub('\s+', ' ', line)
+
+ first_space_index = line.find(' ')
+ if first_space_index == -1:
+ # Unsymbolized addresses.
+ return line
+ return line[first_space_index + 1:]
+
+def collapse_perf_script_output(lines):
+ collapsed_lines = {}
+ stack_so_far = []
+ thread_id = ''
+
+ for line in lines:
+ line = line.strip()
+
+ if not line:
+ if stack_so_far:
+ stack = ';'.join(stack_so_far)
+ collapsed_lines[thread_id][stack] = (
+ collapsed_lines[thread_id].setdefault(stack, 0) + 1)
+ stack_so_far = []
+ continue
+
+ if line[0] == '#':
+ continue
+
+ match_header = re.match('\w+\ (\w+)\ cycles:\s*$', line)
+ if match_header:
+ thread_id = match_header.group(1)
+ if not thread_id in collapsed_lines:
+ collapsed_lines[thread_id] = {}
+ continue
+
+ stack_so_far.insert(0, extract_function_name(line))
+
+ return collapsed_lines
+
+def add_sample(root, thread_id, current_stack, start_time, end_time,
+ original_stack):
+ node_so_far = root
+ for function in current_stack:
+ # Can get the same stack on different threads, so identify samples by
+ # combination of function name and thread_id.
+ key = function + ';' + thread_id
+ if key in node_so_far:
+ node_so_far[key]['end_time'] = end_time
+ else:
+ node_so_far[key] = {
+ 'children': {},
+ 'start_time': start_time,
+ 'end_time': end_time,
+ }
+ node_so_far = node_so_far[key]['children']
+
+def compute_tree(collapsed_lines):
+ total_samples_per_thread = {}
+ total_samples_for_all_threads = 0
+ tree = {}
+
+ for thread_id in collapsed_lines:
+ for stack in sorted(collapsed_lines[thread_id].iterkeys()):
+ samples = collapsed_lines[thread_id][stack]
+ total_samples_for_thread = total_samples_per_thread.setdefault(
+ thread_id, 0)
+ add_sample(tree, thread_id, stack.split(';'),
+ total_samples_for_thread, total_samples_for_thread + samples,
+ stack)
+ total_samples_per_thread[thread_id] += samples
+ total_samples_for_all_threads += samples
+
+ return tree, total_samples_for_all_threads, total_samples_per_thread
+
+def json_for_subtree(node, trace_data, total_samples_for_all_threads,
+ total_samples_per_thread):
+ for key in sorted(node.iterkeys()):
+ function, thread_id = key.split(';')
+ start_time = int(node[key]['start_time'])
+ end_time = int(node[key]['end_time'])
+ duration = end_time - start_time
+ process_percent = '%2.2f%%' % (
+ 100 * float(duration) / total_samples_for_all_threads)
+ thread_percent = '%2.2f%%' % (
+ 100 * float(duration) / total_samples_per_thread[thread_id])
+
+ # FIXME: extract out process IDs.
+ children = node[key]['children']
+
+ # If there are no children, we can use a Complete event instead two
+ # Duration events.
+ if not children:
+ trace_data.append({
eseidel 2014/04/05 00:05:09 Crazy. When I started down this path I just used
ojan 2014/04/05 00:13:50 What are immediate events?
dsinclair 2014/04/08 02:28:36 Immediate events are drawn as a line, instead of a
ojan 2014/04/08 19:38:40 The heading fragment doesn't seem to work. Searchi
dsinclair 2014/04/08 19:42:44 Sorry, I should use the right words, we call them
+ "pid": 1,
+ "tid": thread_id,
+ "name": function,
+ "ts": start_time,
+ "dur": duration,
+ "ph": "X",
+ "args": {
+ "process percent": process_percent,
+ "thread percent": thread_percent
+ },
+ })
+ continue
+
+ trace_data.append({
dsinclair 2014/04/08 02:28:36 I think, although I maybe mistaken, you could use
ojan 2014/04/08 19:38:40 I'm not sure what you're suggesting...how would th
dsinclair 2014/04/08 19:42:44 We create the nested slices based on timestamps no
+ "pid": 1,
+ "tid": thread_id,
+ "name": function,
+ "ts": start_time,
+ "ph": "B",
+ })
+
+ json_for_subtree(children, trace_data, total_samples_for_all_threads,
+ total_samples_per_thread)
+
+ trace_data.append({
+ "pid": 1,
+ "tid": thread_id,
+ "name": function,
+ "ts": end_time,
+ "ph": "E",
+ "args": {
+ "process percent": process_percent,
+ "thread percent": thread_percent
+ },
+ })
+
+def stringified_json_output(lines):
+ tree, total_samples_for_all_threads, total_samples_per_thread = (
+ compute_tree(collapse_perf_script_output(lines)))
+ trace_data = []
+ json_for_subtree(tree, trace_data, total_samples_for_all_threads,
+ total_samples_per_thread)
+ return json.dumps(trace_data, separators=(',',':'))
+
+if __name__ == "__main__":
+ print stringified_json_output(sys.stdin.readlines())
« no previous file with comments | « no previous file | tools/convert_perf_script_to_tracing_json_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698