Fix traces generated from an async invocation of atrace

Cherry-pick of 977b6ae5eacf2bef7d54b0291728959a6e581dd6 from AOSP

Such traces can become inconsistent because the trace buffers
are kept per CPU. To work around this the prefix of the trace
where not all CPUs have data yet are discarded.

Bug: 21037114
Bug: 19437218
Change-Id: I8e0c5d5eab89a81691a3dfe485b62f76c7c1535c
diff --git a/systrace.py b/systrace.py
index 4d89fce..516c0af 100755
--- a/systrace.py
+++ b/systrace.py
@@ -93,6 +93,8 @@
                     'list of app cmdlines')
   parser.add_option('--no-fix-threads', dest='fix_threads', default=True,
                     action='store_false', help='don\'t fix missing or truncated thread names')
+  parser.add_option('--no-fix-circular', dest='fix_circular', default=True,
+                    action='store_false', help='don\'t fix truncated circular traces')
 
   parser.add_option('--link-assets', dest='link_assets', default=False,
                     action='store_true', help='link to original CSS or JS resources '
@@ -256,6 +258,9 @@
             return m.group(0)
         out = re.sub(r'^\s*(\S+)-(\d+)', repl, out, flags=re.MULTILINE)
 
+      if options.fix_circular:
+        out = fix_circular_traces(out)
+
       html_prefix = read_asset(script_dir, 'prefix.html')
       html_suffix = read_asset(script_dir, 'suffix.html')
       trace_viewer_html = read_asset(script_dir, 'systrace_trace_viewer.html')
@@ -280,6 +285,36 @@
 def read_asset(src_dir, filename):
   return open(os.path.join(src_dir, filename)).read()
 
+def fix_circular_traces(out):
+  """Fix inconsistentcies in traces due to circular buffering.
+
+  The circular buffers are kept per CPU, so it is not guaranteed that the
+  beginning of a slice is overwritten before the end. To work around this, we
+  throw away the prefix of the trace where not all CPUs have events yet."""
+
+  # If any of the CPU's buffers have filled up and
+  # older events have been dropped, the kernel
+  # emits markers of the form '##### CPU 2 buffer started ####' on
+  # the line before the first event in the trace on that CPU.
+  #
+  # No such headers are emitted if there were no overflows or the trace
+  # was captured with non-circular buffers.
+  buffer_start_re = re.compile(r'^#+ CPU \d+ buffer started', re.MULTILINE)
+
+  start_of_full_trace = 0
+
+  while True:
+    result = buffer_start_re.search(out, start_of_full_trace + 1)
+    if result:
+      start_of_full_trace = result.start()
+    else:
+      break
+
+  if start_of_full_trace > 0:
+    # Need to keep the header intact to make the importer happy.
+    end_of_header = re.search(r'^[^#]', out, re.MULTILINE).start()
+    out = out[:end_of_header] + out[start_of_full_trace:]
+  return out
 
 if __name__ == '__main__':
   main()