Sync to latest from aosp master

bug:22636165

Change-Id: Iccf99b34047273f277eae53ef9614e3e562ece4d
diff --git a/UPSTREAM_REVISION b/UPSTREAM_REVISION
index 30ce84f..fcec727 100644
--- a/UPSTREAM_REVISION
+++ b/UPSTREAM_REVISION
@@ -1 +1 @@
-61c9bc212e36be84712198163bea009e04fd994a
+98bb819e78ccfd8db0e9c08c4bf91d1fab162ab8
diff --git a/agents/__init__.py b/agents/__init__.py
new file mode 100644
index 0000000..8cc6cfd
--- /dev/null
+++ b/agents/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2015 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.
\ No newline at end of file
diff --git a/agents/atrace_agent.py b/agents/atrace_agent.py
new file mode 100644
index 0000000..5ac96d9
--- /dev/null
+++ b/agents/atrace_agent.py
@@ -0,0 +1,553 @@
+# Copyright (c) 2015 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 Queue
+import re
+import subprocess
+import sys
+import threading
+import time
+import zlib
+
+import systrace_agent
+import util
+
+# Text that ADB sends, but does not need to be displayed to the user.
+ADB_IGNORE_REGEXP = r'^capturing trace\.\.\. done|^capturing trace\.\.\.'
+# The number of seconds to wait on output from ADB.
+ADB_STDOUT_READ_TIMEOUT = 0.2
+# The adb shell command to initiate a trace.
+ATRACE_BASE_ARGS = ['atrace']
+# If a custom list of categories is not specified, traces will include
+# these categories (if available on the device).
+DEFAULT_CATEGORIES = 'sched gfx view dalvik webview input disk am wm'.split()
+# The command to list trace categories.
+LIST_CATEGORIES_ARGS = ATRACE_BASE_ARGS + ['--list_categories']
+# Minimum number of seconds between displaying status updates.
+MIN_TIME_BETWEEN_STATUS_UPDATES = 0.2
+# ADB sends this text to indicate the beginning of the trace data.
+TRACE_START_REGEXP = r'TRACE\:'
+# Plain-text trace data should always start with this string.
+TRACE_TEXT_HEADER = '# tracer'
+
+# This list is based on the tags in frameworks/native/include/utils/Trace.h for
+# legacy platform.
+LEGACY_TRACE_TAG_BITS = (
+  ('gfx',       1<<1),
+  ('input',     1<<2),
+  ('view',      1<<3),
+  ('webview',   1<<4),
+  ('wm',        1<<5),
+  ('am',        1<<6),
+  ('sm',        1<<7),
+  ('audio',     1<<8),
+  ('video',     1<<9),
+  ('camera',    1<<10),
+)
+
+
+def try_create_agent(options, categories):
+  if options.from_file is not None:
+    return AtraceAgent(options, categories)
+
+  device_sdk_version = util.get_device_sdk_version()
+  if device_sdk_version >= 18:
+    return AtraceAgent(options, categories)
+  elif device_sdk_version >= 16:
+    return AtraceLegacyAgent(options, categories)
+
+
+class AtraceAgent(systrace_agent.SystraceAgent):
+  def __init__(self, options, categories):
+    super(AtraceAgent, self).__init__(options, categories)
+    self._expect_trace = False
+    self._adb = None
+    self._trace_data = None
+
+  def start(self):
+    tracer_args = self._construct_trace_command()
+    try:
+      self._adb = subprocess.Popen(tracer_args, stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE)
+    except OSError as error:
+      print >> sys.stderr, (
+          'The command "%s" failed with the following error:' %
+          ' '.join(tracer_args))
+      print >> sys.stderr, '    ', error
+      sys.exit(1)
+
+  def collect_result(self):
+    trace_data = self._collect_trace_data();
+    if self._expect_trace:
+      self._trace_data = self._preprocess_trace_data(trace_data);
+
+  def expect_trace(self):
+    return self._expect_trace
+
+  def get_trace_data(self):
+    return self._trace_data
+
+  def get_class_name(self):
+    return 'trace-data'
+
+  def _construct_list_categories_command(self):
+    return util.construct_adb_shell_command(
+          LIST_CATEGORIES_ARGS, self._options.device_serial)
+
+  def _construct_extra_trace_command(self):
+    extra_args = []
+    if self._options.app_name is not None:
+      extra_args.extend(['-a', self._options.app_name])
+
+    if self._options.kfuncs is not None:
+      extra_args.extend(['-k', self._options.kfuncs])
+
+    if not self._categories:
+      self._categories = get_default_categories(self._options.device_serial)
+    extra_args.extend(self._categories)
+    return extra_args
+
+  def _construct_trace_command(self):
+    """Builds a command-line used to invoke a trace process.
+
+    Returns:
+      A tuple where the first element is an array of command-line arguments, and
+      the second element is a boolean which will be true if the commend will
+      stream trace data.
+    """
+    if self._options.list_categories:
+      tracer_args = self._construct_list_categories_command()
+      self._expect_trace = False
+    elif self._options.from_file is not None:
+      tracer_args = ['cat', self._options.from_file]
+      self._expect_trace = True
+    else:
+      atrace_args = ATRACE_BASE_ARGS[:]
+      self._expect_trace = True
+      if self._options.compress_trace_data:
+        atrace_args.extend(['-z'])
+
+      if ((self._options.trace_time is not None)
+          and (self._options.trace_time > 0)):
+        atrace_args.extend(['-t', str(self._options.trace_time)])
+
+      if ((self._options.trace_buf_size is not None)
+          and (self._options.trace_buf_size > 0)):
+        atrace_args.extend(['-b', str(self._options.trace_buf_size)])
+      extra_args = self._construct_extra_trace_command()
+      atrace_args.extend(extra_args)
+
+      if self._options.fix_threads:
+        atrace_args.extend([';', 'ps', '-t'])
+      tracer_args = util.construct_adb_shell_command(
+          atrace_args, self._options.device_serial)
+
+    return tracer_args
+
+  def _collect_trace_data(self):
+    # Read the output from ADB in a worker thread.  This allows us to monitor
+    # the progress of ADB and bail if ADB becomes unresponsive for any reason.
+
+    # Limit the stdout_queue to 128 entries because we will initially be reading
+    # one byte at a time.  When the queue fills up, the reader thread will
+    # block until there is room in the queue.  Once we start downloading the
+    # trace data, we will switch to reading data in larger chunks, and 128
+    # entries should be plenty for that purpose.
+    stdout_queue = Queue.Queue(maxsize=128)
+    stderr_queue = Queue.Queue()
+
+    if self._expect_trace:
+      # Use stdout.write() (here and for the rest of this function) instead
+      # of print() to avoid extra newlines.
+      sys.stdout.write('Capturing trace...')
+
+    # Use a chunk_size of 1 for stdout so we can display the output to
+    # the user without waiting for a full line to be sent.
+    stdout_thread = FileReaderThread(self._adb.stdout, stdout_queue,
+                                     text_file=False, chunk_size=1)
+    stderr_thread = FileReaderThread(self._adb.stderr, stderr_queue,
+                                     text_file=True)
+    stdout_thread.start()
+    stderr_thread.start()
+
+    # Holds the trace data returned by ADB.
+    trace_data = []
+    # Keep track of the current line so we can find the TRACE_START_REGEXP.
+    current_line = ''
+    # Set to True once we've received the TRACE_START_REGEXP.
+    reading_trace_data = False
+
+    last_status_update_time = time.time()
+
+    while (stdout_thread.isAlive() or stderr_thread.isAlive() or
+           not stdout_queue.empty() or not stderr_queue.empty()):
+      if self._expect_trace:
+        last_status_update_time = status_update(last_status_update_time)
+
+      while not stderr_queue.empty():
+        # Pass along errors from adb.
+        line = stderr_queue.get()
+        sys.stderr.write(line)
+
+      # Read stdout from adb.  The loop exits if we don't get any data for
+      # ADB_STDOUT_READ_TIMEOUT seconds.
+      while True:
+        try:
+          chunk = stdout_queue.get(True, ADB_STDOUT_READ_TIMEOUT)
+        except Queue.Empty:
+          # Didn't get any data, so exit the loop to check that ADB is still
+          # alive and print anything sent to stderr.
+          break
+
+        if reading_trace_data:
+          # Save, but don't print, the trace data.
+          trace_data.append(chunk)
+        else:
+          if not self._expect_trace:
+            sys.stdout.write(chunk)
+          else:
+            # Buffer the output from ADB so we can remove some strings that
+            # don't need to be shown to the user.
+            current_line += chunk
+            if re.match(TRACE_START_REGEXP, current_line):
+              # We are done capturing the trace.
+              sys.stdout.write('Done.\n')
+              # Now we start downloading the trace data.
+              sys.stdout.write('Downloading trace...')
+              current_line = ''
+              # Use a larger chunk size for efficiency since we no longer
+              # need to worry about parsing the stream.
+              stdout_thread.set_chunk_size(4096)
+              reading_trace_data = True
+            elif chunk == '\n' or chunk == '\r':
+              # Remove ADB output that we don't care about.
+              current_line = re.sub(ADB_IGNORE_REGEXP, '', current_line)
+              if len(current_line) > 1:
+                # ADB printed something that we didn't understand, so show it
+                # it to the user (might be helpful for debugging).
+                sys.stdout.write(current_line)
+              # Reset our current line.
+              current_line = ''
+
+    if self._expect_trace:
+      if reading_trace_data:
+        # Indicate to the user that the data download is complete.
+        sys.stdout.write('Done.\n')
+      else:
+        # We didn't receive the trace start tag, so something went wrong.
+        sys.stdout.write('ERROR.\n')
+        # Show any buffered ADB output to the user.
+        current_line = re.sub(ADB_IGNORE_REGEXP, '', current_line)
+        if current_line:
+          sys.stdout.write(current_line)
+          sys.stdout.write('\n')
+
+    # The threads should already have stopped, so this is just for cleanup.
+    stdout_thread.join()
+    stderr_thread.join()
+
+    self._adb.stdout.close()
+    self._adb.stderr.close()
+
+    # The adb process should be done since it's io pipes are closed.  Call
+    # poll() to set the returncode.
+    self._adb.poll()
+
+    if self._adb.returncode != 0:
+      print >> sys.stderr, ('The command "%s" returned error code %d.' %
+                            (' '.join(tracer_args), self._adb.returncode))
+      sys.exit(1)
+
+    return trace_data
+
+  def _preprocess_trace_data(self, trace_data):
+    """Performs various processing on atrace data.
+
+    Args:
+      trace_data: The raw trace data.
+    Returns:
+      The processed trace data.
+    """
+    trace_data = ''.join(trace_data)
+
+    if self._options.fix_threads:
+      # Extract the thread list dumped by ps.
+      trace_data, thread_names = extract_thread_list(trace_data)
+
+    if trace_data:
+      trace_data = strip_and_decompress_trace(trace_data)
+
+    if not trace_data:
+      print >> sys.stderr, ('No data was captured.  Output file was not '
+                            'written.')
+      sys.exit(1)
+
+    if self._options.fix_threads:
+      trace_data = fix_thread_names(trace_data, thread_names)
+
+    if self._options.fix_circular:
+      trace_data = fix_circular_traces(trace_data)
+
+    return trace_data
+
+
+class AtraceLegacyAgent(AtraceAgent):
+  def _construct_list_categories_command(self):
+    LEGACY_CATEGORIES = """       sched - CPU Scheduling
+        freq - CPU Frequency
+        idle - CPU Idle
+        load - CPU Load
+        disk - Disk I/O (requires root)
+         bus - Bus utilization (requires root)
+   workqueue - Kernel workqueues (requires root)"""
+    return ["echo", LEGACY_CATEGORIES]
+
+  def start(self):
+    super(AtraceLegacyAgent, self).start()
+    if self.expect_trace():
+      SHELL_ARGS = ['getprop', 'debug.atrace.tags.enableflags']
+      output, return_code = util.run_adb_shell(SHELL_ARGS, self._options.device_serial)
+      flags = 0
+      if return_code == 0:
+        try:
+          if output.startswith('0x'):
+            flags = int(output, 16)
+          elif output.startswith('0'):
+            flags = int(output, 8)
+          else:
+            flags = int(output)
+        except ValueError, e:
+          pass
+
+      if flags:
+        tags = []
+        for desc, bit in LEGACY_TRACE_TAG_BITS:
+          if bit & flags:
+            tags.append(desc)
+        categories = tags + self._categories
+        print 'Collecting data with following categories:', ' '.join(categories)
+
+  def _construct_extra_trace_command(self):
+    extra_args = []
+    if not self._categories:
+      self._categories = ['sched', ]
+    if 'sched' in self._categories:
+      extra_args.append('-s')
+    if 'freq' in self._categories:
+      extra_args.append('-f')
+    if 'idle' in self._categories:
+      extra_args.append('-i')
+    if 'load' in self._categories:
+      extra_args.append('-l')
+    if 'disk' in self._categories:
+      extra_args.append('-d')
+    if 'bus' in self._categories:
+      extra_args.append('-u')
+    if 'workqueue' in self._categories:
+      extra_args.append('-w')
+
+    return extra_args
+
+
+class FileReaderThread(threading.Thread):
+  """Reads data from a file/pipe on a worker thread.
+
+  Use the standard threading. Thread object API to start and interact with the
+  thread (start(), join(), etc.).
+  """
+
+  def __init__(self, file_object, output_queue, text_file, chunk_size=-1):
+    """Initializes a FileReaderThread.
+
+    Args:
+      file_object: The file or pipe to read from.
+      output_queue: A Queue.Queue object that will receive the data
+      text_file: If True, the file will be read one line at a time, and
+          chunk_size will be ignored.  If False, line breaks are ignored and
+          chunk_size must be set to a positive integer.
+      chunk_size: When processing a non-text file (text_file = False),
+          chunk_size is the amount of data to copy into the queue with each
+          read operation.  For text files, this parameter is ignored.
+    """
+    threading.Thread.__init__(self)
+    self._file_object = file_object
+    self._output_queue = output_queue
+    self._text_file = text_file
+    self._chunk_size = chunk_size
+    assert text_file or chunk_size > 0
+
+  def run(self):
+    """Overrides Thread's run() function.
+
+    Returns when an EOF is encountered.
+    """
+    if self._text_file:
+      # Read a text file one line at a time.
+      for line in self._file_object:
+        self._output_queue.put(line)
+    else:
+      # Read binary or text data until we get to EOF.
+      while True:
+        chunk = self._file_object.read(self._chunk_size)
+        if not chunk:
+          break
+        self._output_queue.put(chunk)
+
+  def set_chunk_size(self, chunk_size):
+    """Change the read chunk size.
+
+    This function can only be called if the FileReaderThread object was
+    created with an initial chunk_size > 0.
+    Args:
+      chunk_size: the new chunk size for this file.  Must be > 0.
+    """
+    # The chunk size can be changed asynchronously while a file is being read
+    # in a worker thread.  However, type of file can not be changed after the
+    # the FileReaderThread has been created.  These asserts verify that we are
+    # only changing the chunk size, and not the type of file.
+    assert not self._text_file
+    assert chunk_size > 0
+    self._chunk_size = chunk_size
+
+
+def get_default_categories(device_serial):
+  categories_output, return_code = util.run_adb_shell(LIST_CATEGORIES_ARGS,
+                                                    device_serial)
+
+  if return_code == 0 and categories_output:
+    categories = [c.split('-')[0].strip()
+                  for c in categories_output.splitlines()]
+    return [c for c in categories if c in DEFAULT_CATEGORIES]
+
+  return []
+
+
+def status_update(last_update_time):
+  current_time = time.time()
+  if (current_time - last_update_time) >= MIN_TIME_BETWEEN_STATUS_UPDATES:
+    # Gathering a trace may take a while.  Keep printing something so users
+    # don't think the script has hung.
+    sys.stdout.write('.')
+    sys.stdout.flush()
+    return current_time
+
+  return last_update_time
+
+
+def extract_thread_list(trace_data):
+  """Removes the thread list from the given trace data.
+
+  Args:
+    trace_data: The raw trace data (before decompression).
+  Returns:
+    A tuple containing the trace data and a map of thread ids to thread names.
+  """
+  threads = {}
+  parts = re.split('USER +PID +PPID +VSIZE +RSS +WCHAN +PC +NAME',
+                   trace_data, 1)
+  if len(parts) == 2:
+    trace_data = parts[0]
+    for line in parts[1].splitlines():
+      cols = line.split(None, 8)
+      if len(cols) == 9:
+        tid = int(cols[1])
+        name = cols[8]
+        threads[tid] = name
+
+  return (trace_data, threads)
+
+
+def strip_and_decompress_trace(trace_data):
+  """Fixes new-lines and decompresses trace data.
+
+  Args:
+    trace_data: The trace data returned by atrace.
+  Returns:
+    The decompressed trace data.
+  """
+  # Collapse CRLFs that are added by adb shell.
+  if trace_data.startswith('\r\n'):
+    trace_data = trace_data.replace('\r\n', '\n')
+  elif trace_data.startswith('\r\r\n'):
+    # On windows, adb adds an extra '\r' character for each line.
+    trace_data = trace_data.replace('\r\r\n', '\n')
+
+  # Skip the initial newline.
+  trace_data = trace_data[1:]
+
+  if not trace_data.startswith(TRACE_TEXT_HEADER):
+    # No header found, so assume the data is compressed.
+    trace_data = zlib.decompress(trace_data)
+
+  # Enforce Unix line-endings.
+  trace_data = trace_data.replace('\r', '')
+
+  # Skip any initial newlines.
+  while trace_data and trace_data[0] == '\n':
+    trace_data = trace_data[1:]
+
+  return trace_data
+
+
+def fix_thread_names(trace_data, thread_names):
+  """Replaces thread ids with their names.
+
+  Args:
+    trace_data: The atrace data.
+    thread_names: A mapping of thread ids to thread names.
+  Returns:
+    The updated trace data.
+  """
+  def repl(m):
+    tid = int(m.group(2))
+    if tid > 0:
+      name = thread_names.get(tid)
+      if name is None:
+        name = m.group(1)
+        if name == '<...>':
+          name = '<' + str(tid) + '>'
+        thread_names[tid] = name
+      return name + '-' + m.group(2)
+    else:
+      return m.group(0)
+  trace_data = re.sub(r'^\s*(\S+)-(\d+)', repl, trace_data,
+                      flags=re.MULTILINE)
+  return trace_data
+
+
+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.
+
+  Args:
+    out: The data to fix.
+  Returns:
+    The updated trace data.
+  """
+  # 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
diff --git a/run_unittest.py b/run_unittest.py
new file mode 100755
index 0000000..4782073
--- /dev/null
+++ b/run_unittest.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2015 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 contextlib
+import unittest
+
+import agents.atrace_agent as atrace_agent
+import systrace
+import util
+
+DEVICE_SERIAL = 'AG8404EC0444AGC'
+LIST_TMP_ARGS = ['ls', '/data/local/tmp']
+ATRACE_ARGS = ['atrace', '-z', '-t', '10']
+CATEGORIES = ['sched', 'gfx', 'view', 'wm']
+ADB_SHELL = ['adb', '-s', DEVICE_SERIAL, 'shell']
+
+SYSTRACE_CMD = ['./systrace.py', '--time', '10', '-o', 'out.html', '-e',
+                DEVICE_SERIAL] + CATEGORIES
+TRACE_CMD = (ADB_SHELL + ATRACE_ARGS + CATEGORIES +
+             [';', 'ps', '-t'])
+
+SYSTRACE_LIST_CATEGORIES_CMD = ['./systrace.py', '-e', DEVICE_SERIAL, '-l']
+TRACE_LIST_CATEGORIES_CMD = (ADB_SHELL + ['atrace', '--list_categories'])
+
+LEGACY_ATRACE_ARGS = ['atrace', '-z', '-t', '10', '-s']
+LEGACY_TRACE_CMD = (ADB_SHELL + LEGACY_ATRACE_ARGS +
+             [';', 'ps', '-t'])
+
+TEST_DIR = 'test_data/'
+ATRACE_DATA = TEST_DIR + 'atrace_data'
+ATRACE_DATA_RAW = TEST_DIR + 'atrace_data_raw'
+ATRACE_DATA_STRIPPED = TEST_DIR + 'atrace_data_stripped'
+ATRACE_DATA_THREAD_FIXED = TEST_DIR + 'atrace_data_thread_fixed'
+ATRACE_DATA_WITH_THREAD_LIST = TEST_DIR + 'atrace_data_with_thread_list'
+ATRACE_THREAD_NAMES = TEST_DIR + 'atrace_thread_names'
+
+
+class UtilUnitTest(unittest.TestCase):
+  def test_construct_adb_shell_command(self):
+    command = util.construct_adb_shell_command(LIST_TMP_ARGS, None)
+    self.assertEqual(' '.join(command), 'adb shell ls /data/local/tmp')
+
+    command = util.construct_adb_shell_command(LIST_TMP_ARGS, DEVICE_SERIAL)
+    self.assertEqual(' '.join(command),
+                     'adb -s AG8404EC0444AGC shell ls /data/local/tmp')
+
+    command = util.construct_adb_shell_command(ATRACE_ARGS, DEVICE_SERIAL)
+    self.assertEqual(' '.join(command),
+                     'adb -s AG8404EC0444AGC shell atrace -z -t 10')
+
+
+class AtraceAgentUnitTest(unittest.TestCase):
+  def test_construct_trace_command(self):
+    options, categories = systrace.parse_options(SYSTRACE_CMD)
+    agent = atrace_agent.AtraceAgent(options, categories)
+    tracer_args = agent._construct_trace_command()
+    self.assertEqual(' '.join(TRACE_CMD), ' '.join(tracer_args))
+    self.assertEqual(True, agent.expect_trace())
+
+  def test_extract_thread_list(self):
+    with contextlib.nested(open(ATRACE_DATA_WITH_THREAD_LIST, 'r'),
+                           open(ATRACE_DATA_RAW, 'r'),
+                           open(ATRACE_THREAD_NAMES, 'r')) as (f1, f2, f3):
+      atrace_data_with_thread_list = f1.read()
+      atrace_data_raw = f2.read()
+      atrace_thread_names = f3.read()
+
+      trace_data, thread_names = atrace_agent.extract_thread_list(
+          atrace_data_with_thread_list)
+      self.assertEqual(atrace_data_raw, trace_data)
+      self.assertEqual(atrace_thread_names, str(thread_names))
+
+  def test_strip_and_decompress_trace(self):
+    with contextlib.nested(open(ATRACE_DATA_RAW, 'r'),
+                           open(ATRACE_DATA_STRIPPED, 'r')) as (f1, f2):
+      atrace_data_raw = f1.read()
+      atrace_data_stripped = f2.read()
+
+      trace_data = atrace_agent.strip_and_decompress_trace(atrace_data_raw)
+      self.assertEqual(atrace_data_stripped, trace_data)
+
+  def test_fix_thread_names(self):
+    with contextlib.nested(
+        open(ATRACE_DATA_STRIPPED, 'r'),
+        open(ATRACE_THREAD_NAMES, 'r'),
+        open(ATRACE_DATA_THREAD_FIXED, 'r')) as (f1, f2, f3):
+      atrace_data_stripped = f1.read()
+      atrace_thread_names = f2.read()
+      atrace_data_thread_fixed = f3.read()
+      thread_names = eval(atrace_thread_names)
+
+      trace_data = atrace_agent.fix_thread_names(
+          atrace_data_stripped, thread_names)
+      self.assertEqual(atrace_data_thread_fixed, trace_data)
+
+  def test_preprocess_trace_data(self):
+    with contextlib.nested(open(ATRACE_DATA_WITH_THREAD_LIST, 'r'),
+                           open(ATRACE_DATA, 'r')) as (f1, f2):
+      atrace_data_with_thread_list = f1.read()
+      atrace_data = f2.read()
+
+      options, categories = systrace.parse_options([])
+      agent = atrace_agent.AtraceAgent(options, categories)
+      trace_data = agent._preprocess_trace_data(atrace_data_with_thread_list)
+      self.assertEqual(atrace_data, trace_data)
+
+  def test_list_categories(self):
+    options, categories = systrace.parse_options(SYSTRACE_LIST_CATEGORIES_CMD)
+    agent = atrace_agent.AtraceAgent(options, categories)
+    tracer_args = agent._construct_trace_command()
+    self.assertEqual(' '.join(TRACE_LIST_CATEGORIES_CMD), ' '.join(tracer_args))
+    self.assertEqual(False, agent.expect_trace())
+
+class AtraceLegacyAgentUnitTest(unittest.TestCase):
+  def test_construct_trace_command(self):
+    options, categories = systrace.parse_options(SYSTRACE_CMD)
+    agent = atrace_agent.AtraceLegacyAgent(options, categories)
+    tracer_args = agent._construct_trace_command()
+    self.assertEqual(' '.join(LEGACY_TRACE_CMD), ' '.join(tracer_args))
+    self.assertEqual(True, agent.expect_trace())
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/systrace-legacy.py b/systrace-legacy.py
index 2ee89d4..4e29572 100755
--- a/systrace-legacy.py
+++ b/systrace-legacy.py
@@ -169,6 +169,8 @@
             html_file.write(
               html_prefix.replace("{{SYSTRACE_TRACE_VIEWER_HTML}}",
                   trace_viewer_html))
+            html_file.write('<!-- BEGIN TRACE -->\n' +
+              '  <script class="trace-data" type="application/text">\n')
             trace_started = True
             break
           elif 'TRACE:'.startswith(line) and i == len(lines) - 1:
@@ -190,6 +192,7 @@
     html_out = dec.flush().replace('\n', '\\n\\\n').replace('\r', '')
     if len(html_out) > 0:
       html_file.write(html_out)
+    html_file.write('  </script>\n<!-- END TRACE -->\n')
     html_suffix = read_asset(script_dir, 'suffix.html')
     html_file.write(html_suffix)
     html_file.close()
diff --git a/systrace.py b/systrace.py
index 516c0af..38443fe 100755
--- a/systrace.py
+++ b/systrace.py
@@ -10,73 +10,35 @@
 the kernel.  It creates an HTML file for visualizing the trace.
 """
 
-import errno, optparse, os, re, select, subprocess, sys, time, zlib
+import sys
 
-default_categories = 'sched gfx view dalvik webview input disk am wm'.split()
+# Make sure we're using a new enough version of Python.
+# The flags= parameter of re.sub() is new in Python 2.7.
+if sys.version_info[:2] < (2, 7):
+  print >> sys.stderr, '\nThis script requires Python 2.7 or newer.'
+  sys.exit(1)
 
-class OptionParserIgnoreErrors(optparse.OptionParser):
-  def error(self, msg):
-    pass
+# pylint: disable=g-bad-import-order,g-import-not-at-top
+import imp
+import optparse
+import os
 
-  def exit(self):
-    pass
+import util
 
-  def print_usage(self):
-    pass
 
-  def print_help(self):
-    pass
+# The default agent directory.
+DEFAULT_AGENT_DIR = 'agents'
 
-  def print_version(self):
-    pass
 
-def get_device_sdk_version():
-  getprop_args = ['adb', 'shell', 'getprop', 'ro.build.version.sdk']
+def parse_options(argv):
+  """Parses and checks the command-line options.
 
-  parser = OptionParserIgnoreErrors()
-  parser.add_option('-e', '--serial', dest='device_serial', type='string')
-  options, args = parser.parse_args()
-  if options.device_serial is not None:
-    getprop_args[1:1] = ['-s', options.device_serial]
-
-  try:
-    adb = subprocess.Popen(getprop_args, stdout=subprocess.PIPE,
-                           stderr=subprocess.PIPE)
-  except OSError:
-    print 'Missing adb?'
-    sys.exit(1)
-  out, err = adb.communicate()
-  if adb.returncode != 0:
-    print >> sys.stderr, 'Error querying device SDK-version:'
-    print >> sys.stderr, err
-    sys.exit(1)
-
-  version = int(out)
-  return version
-
-def add_adb_serial(command, serial):
-  if serial is not None:
-    command.insert(1, serial)
-    command.insert(1, '-s')
-
-def get_default_categories():
-  list_command = ['adb', 'shell', 'atrace', '--list_categories']
-  try:
-    categories_output = subprocess.check_output(list_command)
-    categories = [c.split('-')[0].strip() for c in categories_output.splitlines()]
-
-    return [c for c in categories if c in default_categories]
-  except:
-    return []
-
-def main():
-  device_sdk_version = get_device_sdk_version()
-  if device_sdk_version < 18:
-    legacy_script = os.path.join(os.path.dirname(sys.argv[0]), 'systrace-legacy.py')
-    os.execv(legacy_script, sys.argv)
-
-  usage = "Usage: %prog [options] [category1 [category2 ...]]"
-  desc = "Example: %prog -b 32768 -t 15 gfx input view sched freq"
+  Returns:
+    A tuple containing the options structure and a list of categories to
+    be traced.
+  """
+  usage = 'Usage: %prog [options] [category1 [category2 ...]]'
+  desc = 'Example: %prog -b 32768 -t 15 gfx input view sched freq'
   parser = optparse.OptionParser(usage=usage, description=desc)
   parser.add_option('-o', dest='output_file', help='write HTML to FILE',
                     default='trace.html', metavar='FILE')
@@ -85,236 +47,153 @@
   parser.add_option('-b', '--buf-size', dest='trace_buf_size', type='int',
                     help='use a trace buffer size of N KB', metavar='N')
   parser.add_option('-k', '--ktrace', dest='kfuncs', action='store',
-                    help='specify a comma-separated list of kernel functions to trace')
-  parser.add_option('-l', '--list-categories', dest='list_categories', default=False,
-                    action='store_true', help='list the available categories and exit')
+                    help='specify a comma-separated list of kernel functions '
+                    'to trace')
+  parser.add_option('-l', '--list-categories', dest='list_categories',
+                    default=False, action='store_true',
+                    help='list the available categories and exit')
   parser.add_option('-a', '--app', dest='app_name', default=None, type='string',
-                    action='store', help='enable application-level tracing for comma-separated ' +
+                    action='store',
+                    help='enable application-level tracing for comma-separated '
                     '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')
+                    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')
-
+                    action='store_false',
+                    help='don\'t fix truncated circular traces')
+  parser.add_option('--no-compress', dest='compress_trace_data',
+                    default=True, action='store_false',
+                    help='Tell the device not to send the trace data in '
+                    'compressed form.')
   parser.add_option('--link-assets', dest='link_assets', default=False,
-                    action='store_true', help='link to original CSS or JS resources '
-                    'instead of embedding them')
+                    action='store_true',
+                    help='(deprecated)')
   parser.add_option('--from-file', dest='from_file', action='store',
-                    help='read the trace from a file (compressed) rather than running a live trace')
+                    help='read the trace from a file (compressed) rather than '
+                    'running a live trace')
   parser.add_option('--asset-dir', dest='asset_dir', default='trace-viewer',
-                    type='string', help='')
+                    type='string', help='(deprecated)')
   parser.add_option('-e', '--serial', dest='device_serial', type='string',
                     help='adb device serial number')
+  parser.add_option('--agent-dirs', dest='agent_dirs', type='string',
+                    help='the directories of additional systrace agent modules.'
+                    ' The directories should be comma separated, e.g., '
+                    '--agent-dirs=dir1,dir2,dir3. Directory |%s| is the default'
+                    ' agent directory and will always be checked.'
+                    % DEFAULT_AGENT_DIR)
 
-  options, categories = parser.parse_args()
+  options, categories = parser.parse_args(argv[1:])
 
   if options.link_assets or options.asset_dir != 'trace-viewer':
-    parser.error('--link-assets and --asset-dir is deprecated.')
+    parser.error('--link-assets and --asset-dir are deprecated.')
 
-  if options.list_categories:
-    tracer_args = ['adb', 'shell', 'atrace --list_categories']
-    expect_trace = False
-  elif options.from_file is not None:
-    tracer_args = ['cat', options.from_file]
-    expect_trace = True
-  else:
-    atrace_args = ['atrace', '-z']
-    expect_trace = True
+  if (options.trace_time is not None) and (options.trace_time <= 0):
+    parser.error('the trace time must be a positive number')
 
-    if options.trace_time is not None:
-      if options.trace_time > 0:
-        atrace_args.extend(['-t', str(options.trace_time)])
-      else:
-        parser.error('the trace time must be a positive number')
+  if (options.trace_buf_size is not None) and (options.trace_buf_size <= 0):
+    parser.error('the trace buffer size must be a positive number')
 
-    if options.trace_buf_size is not None:
-      if options.trace_buf_size > 0:
-        atrace_args.extend(['-b', str(options.trace_buf_size)])
-      else:
-        parser.error('the trace buffer size must be a positive number')
+  return (options, categories)
 
-    if options.app_name is not None:
-      atrace_args.extend(['-a', options.app_name])
 
-    if options.kfuncs is not None:
-      atrace_args.extend(['-k', options.kfuncs])
+def write_trace_html(html_filename, script_dir, agents):
+  """Writes out a trace html file.
 
-    if not categories:
-      categories = get_default_categories()
-    atrace_args.extend(categories)
+  Args:
+    html_filename: The name of the file to write.
+    script_dir: The directory containing this script.
+    agents: The systrace agents.
+  """
+  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')
 
-    if options.fix_threads:
-      atrace_args.extend([';', 'ps', '-t'])
-    tracer_args = ['adb', 'shell', ' '.join(atrace_args)]
+  # Open the file in binary mode to prevent python from changing the
+  # line endings.
+  html_file = open(html_filename, 'wb')
+  html_file.write(html_prefix.replace('{{SYSTRACE_TRACE_VIEWER_HTML}}',
+                                      trace_viewer_html))
 
-  if tracer_args[0] == 'adb':
-    add_adb_serial(tracer_args, options.device_serial)
+  html_file.write('<!-- BEGIN TRACE -->\n')
+  for a in agents:
+    html_file.write('  <script class="')
+    html_file.write(a.get_class_name())
+    html_file.write('" type="application/text">\n')
+    html_file.write(a.get_trace_data())
+    html_file.write('  </script>\n')
+  html_file.write('<!-- END TRACE -->\n')
+
+  html_file.write(html_suffix)
+  html_file.close()
+  print '\n    wrote file://%s\n' % os.path.abspath(html_filename)
+
+
+def create_agents(options, categories):
+  """Create systrace agents.
+
+  This function will search systrace agent modules in agent directories and
+  create the corresponding systrace agents.
+  Args:
+    options: The command-line options.
+    categories: The trace categories to capture.
+  Returns:
+    The list of systrace agents.
+  """
+  agent_dirs = [os.path.join(os.path.dirname(__file__), DEFAULT_AGENT_DIR)]
+  if options.agent_dirs:
+    agent_dirs.extend(options.agent_dirs.split(','))
+
+  agents = []
+  for agent_dir in agent_dirs:
+    if not agent_dir:
+      continue
+    for filename in os.listdir(agent_dir):
+      (module_name, ext) = os.path.splitext(filename)
+      if ext != '.py' or module_name == '__init__':
+        continue
+      (f, pathname, data) = imp.find_module(module_name, [agent_dir])
+      try:
+        module = imp.load_module(module_name, f, pathname, data)
+      finally:
+        if f:
+          f.close()
+      if module:
+        agent = module.try_create_agent(options, categories)
+        if not agent:
+          continue
+        agents.append(agent)
+  return agents
+
+
+def main():
+  options, categories = parse_options(sys.argv)
+  agents = create_agents(options, categories)
+
+  if not agents:
+    dirs = DEFAULT_AGENT_DIR
+    if options.agent_dirs:
+      dirs += ',' + options.agent_dirs
+    print >> sys.stderr, ('No systrace agent is available in directories |%s|.'
+                          % dirs)
+    sys.exit(1)
+
+  for a in agents:
+    a.start()
+
+  for a in agents:
+    a.collect_result()
+    if not a.expect_trace():
+      # Nothing more to do.
+      return
 
   script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
+  write_trace_html(options.output_file, script_dir, agents)
 
-  html_filename = options.output_file
-
-  adb = subprocess.Popen(tracer_args, stdout=subprocess.PIPE,
-                         stderr=subprocess.PIPE)
-
-  result = None
-  data = []
-
-  # Read the text portion of the output and watch for the 'TRACE:' marker that
-  # indicates the start of the trace data.
-  while result is None:
-    ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr])
-    if adb.stderr in ready[0]:
-      err = os.read(adb.stderr.fileno(), 4096)
-      sys.stderr.write(err)
-      sys.stderr.flush()
-    if adb.stdout in ready[0]:
-      out = os.read(adb.stdout.fileno(), 4096)
-      parts = out.split('\nTRACE:', 1)
-
-      txt = parts[0].replace('\r', '')
-      if len(parts) == 2:
-        # The '\nTRACE:' match stole the last newline from the text, so add it
-        # back here.
-        txt += '\n'
-      sys.stdout.write(txt)
-      sys.stdout.flush()
-
-      if len(parts) == 2:
-        data.append(parts[1])
-        sys.stdout.write("downloading trace...")
-        sys.stdout.flush()
-        break
-
-    result = adb.poll()
-
-  # Read and buffer the data portion of the output.
-  while True:
-    ready = select.select([adb.stdout, adb.stderr], [], [adb.stdout, adb.stderr])
-    keepReading = False
-    if adb.stderr in ready[0]:
-      err = os.read(adb.stderr.fileno(), 4096)
-      if len(err) > 0:
-        keepReading = True
-        sys.stderr.write(err)
-        sys.stderr.flush()
-    if adb.stdout in ready[0]:
-      out = os.read(adb.stdout.fileno(), 4096)
-      if len(out) > 0:
-        keepReading = True
-        data.append(out)
-
-    if result is not None and not keepReading:
-      break
-
-    result = adb.poll()
-
-  if result == 0:
-    if expect_trace:
-      data = ''.join(data)
-
-      # Collapse CRLFs that are added by adb shell.
-      if data.startswith('\r\n'):
-        data = data.replace('\r\n', '\n')
-
-      # Skip the initial newline.
-      data = data[1:]
-
-      if not data:
-        print >> sys.stderr, ('No data was captured.  Output file was not ' +
-          'written.')
-        sys.exit(1)
-      else:
-        # Indicate to the user that the data download is complete.
-        print " done\n"
-
-      # Extract the thread list dumped by ps.
-      threads = {}
-      if options.fix_threads:
-        parts = re.split('USER +PID +PPID +VSIZE +RSS +WCHAN +PC +NAME', data, 1)
-        if len(parts) == 2:
-          data = parts[0]
-          for line in parts[1].splitlines():
-            cols = line.split(None, 8)
-            if len(cols) == 9:
-              tid = int(cols[1])
-              name = cols[8]
-              threads[tid] = name
-
-      # Decompress and preprocess the data.
-      out = zlib.decompress(data)
-      if options.fix_threads:
-        def repl(m):
-          tid = int(m.group(2))
-          if tid > 0:
-            name = threads.get(tid)
-            if name is None:
-              name = m.group(1)
-              if name == '<...>':
-                name = '<' + str(tid) + '>'
-              threads[tid] = name
-            return name + '-' + m.group(2)
-          else:
-            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')
-
-      html_file = open(html_filename, 'w')
-      html_file.write(
-        html_prefix.replace("{{SYSTRACE_TRACE_VIEWER_HTML}}", trace_viewer_html))
-
-      html_file.write('<!-- BEGIN TRACE -->\n' +
-          '  <script class="trace-data" type="application/text">\n')
-      html_file.write(out)
-      html_file.write('  </script>\n<!-- END TRACE -->\n')
-
-      html_file.write(html_suffix)
-      html_file.close()
-      print "\n    wrote file://%s\n" % os.path.abspath(options.output_file)
-
-  else: # i.e. result != 0
-    print >> sys.stderr, 'adb returned error code %d' % result
-    sys.exit(1)
 
 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()
diff --git a/systrace_agent.py b/systrace_agent.py
new file mode 100644
index 0000000..376d4f2
--- /dev/null
+++ b/systrace_agent.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2015 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.
+
+class SystraceAgent(object):
+  """The base class for systrace agents.
+
+  A systrace agent contains the command-line options and trace categories to
+  capture. Each systrace agent has its own tracing implementation.
+  """
+
+  def __init__(self, options, categories):
+    """Initialize a systrace agent.
+
+    Args:
+      options: The command-line options.
+      categories: The trace categories to capture.
+    """
+    self._options = options
+    self._categories = categories
+
+  def start(self):
+    """Start tracing.
+    """
+    raise NotImplementedError()
+
+  def collect_result(self):
+    """Collect the result of tracing.
+
+    This function will block while collecting the result. For sync mode, it
+    reads the data, e.g., from stdout, until it finishes. For async mode, it
+    blocks until the agent is stopped and the data is ready.
+    """
+    raise NotImplementedError()
+
+  def expect_trace(self):
+    """Check if the agent is returning a trace or not.
+
+    This will be determined in collect_result().
+    Returns:
+      Whether the agent is expecting a trace or not.
+    """
+    raise NotImplementedError()
+
+  def get_trace_data(self):
+    """Get the trace data.
+
+    Returns:
+      The trace data.
+    """
+    raise NotImplementedError()
+
+  def get_class_name(self):
+    """Get the class name
+
+    The class name is used to identify the trace type when the trace is written
+    to the html file
+    Returns:
+      The class name.
+    """
+    raise NotImplementedError()
diff --git a/test_data/atrace_data b/test_data/atrace_data
new file mode 100644
index 0000000..7b423e7
--- /dev/null
+++ b/test_data/atrace_data
@@ -0,0 +1,127 @@
+# tracer: nop
+#
+# entries-in-buffer/entries-written: 116/116   #P:1
+#
+#                              _-----=> irqs-off
+#                             / _----=> need-resched
+#                            | / _---=> hardirq/softirq
+#                            || / _--=> preempt-depth
+#                            ||| /     delay
+#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
+#              | |       |   ||||       |         |
+atrace-14446 [000] ...2  1212.465062: sched_switch: prev_comm=atrace prev_pid=14446 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465074: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465082: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.465092: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465102: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465126: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.465132: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465139: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465145: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.465227: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h5  1212.465297: sched_wakeup: comm=adbd pid=212 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465306: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=adbd next_pid=212 next_prio=120
+adbd-212   [000] d..4  1212.465329: sched_wakeup: comm=adbd pid=209 prio=120 success=1 target_cpu=000
+adbd-212   [000] ...2  1212.465348: sched_switch: prev_comm=adbd prev_pid=212 prev_prio=120 prev_state=S ==> next_comm=adbd next_pid=209 next_prio=120
+/sbin/adbd-209   [000] d..4  1212.465395: sched_wakeup: comm=adbd pid=211 prio=120 success=1 target_cpu=000
+/sbin/adbd-209   [000] ...2  1212.465441: sched_switch: prev_comm=adbd prev_pid=209 prev_prio=120 prev_state=S ==> next_comm=adbd next_pid=211 next_prio=120
+adbd-211   [000] ...2  1212.465448: sched_switch: prev_comm=adbd prev_pid=211 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h5  1212.574554: sched_wakeup: comm=sensors.qcom pid=292 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.574566: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=sensors.qcom next_pid=292 next_prio=120
+sensors.qcom-292   [000] ...2  1212.574665: sched_switch: prev_comm=sensors.qcom prev_pid=292 prev_prio=120 prev_state=S ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+sensors.qcom-14447 [000] d..4  1212.574797: sched_wakeup: comm=sensors.qcom pid=1593 prio=120 success=1 target_cpu=000
+sensors.qcom-14447 [000] ...2  1212.574802: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=R+ ==> next_comm=sensors.qcom next_pid=1593 next_prio=120
+sensors.qcom-1593  [000] ...2  1212.574819: sched_switch: prev_comm=sensors.qcom prev_pid=1593 prev_prio=120 prev_state=D ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+sensors.qcom-14447 [000] d..3  1212.574823: sched_wakeup: comm=sensors.qcom pid=1593 prio=120 success=1 target_cpu=000
+sensors.qcom-14447 [000] ...2  1212.574827: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=R+ ==> next_comm=sensors.qcom next_pid=1593 next_prio=120
+sensors.qcom-1593  [000] d..4  1212.574865: sched_wakeup: comm=sensors.qcom pid=760 prio=120 success=1 target_cpu=000
+sensors.qcom-1593  [000] ...2  1212.574876: sched_switch: prev_comm=sensors.qcom prev_pid=1593 prev_prio=120 prev_state=S ==> next_comm=sensors.qcom next_pid=760 next_prio=120
+sensors.qcom-760   [000] d..4  1212.574905: sched_wakeup: comm=system_server pid=782 prio=118 success=1 target_cpu=000
+sensors.qcom-760   [000] ...2  1212.574917: sched_switch: prev_comm=sensors.qcom prev_pid=760 prev_prio=120 prev_state=S ==> next_comm=system_server next_pid=782 next_prio=118
+system_server-782   [000] d..4  1212.574981: sched_wakeup: comm=system_server pid=785 prio=118 success=1 target_cpu=000
+system_server-782   [000] ...2  1212.575009: sched_switch: prev_comm=system_server prev_pid=782 prev_prio=118 prev_state=S ==> next_comm=system_server next_pid=785 next_prio=118
+system_server-785   [000] ...2  1212.575045: sched_switch: prev_comm=system_server prev_pid=785 prev_prio=118 prev_state=S ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+sensors.qcom-14447 [000] ...3  1212.575143: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=x ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575153: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575159: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.575167: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575175: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575181: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.575188: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575195: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575201: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.575211: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1212.649601: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.649614: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+MMHandlerThread-7231  [000] ...2  1212.649630: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.729539: sched_wakeup: comm=kworker/u:1 pid=21 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNs6  1212.729550: sched_wakeup: comm=kworker/0:2H pid=557 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.729563: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:1 next_pid=21 next_prio=120
+kworker/u:1-21    [000] d..5  1212.729571: sched_wakeup: comm=mpdecision pid=2046 prio=113 success=1 target_cpu=000
+kworker/u:1-21    [000] ...2  1212.729578: sched_switch: prev_comm=kworker/u:1 prev_pid=21 prev_prio=120 prev_state=S ==> next_comm=kworker/0:2H next_pid=557 next_prio=100
+kworker/0:2H-557   [000] d..4  1212.729597: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+kworker/0:2H-557   [000] ...2  1212.729600: sched_switch: prev_comm=kworker/0:2H prev_pid=557 prev_prio=100 prev_state=D ==> next_comm=mpdecision next_pid=2046 next_prio=113
+mpdecision-2046  [000] ...2  1212.729801: sched_switch: prev_comm=mpdecision prev_pid=2046 prev_prio=113 prev_state=S ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+kworker/0:1H-17    [000] ...2  1212.730104: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730134: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730154: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.730176: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730201: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730220: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.730241: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730262: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730280: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.730303: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.730638: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730669: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+kworker/u:0H-7     [000] d..6  1212.730707: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+kworker/u:0H-7     [000] ...2  1212.730728: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+kworker/0:1H-17    [000] ...2  1212.730916: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.731632: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.731661: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+kworker/u:0H-7     [000] d..6  1212.731702: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+kworker/u:0H-7     [000] ...2  1212.731722: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+kworker/0:1H-17    [000] ...2  1212.731832: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.732685: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.732714: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+kworker/u:0H-7     [000] d..6  1212.732747: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+kworker/u:0H-7     [000] ...2  1212.732767: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+kworker/0:1H-17    [000] d..4  1212.732810: sched_wakeup: comm=kworker/0:2H pid=557 prio=100 success=1 target_cpu=000
+kworker/0:1H-17    [000] ...2  1212.732829: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=S ==> next_comm=kworker/0:2H next_pid=557 next_prio=100
+kworker/0:2H-557   [000] ...2  1212.732854: sched_switch: prev_comm=kworker/0:2H prev_pid=557 prev_prio=100 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1212.876266: sched_wakeup: comm=RILSender0 pid=1365 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1212.876284: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.876316: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=RILSender0 next_pid=1365 next_prio=120
+RILSender0-1365  [000] ...2  1212.876415: sched_switch: prev_comm=RILSender0 prev_pid=1365 prev_prio=120 prev_state=S ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+MMHandlerThread-7231  [000] ...2  1212.876454: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.089569: sched_wakeup: comm=Thread-625 pid=5750 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1213.089587: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.089622: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=Thread-625 next_pid=5750 next_prio=120
+Thread-625-5750  [000] ...2  1213.089842: sched_switch: prev_comm=Thread-625 prev_pid=5750 prev_prio=120 prev_state=S ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+MMHandlerThread-7231  [000] ...2  1213.089879: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.327439: sched_wakeup: comm=pandora.android pid=5395 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1213.327455: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.327487: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+MMHandlerThread-7231  [000] ...2  1213.327518: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=pandora.android next_pid=5395 next_prio=120
+com.pandora.android-5395  [000] d..4  1213.327718: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+com.pandora.android-5395  [000] ...2  1213.327739: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=780 next_prio=120
+Binder_1-780   [000] ...2  1213.327763: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=D ==> next_comm=pandora.android next_pid=5395 next_prio=120
+com.pandora.android-5395  [000] d..3  1213.327781: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+com.pandora.android-5395  [000] ...2  1213.327795: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=780 next_prio=120
+Binder_1-780   [000] d..4  1213.328056: sched_wakeup: comm=Binder_1 pid=878 prio=120 success=1 target_cpu=000
+Binder_1-780   [000] ...2  1213.328095: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=S ==> next_comm=Binder_1 next_pid=878 next_prio=120
+Binder_1-878   [000] d..4  1213.328263: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+Binder_1-878   [000] ...2  1213.328345: sched_switch: prev_comm=Binder_1 prev_pid=878 prev_prio=120 prev_state=S ==> next_comm=Binder_1 next_pid=780 next_prio=120
+Binder_1-780   [000] ...2  1213.328558: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=S ==> next_comm=pandora.android next_pid=5395 next_prio=120
+com.pandora.android-5395  [000] ...2  1213.328743: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328773: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328793: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1213.328821: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328846: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328866: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1213.328891: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328913: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328931: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1213.328964: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.465138: sched_wakeup: comm=atrace pid=14446 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.465171: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=atrace next_pid=14446 next_prio=120
diff --git a/test_data/atrace_data_raw b/test_data/atrace_data_raw
new file mode 100644
index 0000000..6500ec0
--- /dev/null
+++ b/test_data/atrace_data_raw
Binary files differ
diff --git a/test_data/atrace_data_stripped b/test_data/atrace_data_stripped
new file mode 100644
index 0000000..3261ebf
--- /dev/null
+++ b/test_data/atrace_data_stripped
@@ -0,0 +1,127 @@
+# tracer: nop
+#
+# entries-in-buffer/entries-written: 116/116   #P:1
+#
+#                              _-----=> irqs-off
+#                             / _----=> need-resched
+#                            | / _---=> hardirq/softirq
+#                            || / _--=> preempt-depth
+#                            ||| /     delay
+#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
+#              | |       |   ||||       |         |
+          atrace-14446 [000] ...2  1212.465062: sched_switch: prev_comm=atrace prev_pid=14446 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465074: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465082: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.465092: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465102: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465126: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.465132: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465139: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465145: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.465227: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h5  1212.465297: sched_wakeup: comm=adbd pid=212 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465306: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=adbd next_pid=212 next_prio=120
+            adbd-212   [000] d..4  1212.465329: sched_wakeup: comm=adbd pid=209 prio=120 success=1 target_cpu=000
+            adbd-212   [000] ...2  1212.465348: sched_switch: prev_comm=adbd prev_pid=212 prev_prio=120 prev_state=S ==> next_comm=adbd next_pid=209 next_prio=120
+            adbd-209   [000] d..4  1212.465395: sched_wakeup: comm=adbd pid=211 prio=120 success=1 target_cpu=000
+            adbd-209   [000] ...2  1212.465441: sched_switch: prev_comm=adbd prev_pid=209 prev_prio=120 prev_state=S ==> next_comm=adbd next_pid=211 next_prio=120
+            adbd-211   [000] ...2  1212.465448: sched_switch: prev_comm=adbd prev_pid=211 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h5  1212.574554: sched_wakeup: comm=sensors.qcom pid=292 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.574566: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=sensors.qcom next_pid=292 next_prio=120
+    sensors.qcom-292   [000] ...2  1212.574665: sched_switch: prev_comm=sensors.qcom prev_pid=292 prev_prio=120 prev_state=S ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+    sensors.qcom-14447 [000] d..4  1212.574797: sched_wakeup: comm=sensors.qcom pid=1593 prio=120 success=1 target_cpu=000
+    sensors.qcom-14447 [000] ...2  1212.574802: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=R+ ==> next_comm=sensors.qcom next_pid=1593 next_prio=120
+    sensors.qcom-1593  [000] ...2  1212.574819: sched_switch: prev_comm=sensors.qcom prev_pid=1593 prev_prio=120 prev_state=D ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+    sensors.qcom-14447 [000] d..3  1212.574823: sched_wakeup: comm=sensors.qcom pid=1593 prio=120 success=1 target_cpu=000
+    sensors.qcom-14447 [000] ...2  1212.574827: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=R+ ==> next_comm=sensors.qcom next_pid=1593 next_prio=120
+    sensors.qcom-1593  [000] d..4  1212.574865: sched_wakeup: comm=sensors.qcom pid=760 prio=120 success=1 target_cpu=000
+    sensors.qcom-1593  [000] ...2  1212.574876: sched_switch: prev_comm=sensors.qcom prev_pid=1593 prev_prio=120 prev_state=S ==> next_comm=sensors.qcom next_pid=760 next_prio=120
+    sensors.qcom-760   [000] d..4  1212.574905: sched_wakeup: comm=system_server pid=782 prio=118 success=1 target_cpu=000
+    sensors.qcom-760   [000] ...2  1212.574917: sched_switch: prev_comm=sensors.qcom prev_pid=760 prev_prio=120 prev_state=S ==> next_comm=system_server next_pid=782 next_prio=118
+   system_server-782   [000] d..4  1212.574981: sched_wakeup: comm=system_server pid=785 prio=118 success=1 target_cpu=000
+   system_server-782   [000] ...2  1212.575009: sched_switch: prev_comm=system_server prev_pid=782 prev_prio=118 prev_state=S ==> next_comm=system_server next_pid=785 next_prio=118
+   system_server-785   [000] ...2  1212.575045: sched_switch: prev_comm=system_server prev_pid=785 prev_prio=118 prev_state=S ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+    sensors.qcom-14447 [000] ...3  1212.575143: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=x ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575153: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575159: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.575167: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575175: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575181: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.575188: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575195: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575201: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.575211: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1212.649601: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.649614: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+ MMHandlerThread-7231  [000] ...2  1212.649630: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.729539: sched_wakeup: comm=kworker/u:1 pid=21 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNs6  1212.729550: sched_wakeup: comm=kworker/0:2H pid=557 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.729563: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:1 next_pid=21 next_prio=120
+     kworker/u:1-21    [000] d..5  1212.729571: sched_wakeup: comm=mpdecision pid=2046 prio=113 success=1 target_cpu=000
+     kworker/u:1-21    [000] ...2  1212.729578: sched_switch: prev_comm=kworker/u:1 prev_pid=21 prev_prio=120 prev_state=S ==> next_comm=kworker/0:2H next_pid=557 next_prio=100
+    kworker/0:2H-557   [000] d..4  1212.729597: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+    kworker/0:2H-557   [000] ...2  1212.729600: sched_switch: prev_comm=kworker/0:2H prev_pid=557 prev_prio=100 prev_state=D ==> next_comm=mpdecision next_pid=2046 next_prio=113
+      mpdecision-2046  [000] ...2  1212.729801: sched_switch: prev_comm=mpdecision prev_pid=2046 prev_prio=113 prev_state=S ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+    kworker/0:1H-17    [000] ...2  1212.730104: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730134: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730154: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.730176: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730201: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730220: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.730241: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730262: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730280: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1212.730303: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.730638: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730669: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+    kworker/u:0H-7     [000] d..6  1212.730707: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+    kworker/u:0H-7     [000] ...2  1212.730728: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+    kworker/0:1H-17    [000] ...2  1212.730916: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.731632: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.731661: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+    kworker/u:0H-7     [000] d..6  1212.731702: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+    kworker/u:0H-7     [000] ...2  1212.731722: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+    kworker/0:1H-17    [000] ...2  1212.731832: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.732685: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.732714: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+    kworker/u:0H-7     [000] d..6  1212.732747: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+    kworker/u:0H-7     [000] ...2  1212.732767: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+    kworker/0:1H-17    [000] d..4  1212.732810: sched_wakeup: comm=kworker/0:2H pid=557 prio=100 success=1 target_cpu=000
+    kworker/0:1H-17    [000] ...2  1212.732829: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=S ==> next_comm=kworker/0:2H next_pid=557 next_prio=100
+    kworker/0:2H-557   [000] ...2  1212.732854: sched_switch: prev_comm=kworker/0:2H prev_pid=557 prev_prio=100 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1212.876266: sched_wakeup: comm=RILSender0 pid=1365 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1212.876284: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.876316: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=RILSender0 next_pid=1365 next_prio=120
+      RILSender0-1365  [000] ...2  1212.876415: sched_switch: prev_comm=RILSender0 prev_pid=1365 prev_prio=120 prev_state=S ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+ MMHandlerThread-7231  [000] ...2  1212.876454: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.089569: sched_wakeup: comm=Thread-625 pid=5750 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1213.089587: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.089622: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=Thread-625 next_pid=5750 next_prio=120
+      Thread-625-5750  [000] ...2  1213.089842: sched_switch: prev_comm=Thread-625 prev_pid=5750 prev_prio=120 prev_state=S ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+ MMHandlerThread-7231  [000] ...2  1213.089879: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.327439: sched_wakeup: comm=pandora.android pid=5395 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1213.327455: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.327487: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+ MMHandlerThread-7231  [000] ...2  1213.327518: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=pandora.android next_pid=5395 next_prio=120
+ pandora.android-5395  [000] d..4  1213.327718: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+ pandora.android-5395  [000] ...2  1213.327739: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=780 next_prio=120
+        Binder_1-780   [000] ...2  1213.327763: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=D ==> next_comm=pandora.android next_pid=5395 next_prio=120
+ pandora.android-5395  [000] d..3  1213.327781: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+ pandora.android-5395  [000] ...2  1213.327795: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=780 next_prio=120
+        Binder_1-780   [000] d..4  1213.328056: sched_wakeup: comm=Binder_1 pid=878 prio=120 success=1 target_cpu=000
+        Binder_1-780   [000] ...2  1213.328095: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=S ==> next_comm=Binder_1 next_pid=878 next_prio=120
+        Binder_1-878   [000] d..4  1213.328263: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+        Binder_1-878   [000] ...2  1213.328345: sched_switch: prev_comm=Binder_1 prev_pid=878 prev_prio=120 prev_state=S ==> next_comm=Binder_1 next_pid=780 next_prio=120
+        Binder_1-780   [000] ...2  1213.328558: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=S ==> next_comm=pandora.android next_pid=5395 next_prio=120
+ pandora.android-5395  [000] ...2  1213.328743: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328773: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328793: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1213.328821: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328846: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328866: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1213.328891: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328913: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328931: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+     ksoftirqd/0-3     [000] ...2  1213.328964: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.465138: sched_wakeup: comm=atrace pid=14446 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.465171: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=atrace next_pid=14446 next_prio=120
diff --git a/test_data/atrace_data_thread_fixed b/test_data/atrace_data_thread_fixed
new file mode 100644
index 0000000..7b423e7
--- /dev/null
+++ b/test_data/atrace_data_thread_fixed
@@ -0,0 +1,127 @@
+# tracer: nop
+#
+# entries-in-buffer/entries-written: 116/116   #P:1
+#
+#                              _-----=> irqs-off
+#                             / _----=> need-resched
+#                            | / _---=> hardirq/softirq
+#                            || / _--=> preempt-depth
+#                            ||| /     delay
+#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
+#              | |       |   ||||       |         |
+atrace-14446 [000] ...2  1212.465062: sched_switch: prev_comm=atrace prev_pid=14446 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465074: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465082: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.465092: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465102: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465126: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.465132: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.465139: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465145: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.465227: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h5  1212.465297: sched_wakeup: comm=adbd pid=212 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.465306: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=adbd next_pid=212 next_prio=120
+adbd-212   [000] d..4  1212.465329: sched_wakeup: comm=adbd pid=209 prio=120 success=1 target_cpu=000
+adbd-212   [000] ...2  1212.465348: sched_switch: prev_comm=adbd prev_pid=212 prev_prio=120 prev_state=S ==> next_comm=adbd next_pid=209 next_prio=120
+/sbin/adbd-209   [000] d..4  1212.465395: sched_wakeup: comm=adbd pid=211 prio=120 success=1 target_cpu=000
+/sbin/adbd-209   [000] ...2  1212.465441: sched_switch: prev_comm=adbd prev_pid=209 prev_prio=120 prev_state=S ==> next_comm=adbd next_pid=211 next_prio=120
+adbd-211   [000] ...2  1212.465448: sched_switch: prev_comm=adbd prev_pid=211 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h5  1212.574554: sched_wakeup: comm=sensors.qcom pid=292 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.574566: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=sensors.qcom next_pid=292 next_prio=120
+sensors.qcom-292   [000] ...2  1212.574665: sched_switch: prev_comm=sensors.qcom prev_pid=292 prev_prio=120 prev_state=S ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+sensors.qcom-14447 [000] d..4  1212.574797: sched_wakeup: comm=sensors.qcom pid=1593 prio=120 success=1 target_cpu=000
+sensors.qcom-14447 [000] ...2  1212.574802: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=R+ ==> next_comm=sensors.qcom next_pid=1593 next_prio=120
+sensors.qcom-1593  [000] ...2  1212.574819: sched_switch: prev_comm=sensors.qcom prev_pid=1593 prev_prio=120 prev_state=D ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+sensors.qcom-14447 [000] d..3  1212.574823: sched_wakeup: comm=sensors.qcom pid=1593 prio=120 success=1 target_cpu=000
+sensors.qcom-14447 [000] ...2  1212.574827: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=R+ ==> next_comm=sensors.qcom next_pid=1593 next_prio=120
+sensors.qcom-1593  [000] d..4  1212.574865: sched_wakeup: comm=sensors.qcom pid=760 prio=120 success=1 target_cpu=000
+sensors.qcom-1593  [000] ...2  1212.574876: sched_switch: prev_comm=sensors.qcom prev_pid=1593 prev_prio=120 prev_state=S ==> next_comm=sensors.qcom next_pid=760 next_prio=120
+sensors.qcom-760   [000] d..4  1212.574905: sched_wakeup: comm=system_server pid=782 prio=118 success=1 target_cpu=000
+sensors.qcom-760   [000] ...2  1212.574917: sched_switch: prev_comm=sensors.qcom prev_pid=760 prev_prio=120 prev_state=S ==> next_comm=system_server next_pid=782 next_prio=118
+system_server-782   [000] d..4  1212.574981: sched_wakeup: comm=system_server pid=785 prio=118 success=1 target_cpu=000
+system_server-782   [000] ...2  1212.575009: sched_switch: prev_comm=system_server prev_pid=782 prev_prio=118 prev_state=S ==> next_comm=system_server next_pid=785 next_prio=118
+system_server-785   [000] ...2  1212.575045: sched_switch: prev_comm=system_server prev_pid=785 prev_prio=118 prev_state=S ==> next_comm=sensors.qcom next_pid=14447 next_prio=120
+sensors.qcom-14447 [000] ...3  1212.575143: sched_switch: prev_comm=sensors.qcom prev_pid=14447 prev_prio=120 prev_state=x ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575153: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575159: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.575167: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575175: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575181: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.575188: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.575195: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.575201: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.575211: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1212.649601: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.649614: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+MMHandlerThread-7231  [000] ...2  1212.649630: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.729539: sched_wakeup: comm=kworker/u:1 pid=21 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNs6  1212.729550: sched_wakeup: comm=kworker/0:2H pid=557 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.729563: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:1 next_pid=21 next_prio=120
+kworker/u:1-21    [000] d..5  1212.729571: sched_wakeup: comm=mpdecision pid=2046 prio=113 success=1 target_cpu=000
+kworker/u:1-21    [000] ...2  1212.729578: sched_switch: prev_comm=kworker/u:1 prev_pid=21 prev_prio=120 prev_state=S ==> next_comm=kworker/0:2H next_pid=557 next_prio=100
+kworker/0:2H-557   [000] d..4  1212.729597: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+kworker/0:2H-557   [000] ...2  1212.729600: sched_switch: prev_comm=kworker/0:2H prev_pid=557 prev_prio=100 prev_state=D ==> next_comm=mpdecision next_pid=2046 next_prio=113
+mpdecision-2046  [000] ...2  1212.729801: sched_switch: prev_comm=mpdecision prev_pid=2046 prev_prio=113 prev_state=S ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+kworker/0:1H-17    [000] ...2  1212.730104: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730134: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730154: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.730176: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730201: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730220: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.730241: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1212.730262: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730280: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1212.730303: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.730638: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.730669: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+kworker/u:0H-7     [000] d..6  1212.730707: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+kworker/u:0H-7     [000] ...2  1212.730728: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+kworker/0:1H-17    [000] ...2  1212.730916: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.731632: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.731661: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+kworker/u:0H-7     [000] d..6  1212.731702: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+kworker/u:0H-7     [000] ...2  1212.731722: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+kworker/0:1H-17    [000] ...2  1212.731832: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h6  1212.732685: sched_wakeup: comm=kworker/u:0H pid=7 prio=100 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.732714: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/u:0H next_pid=7 next_prio=100
+kworker/u:0H-7     [000] d..6  1212.732747: sched_wakeup: comm=kworker/0:1H pid=17 prio=100 success=1 target_cpu=000
+kworker/u:0H-7     [000] ...2  1212.732767: sched_switch: prev_comm=kworker/u:0H prev_pid=7 prev_prio=100 prev_state=D ==> next_comm=kworker/0:1H next_pid=17 next_prio=100
+kworker/0:1H-17    [000] d..4  1212.732810: sched_wakeup: comm=kworker/0:2H pid=557 prio=100 success=1 target_cpu=000
+kworker/0:1H-17    [000] ...2  1212.732829: sched_switch: prev_comm=kworker/0:1H prev_pid=17 prev_prio=100 prev_state=S ==> next_comm=kworker/0:2H next_pid=557 next_prio=100
+kworker/0:2H-557   [000] ...2  1212.732854: sched_switch: prev_comm=kworker/0:2H prev_pid=557 prev_prio=100 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1212.876266: sched_wakeup: comm=RILSender0 pid=1365 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1212.876284: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1212.876316: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=RILSender0 next_pid=1365 next_prio=120
+RILSender0-1365  [000] ...2  1212.876415: sched_switch: prev_comm=RILSender0 prev_pid=1365 prev_prio=120 prev_state=S ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+MMHandlerThread-7231  [000] ...2  1212.876454: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.089569: sched_wakeup: comm=Thread-625 pid=5750 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1213.089587: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.089622: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=Thread-625 next_pid=5750 next_prio=120
+Thread-625-5750  [000] ...2  1213.089842: sched_switch: prev_comm=Thread-625 prev_pid=5750 prev_prio=120 prev_state=S ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+MMHandlerThread-7231  [000] ...2  1213.089879: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.327439: sched_wakeup: comm=pandora.android pid=5395 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] dNh4  1213.327455: sched_wakeup: comm=MMHandlerThread pid=7231 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.327487: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=MMHandlerThread next_pid=7231 next_prio=120
+MMHandlerThread-7231  [000] ...2  1213.327518: sched_switch: prev_comm=MMHandlerThread prev_pid=7231 prev_prio=120 prev_state=S ==> next_comm=pandora.android next_pid=5395 next_prio=120
+com.pandora.android-5395  [000] d..4  1213.327718: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+com.pandora.android-5395  [000] ...2  1213.327739: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=780 next_prio=120
+Binder_1-780   [000] ...2  1213.327763: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=D ==> next_comm=pandora.android next_pid=5395 next_prio=120
+com.pandora.android-5395  [000] d..3  1213.327781: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+com.pandora.android-5395  [000] ...2  1213.327795: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=780 next_prio=120
+Binder_1-780   [000] d..4  1213.328056: sched_wakeup: comm=Binder_1 pid=878 prio=120 success=1 target_cpu=000
+Binder_1-780   [000] ...2  1213.328095: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=S ==> next_comm=Binder_1 next_pid=878 next_prio=120
+Binder_1-878   [000] d..4  1213.328263: sched_wakeup: comm=Binder_1 pid=780 prio=120 success=1 target_cpu=000
+Binder_1-878   [000] ...2  1213.328345: sched_switch: prev_comm=Binder_1 prev_pid=878 prev_prio=120 prev_state=S ==> next_comm=Binder_1 next_pid=780 next_prio=120
+Binder_1-780   [000] ...2  1213.328558: sched_switch: prev_comm=Binder_1 prev_pid=780 prev_prio=120 prev_state=S ==> next_comm=pandora.android next_pid=5395 next_prio=120
+com.pandora.android-5395  [000] ...2  1213.328743: sched_switch: prev_comm=pandora.android prev_pid=5395 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328773: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328793: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1213.328821: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328846: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328866: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1213.328891: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d..4  1213.328913: sched_wakeup: comm=ksoftirqd/0 pid=3 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.328931: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/0 next_pid=3 next_prio=120
+ksoftirqd/0-3     [000] ...2  1213.328964: sched_switch: prev_comm=ksoftirqd/0 prev_pid=3 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
+          <idle>-0     [000] d.h4  1213.465138: sched_wakeup: comm=atrace pid=14446 prio=120 success=1 target_cpu=000
+          <idle>-0     [000] ...2  1213.465171: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=atrace next_pid=14446 next_prio=120
diff --git a/test_data/atrace_data_with_thread_list b/test_data/atrace_data_with_thread_list
new file mode 100644
index 0000000..860d73f
--- /dev/null
+++ b/test_data/atrace_data_with_thread_list
Binary files differ
diff --git a/test_data/atrace_thread_names b/test_data/atrace_thread_names
new file mode 100644
index 0000000..bff5d1b
--- /dev/null
+++ b/test_data/atrace_thread_names
@@ -0,0 +1 @@
+{1: '/init', 2: 'kthreadd', 3: 'ksoftirqd/0', 7: 'kworker/u:0H', 8: 'migration/0', 13: 'khelper', 14: 'netns', 17: 'kworker/0:1H', 18: 'modem_notifier', 19: 'smd_channel_clo', 20: 'smsm_cb_wq', 21: 'kworker/u:1', 22: 'rpm-smd', 23: 'kworker/u:1H', 24: 'irq/317-earjack', 25: 'sync_supers', 26: 'bdi-default', 27: 'kblockd', 28: 'vmalloc', 29: 'khubd', 30: 'irq/102-msm_iom', 31: 'irq/102-msm_iom', 32: 'irq/102-msm_iom', 33: 'irq/79-msm_iomm', 34: 'irq/78-msm_iomm', 35: 'irq/78-msm_iomm', 36: 'irq/74-msm_iomm', 37: 'irq/75-msm_iomm', 38: 'irq/75-msm_iomm', 39: 'irq/75-msm_iomm', 40: 'irq/75-msm_iomm', 41: 'irq/273-msm_iom', 42: 'irq/273-msm_iom', 43: 'irq/97-msm_iomm', 44: 'irq/97-msm_iomm', 45: 'irq/97-msm_iomm', 46: 'l2cap', 47: 'a2mp', 48: 'cfg80211', 49: 'qmi', 50: 'nmea', 51: 'msm_ipc_router', 52: 'apr_driver', 54: 'kswapd0', 55: 'fsnotify_mark', 56: 'cifsiod', 57: 'crypto', 75: 'ad_calc_wq', 76: 'hdmi_tx_workq', 77: 'anx7808_work', 78: 'k_hsuart', 79: 'diag_wq', 80: 'diag_cntl_wq', 81: 'diag_dci_wq', 82: 'kgsl-3d0', 84: 'f9966000.spi', 88: 'usbnet', 89: 'irq/329-anx7808', 90: 'k_rmnet_mux_wor', 91: 'f_mtp', 92: 'file-storage', 93: 'uether', 94: 'synaptics_wq', 95: 'irq/362-s3350', 96: 'kworker/0:2', 97: 'msm_vidc_worker', 98: 'msm_vidc_worker', 99: 'msm_cpp_workque', 100: 'irq/350-bq51013', 102: 'dm_bufio_cache', 103: 'dbs_sync/0', 104: 'dbs_sync/1', 105: 'dbs_sync/2', 106: 'dbs_sync/3', 107: 'cfinteractive', 108: 'irq/170-msm_sdc', 109: 'binder', 110: 'usb_bam_wq', 111: 'krfcommd', 112: 'bam_dmux_rx', 113: 'bam_dmux_tx', 114: 'rq_stats', 115: 'deferwq', 117: 'irq/361-MAX1704', 119: 'mmcqd/1', 120: 'mmcqd/1rpmb', 121: 'wl_event_handle', 122: 'dhd_watchdog_th', 123: 'dhd_dpc', 124: 'dhd_rxf', 125: 'dhd_sysioc', 126: 'vibrator', 127: 'max1462x', 128: 'irq/310-maxim_m', 129: 'irq/311-maxim_m', 130: '/sbin/ueventd', 132: 'jbd2/mmcblk0p25', 133: 'ext4-dio-unwrit', 136: 'flush-179:0', 138: 'jbd2/mmcblk0p28', 139: 'ext4-dio-unwrit', 143: 'jbd2/mmcblk0p27', 144: 'ext4-dio-unwrit', 145: 'jbd2/mmcblk0p16', 146: 'ext4-dio-unwrit', 13678: 'Heap thread poo', 13679: 'Heap thread poo', 13680: 'Signal Catcher', 13681: 'JDWP', 169: '/system/bin/logd', 170: '/sbin/healthd', 171: '/system/bin/lmkd', 172: '/system/bin/servicemanager', 173: '/system/bin/vold', 174: 'IPCRTR', 175: 'sb-1', 177: 'ipc_rtr_q6_ipcr', 179: 'ngd_msm_ctrl_ng', 180: '/system/bin/surfaceflinger', 181: '/system/bin/rmt_storage', 182: '/system/bin/qseecomd', 183: 'msm_slim_qmi_cl', 184: 'msm_qmi_rtx_q', 185: '/system/bin/sh', 187: '/system/bin/subsystem_ramdump', 188: '/system/bin/netd', 189: '/system/bin/debuggerd', 191: '/system/bin/rild', 192: '/system/bin/drmserver', 194: '/system/bin/mediaserver', 195: '/system/bin/installd', 197: '/system/bin/keystore', 198: '/system/bin/bridgemgrd', 199: '/system/bin/qmuxd', 200: '/system/bin/netmgrd', 201: '/system/bin/sensors.qcom', 204: '/system/bin/thermal-engine-hh', 205: 'zygote', 206: '/system/bin/sdcard', 207: '/system/bin/mm-qcamera-daemon', 208: '/system/bin/time_daemon', 209: '/sbin/adbd', 210: 'adbd', 211: 'adbd', 212: 'adbd', 214: 'irq/288-wcd9xxx', 216: 'logd.reader', 217: 'logd.writer', 218: 'logd', 219: 'kauditd', 13690: 'pool-1-thread-1', 223: 'vold', 226: 'vold', 227: 'sdcard', 228: 'sdcard', 240: 'Binder_1', 242: 'DispSync', 243: 'Binder_2', 244: 'logd.auditd', 247: 'thermal-engine-', 250: 'thermal-engine-', 13695: 'org.chromium.chrome.shell', 252: 'thermal-engine-', 253: 'thermal-engine-', 254: 'thermal-engine-', 255: 'thermal-engine-', 257: 'thermal-engine-', 258: 'thermal-engine-', 259: 'thermal-engine-', 260: 'thermal-engine-', 261: 'thermal-engine-', 262: 'thermal-engine-', 263: 'thermal-engine-', 264: 'thermal-engine-', 265: 'thermal-engine-', 266: 'thermal-engine-', 267: 'thermal-engine-', 268: 'thermal-engine-', 269: 'thermal-engine-', 270: 'thermal-engine-', 272: 'thermal-engine-', 273: 'thermal-engine-', 275: 'thermal-engine-', 276: 'thermal-engine-', 277: 'thermal-engine-', 278: 'thermal-engine-', 280: 'thermal-engine-', 281: 'thermal-engine-', 282: 'thermal-engine-', 283: 'thermal-engine-', 284: 'thermal-engine-', 286: 'thermal-engine-', 287: 'thermal-engine-', 288: 'bridgemgrd', 289: 'netmgrd', 290: 'sensors.qcom', 292: 'sensors.qcom', 293: 'qmuxd', 295: 'thermal-engine-', 297: 'thermal-engine-', 299: 'thermal-engine-', 300: 'thermal-engine-', 301: 'thermal-engine-', 308: 'time_daemon', 311: 'msm_thermal:hot', 312: 'msm_thermal:fre', 13707: 'FinalizerDaemon', 335: 'rild', 13710: 'GCDaemon', 343: 'rild', 346: 'rild', 13711: 'Binder_1', 348: '/system/bin/qseecomd', 349: 'qseecomd', 351: 'qseecomd', 360: 'mdss_fb0', 361: 'hwcUeventThread', 362: 'hwcVsyncThread', 8569: 'Binder_4', 8572: 'Binder_5', 8575: 'Binder_5', 386: 'qseecomd', 387: 'qseecomd', 8580: 'Binder_6', 8581: 'Binder_5', 396: 'GL updater', 397: 'surfaceflinger', 398: 'EventThread', 399: 'surfaceflinger', 400: 'EventThread', 401: 'EventControl', 13721: 'Heap thread poo', 13722: 'Heap thread poo', 419: 'Binder_1', 13724: 'Signal Catcher', 13725: 'JDWP', 548: 'netd', 549: 'netd', 550: 'netd', 551: 'netd', 552: 'netd', 553: 'netd', 554: 'netd', 555: 'netd', 557: 'kworker/0:2H', 558: 'IPCRTR', 559: 'thermal-engine-', 560: 'sensors.qcom', 561: 'time_daemon', 562: 'ipc_rtr_smd_ipc', 563: 'sensors.qcom', 564: 'sensors.qcom', 571: 'rmt_storage', 572: 'rmt_storage', 573: 'rmt_storage', 574: 'rmt_storage', 575: 'Binder_3', 576: 'qmuxd', 577: 'qmuxd', 578: 'qmuxd', 579: 'qmuxd', 580: 'qmuxd', 581: 'qmuxd', 582: 'qmuxd', 583: 'qmuxd', 584: 'rild', 585: 'rild', 587: 'rild', 588: 'rild', 589: 'rild', 591: 'rild', 592: 'rild', 593: 'rild', 594: 'rild', 596: 'thermal-engine-', 597: 'time_daemon', 598: 'time_daemon', 599: 'time_daemon', 600: 'thermal-engine-', 601: 'thermal-engine-', 602: 'bridgemgrd', 603: 'bridgemgrd', 605: 'sensors.qcom', 614: 'sensors.qcom', 621: 'sensors.qcom', 622: 'sensors.qcom', 623: 'sensors.qcom', 624: 'sensors.qcom', 625: 'sensors.qcom', 626: 'sensors.qcom', 627: 'sensors.qcom', 628: 'sensors.qcom', 629: 'sensors.qcom', 13660: 'FinalizerWatchd', 633: 'sensors.qcom', 13760: 'FinalizerWatchd', 643: 'sensors.qcom', 8839: 'com.ushaqi.zhuishushenqi:pushservice', 650: 'sensors.qcom', 651: 'sensors.qcom', 8845: 'Heap thread poo', 8846: 'Heap thread poo', 8847: 'Heap thread poo', 8849: 'Signal Catcher', 8850: 'JDWP', 8851: 'ReferenceQueueD', 8852: 'FinalizerDaemon', 8853: 'FinalizerWatchd', 8854: 'HeapTrimmerDaem', 8855: 'GCDaemon', 8856: 'Binder_1', 8857: 'Binder_2', 8867: 'local_job_dispa', 8869: 'remote_job_disp', 8887: 'Upload Http Rec', 8890: 'Connection Cont', 13774: 'com.life360.android.safetymapd', 736: 'netmgrd', 746: 'netmgrd', 747: 'netmgrd', 748: 'netmgrd', 755: 'ApmTone', 756: 'ApmAudio', 757: 'ApmOutput', 758: 'mediaserver', 759: 'FastMixer', 760: 'sensors.qcom', 763: 'sensors.qcom', 764: 'system_server', 767: 'Heap thread poo', 768: 'Heap thread poo', 770: 'Heap thread poo', 8963: 'Smack Packet Re', 773: 'Signal Catcher', 774: 'JDWP', 775: 'ReferenceQueueD', 776: 'FinalizerDaemon', 777: 'FinalizerWatchd', 778: 'HeapTrimmerDaem', 779: 'GCDaemon', 780: 'Binder_1', 781: 'Binder_2', 782: 'system_server', 783: 'system_server', 784: 'sensors.qcom', 785: 'system_server', 786: 'system_server', 788: 'system_server', 789: 'system_server', 790: 'sensors.qcom', 791: 'system_server', 792: 'sensors.qcom', 793: 'system_server', 794: 'sensors.qcom', 795: 'system_server', 796: 'sensors.qcom', 797: 'system_server', 798: 'sensors.qcom', 799: 'system_server', 800: 'sensors.qcom', 801: 'system_server', 802: 'sensors.qcom', 803: 'system_server', 804: 'sensors.qcom', 805: 'system_server', 806: 'sensors.qcom', 807: 'system_server', 808: 'sensors.qcom', 809: 'system_server', 810: 'sensors.qcom', 811: 'system_server', 812: 'sensors.qcom', 813: 'system_server', 814: 'sensors.qcom', 815: 'system_server', 816: 'sensors.qcom', 817: 'system_server', 818: 'sensors.qcom', 819: 'system_server', 820: 'sensors.qcom', 821: 'system_server', 822: 'sensors.qcom', 823: 'system_server', 824: 'sensors.qcom', 826: 'SensorEventAckR', 827: 'SensorService', 828: 'android.bg', 829: 'ActivityManager', 830: 'FileObserver', 831: 'android.fg', 832: 'android.ui', 833: 'android.io', 834: 'android.display', 835: 'CpuTracker', 836: 'PowerManagerSer', 837: 'system_server', 838: 'system_server', 839: 'BatteryStats_wa', 840: 'PackageManager', 841: 'bridgemgrd', 842: 'PackageInstalle', 844: 'AlarmManager', 845: 'UEventObserver', 853: 'InputDispatcher', 854: 'InputReader', 857: 'MountService', 858: 'VoldConnector', 4239: 'FinalizerDaemon', 860: 'NetdConnector', 861: 'NetworkStats', 862: 'NetworkPolicy', 863: 'WifiP2pService', 864: 'WifiStateMachin', 865: 'WifiService', 866: 'ConnectivitySer', 867: 'NsdService', 868: 'mDnsConnector', 869: 'ranker', 870: 'AudioService', 871: 'AudioOut_2', 872: 'AudioOut_4', 873: 'FastMixer', 874: 'AudioOut_6', 878: 'Binder_1', 879: 'Binder_2', 882: 'WifiWatchdogSta', 883: 'WifiManager', 884: 'WifiScanningSer', 885: 'WifiRttService', 886: 'EthernetService', 887: 'backup', 888: '/system/bin/wpa_supplicant', 889: 'Thread-69', 892: 'LazyTaskWriterT', 893: 'UsbService host', 894: 'Thread-73', 915: 'com.android.systemui', 919: 'Heap thread poo', 920: 'Heap thread poo', 921: 'Heap thread poo', 925: 'Signal Catcher', 926: 'JDWP', 927: 'ReferenceQueueD', 928: 'FinalizerDaemon', 929: 'FinalizerWatchd', 930: 'HeapTrimmerDaem', 931: 'GCDaemon', 933: 'Binder_1', 934: 'Binder_2', 936: 'android.process.media', 942: 'Binder_3', 943: 'Heap thread poo', 944: 'Heap thread poo', 945: 'Heap thread poo', 947: 'Signal Catcher', 949: 'JDWP', 950: 'ReferenceQueueD', 951: 'FinalizerDaemon', 952: 'FinalizerWatchd', 953: 'HeapTrimmerDaem', 954: 'GCDaemon', 956: 'Binder_1', 957: 'Binder_2', 13671: 'sogou.mobile.explorer.hotwords', 964: 'SoundPool', 965: 'SoundPoolThread', 970: 'Recents-TaskRes', 13819: 'Binder_2', 1007: 'thumbs thread', 1020: 'MtpServer', 1078: 'SystemUI Storag', 1079: 'watchdog', 1094: 'SoundPool', 1095: 'SoundPoolThread', 1108: 'Binder_4', 1109: 'Binder_5', 1111: 'com.google.android.googlequicksearchbox:interactor', 1113: 'Heap thread poo', 1114: 'Heap thread poo', 1116: 'Heap thread poo', 1121: 'Signal Catcher', 1124: 'JDWP', 1125: 'ReferenceQueueD', 1126: 'FinalizerDaemon', 1127: 'FinalizerWatchd', 1128: 'HeapTrimmerDaem', 1129: 'GCDaemon', 1131: 'Binder_1', 1132: 'Binder_2', 1133: 'Binder_3', 1136: 'com.google.android.inputmethod.pinyin', 1142: 'Heap thread poo', 1143: 'Heap thread poo', 1144: 'Heap thread poo', 1145: 'Signal Catcher', 1146: 'JDWP', 1147: 'ReferenceQueueD', 1148: 'FinalizerDaemon', 1149: 'FinalizerWatchd', 1151: 'HeapTrimmerDaem', 1152: 'GCDaemon', 1153: 'Binder_1', 1154: 'Binder_2', 1186: 'FLP Service Cal', 1188: 'FLP Service Cal', 1191: 'NetworkTimeUpda', 1192: 'FLP Service Cal', 1199: 'com.android.nfc', 1208: 'Heap thread poo', 1209: 'Heap thread poo', 1210: 'Heap thread poo', 1211: 'Signal Catcher', 1212: 'JDWP', 1213: 'ReferenceQueueD', 1214: 'FinalizerDaemon', 1215: 'FinalizerWatchd', 1216: 'HeapTrimmerDaem', 1219: 'GCDaemon', 1220: 'Binder_1', 1221: 'Binder_2', 1226: 'Binder_6', 1233: 'Binder_7', 1234: 'com.redbend.vdmc', 1236: 'Heap thread poo', 1237: 'Heap thread poo', 1238: 'Heap thread poo', 1244: 'Signal Catcher', 1245: 'JDWP', 1246: 'ReferenceQueueD', 1247: 'FLP Service Cal', 1248: 'FinalizerDaemon', 1249: 'FinalizerWatchd', 1250: 'HeapTrimmerDaem', 1251: 'GCDaemon', 1252: 'Binder_1', 1256: 'Binder_8', 1257: 'Binder_2', 1260: 'WifiMonitor', 1271: 'Binder_9', 1274: 'com.android.phone', 1282: 'Heap thread poo', 1283: 'Heap thread poo', 1284: 'Heap thread poo', 1285: 'Signal Catcher', 1286: 'JDWP', 1287: 'ReferenceQueueD', 1288: 'FLP Service Cal', 1289: 'FLP Service Cal', 1290: 'FinalizerDaemon', 1291: 'FinalizerWatchd', 1292: 'HeapTrimmerDaem', 1293: 'GCDaemon', 1299: 'Binder_1', 1305: 'com.google.android.googlequicksearchbox', 1306: 'Heap thread poo', 1307: 'Heap thread poo', 1308: 'Heap thread poo', 1315: 'Binder_2', 1317: 'Signal Catcher', 1318: 'JDWP', 1319: 'FLP Service Cal', 1320: 'FLP Service Cal', 1322: 'ReferenceQueueD', 1323: 'FinalizerDaemon', 1324: 'FinalizerWatchd', 1330: 'GAThread', 1331: 'measurement-1', 1332: 'HeapTrimmerDaem', 1333: 'GCDaemon', 1334: 'Binder_1', 1335: 'Binder_2', 1336: 'pool-1-thread-1', 13684: 'FinalizerWatchd', 13880: 'Thread-1501', 1365: 'RILSender0', 1366: 'RILReceiver0', 1367: 'Thread-89', 1378: 'PhoneStatusBar', 1380: 'DcHandlerThread', 1381: 'WifiManager', 1385: 'AsyncTask #1', 1386: 'launcher-loader', 1388: 'AsyncTask #1', 1391: 'AsyncQueryWorke', 1392: 'GsmCellBroadcas', 1393: 'AsyncTask #1', 1394: 'GsmInboundSmsHa', 1395: 'AsyncTask #1', 1397: 'CellBroadcastHa', 1408: 'AsyncTask #1', 1409: 'AsyncTask #1', 1416: 'ConnectivityMan', 1417: 'CdmaInboundSmsH', 1418: 'CdmaServiceCate', 13890: 'Heap thread poo', 1425: 'AsyncTask #1', 1427: 'DcSwitchStateMa', 1428: 'Binder_3', 1429: 'SyncHandler-0', 1431: 'FlashlightContr', 1432: 'AsyncTask #2', 13892: 'Heap thread poo', 1434: 'AsyncTask #1', 1435: 'QSTileHost', 1438: 'AsyncTask #2', 1441: 'RenderThread', 1442: 'AsyncTask #3', 1443: 'AsyncTask #1', 13894: 'Signal Catcher', 1451: 'com.google.android.googlequicksearchbox:search', 1457: 'Heap thread poo', 1458: 'Heap thread poo', 1459: 'Heap thread poo', 1460: 'Signal Catcher', 1461: 'JDWP', 1462: 'ReferenceQueueD', 1463: 'FinalizerDaemon', 1464: 'FinalizerWatchd', 1466: 'HeapTrimmerDaem', 1468: 'GCDaemon', 1473: 'Binder_3', 1474: 'Binder_1', 1475: 'Binder_2', 1478: 'com.google.process.gapps', 1484: 'GELServices-0', 1485: 'Heap thread poo', 1486: 'Heap thread poo', 1487: 'Heap thread poo', 1488: 'Signal Catcher', 1489: 'JDWP', 1490: 'ReferenceQueueD', 1491: 'FinalizerDaemon', 1492: 'FinalizerWatchd', 1493: 'HeapTrimmerDaem', 1494: 'GCDaemon', 1495: 'Binder_1', 1496: 'Binder_2', 1497: 'Binder_3', 1501: 'Binder_4', 1503: 'AsyncTask #1', 1514: 'RenderThread', 1515: 'User-Facing Non', 1516: 'User-Facing Non', 1517: 'ervice.Executor', 1518: 'WifiManager', 1535: 'User-Facing Non', 1538: 'User-Facing Non', 1540: 'AsyncTask #3', 1553: 'User-Facing Non', 1560: 'IcingConnection', 1561: 'AsyncTask #1', 1563: 'Cat Telephony s', 1564: 'RilMessageDecod', 1565: 'hwuiTask1', 1566: 'hwuiTask2', 1567: 'Cat Icon Loader', 1573: 'Thread-55', 1574: 'Thread-56', 1575: 'Thread-57', 1577: 'SoundPool', 1578: 'SoundPoolThread', 1580: 'AudioRouter-0', 1593: 'sensors.qcom', 1600: 'sensors.qcom', 1613: 'Gservices', 1614: 'RefQueueWorker@', 1615: 'Gservices', 1616: 'RefQueueWorker@', 1618: 'GELServices-1', 1620: 'Gservices', 1621: 'GELServices-2', 1622: 'AsyncTask #2', 1626: 'AsyncFileStorag', 1629: 'GELServices-3', 1632: 'AsyncTask #4', 1633: 'AsyncTask #5', 1635: 'WifiManager', 1636: 'GELServices-4', 1637: 'AsyncTask #4', 1643: 'LocationOracleI', 1644: 'GL updater', 1646: 'GoogleApiClient', 1647: 'GELServices-5', 1654: 'Binder_A', 1664: 'GELServices-6', 1690: 'Binder_4', 1692: 'GL updater', 1693: 'NetworkMonitorN', 1695: 'DhcpStateMachin', 1700: '/system/bin/dhcpcd', 1764: 'Binder_3', 1766: 'GELServices-7', 1769: 'Binder_3', 1770: 'Gservices', 1772: 'RenderThread', 1773: 'RenderThread', 1774: 'RenderThread', 1775: 'RenderThread', 1781: 'AsyncTask #1', 1782: 'AsyncTask #2', 1807: 'RenderThread', 1810: 'ChromiumNet', 1811: 'DnsConfigServic', 1812: 'inotify_reader', 1815: 'Network File Th', 1816: 'SimpleCacheWork', 1817: 'SimpleCacheWork', 1823: 'Binder_4', 1824: 'Binder_5', 1873: 'com.google.android.gms', 1878: 'Heap thread poo', 1880: 'Heap thread poo', 1881: 'Heap thread poo', 1882: 'Signal Catcher', 1883: 'JDWP', 1884: 'ReferenceQueueD', 1885: 'FinalizerDaemon', 1886: 'FinalizerWatchd', 1887: 'HeapTrimmerDaem', 1888: 'GCDaemon', 1889: 'Binder_1', 1890: 'Binder_2', 13702: 'Heap thread poo', 1895: 'Gservices', 1898: 'measurement-1', 1900: 'AsyncTask #1', 1904: 'AsyncTask #2', 1949: 'com.google.android.gms.persistent', 1954: 'Heap thread poo', 1955: 'Heap thread poo', 1956: 'Heap thread poo', 1959: 'Signal Catcher', 1960: 'JDWP', 1961: 'ReferenceQueueD', 1962: 'FinalizerDaemon', 1963: 'FinalizerWatchd', 1964: 'HeapTrimmerDaem', 1965: 'GCDaemon', 1966: 'Binder_1', 1967: 'Binder_2', 1968: 'Gservices', 1973: 'IntentService[G', 1976: 'FlpThread', 1977: 'Binder_3', 1978: 'WifiManager', 1979: 'GeofencerStateM', 1980: 'LocationService', 1984: 'Binder_4', 1986: 'Binder_5', 1990: 'pool-4-thread-1', 1992: 'GmsCoreStatsSer', 1995: 'GoogleLocationS', 1996: 'Binder_4', 1997: 'Binder_5', 1998: 'GELServices-8', 2001: 'Binder_3', 2004: 'Thread-139', 2005: 'Thread-140', 2006: 'Thread-141', 2007: 'Thread-142', 2021: 'NetworkLocation', 2029: 'UlrDispatchingS', 2030: 'nlp-async-worke', 2031: '/system/bin/mpdecision', 2032: 'mpdecision', 2033: 'mpdecision', 2034: 'mpdecision', 2035: 'mpdecision', 2036: 'mpdecision', 2046: 'mpdecision', 2097: 'AsyncTask #3', 2124: 'SyncHandler-0', 2320: 'RemoteViewsCach', 2321: 'RemoteViewsAdap', 10584: 'pool-2-thread-1', 10591: 'RefQueueWorker@', 2497: 'WifiManager', 2509: 'picasa-uploads-', 2510: 'GCMWriter', 2512: 'AsyncTask #1', 2521: 'FitnessServiceF', 2522: 'FitRecordingBro', 13723: 'Heap thread poo', 2526: 'AsyncTask #1', 2530: 'NearbyMessagesB', 2536: 'GCMReader', 2547: 'pool-2-thread-1', 2647: 'com.qiyi.video.market', 2653: 'Heap thread poo', 2654: 'Heap thread poo', 2655: 'Heap thread poo', 2656: 'Signal Catcher', 2657: 'JDWP', 2658: 'ReferenceQueueD', 2659: 'FinalizerDaemon', 2660: 'FinalizerWatchd', 2661: 'HeapTrimmerDaem', 2662: 'GCDaemon', 2663: 'Binder_1', 2664: 'Binder_2', 2671: 'RefQueueWorker@', 2673: '.ProcessManager', 2675: 'Binder_3', 2677: 'Thread-208', 2679: 'pool-2-thread-1', 2680: 'WifiManager', 2682: 'Timer-0', 2683: 'Timer-1', 2710: 'AsyncTask #1', 2718: 'pool-3-thread-1', 2810: 'DownloadReceive', 2902: 'GELServices-9', 2905: 'PowerManagerSer', 2906: 'AsyncTask #2', 2915: 'AsyncTask #3', 2946: 'pool-7-thread-1', 3103: 'pool-4-thread-1', 3104: 'com.qiyi.video.market:pluginDownloadService', 3110: 'Heap thread poo', 3111: 'Heap thread poo', 3112: 'Heap thread poo', 3113: 'Signal Catcher', 3114: 'JDWP', 3115: 'ReferenceQueueD', 3116: 'FinalizerDaemon', 3117: 'FinalizerWatchd', 3118: 'HeapTrimmerDaem', 3119: 'GCDaemon', 3120: 'Binder_1', 3121: 'Binder_2', 3122: 'com.qiyi.video.market:bdservice_v1', 3128: 'Heap thread poo', 3129: 'Heap thread poo', 3130: 'Heap thread poo', 3131: 'Signal Catcher', 3132: 'JDWP', 3133: 'ReferenceQueueD', 3134: 'FinalizerDaemon', 3135: 'FinalizerWatchd', 3136: 'HeapTrimmerDaem', 3137: 'GCDaemon', 3138: 'Binder_1', 3139: 'Binder_2', 3141: 'RefQueueWorker@', 3145: 'RefQueueWorker@', 3154: 'com.qiyi.video.market:baiduLocation', 3163: 'Heap thread poo', 3164: 'Heap thread poo', 3165: 'Heap thread poo', 3166: 'Signal Catcher', 3167: 'JDWP', 3168: 'ReferenceQueueD', 3169: 'FinalizerDaemon', 3170: 'FinalizerWatchd', 3171: 'HeapTrimmerDaem', 3172: 'GCDaemon', 3173: 'Binder_1', 3174: 'Binder_2', 3177: 'RefQueueWorker@', 3179: 'com.tencent.mm:push', 3183: 'Heap thread poo', 3184: 'Heap thread poo', 3185: 'Heap thread poo', 3187: 'Signal Catcher', 3189: 'JDWP', 3190: 'ReferenceQueueD', 3191: 'FinalizerDaemon', 3192: 'FinalizerWatchd', 3193: 'HeapTrimmerDaem', 3194: 'GCDaemon', 3195: 'Binder_1', 3196: 'Binder_2', 3199: 'WifiManager', 3206: 'WifiManager', 3208: 'NanoHttpd Main ', 3210: 'THREAD_POOL_HAN', 3212: 'tencent.mm:push', 3216: 'FileObserver', 3217: 'com.baidu.searchbox:bdservice_v1', 3220: 'Heap thread poo', 3221: 'Heap thread poo', 3223: 'Heap thread poo', 3226: 'Signal Catcher', 3228: 'JDWP', 3229: 'ReferenceQueueD', 3230: 'FinalizerDaemon', 3231: 'FinalizerWatchd', 3233: 'HeapTrimmerDaem', 3234: 'GCDaemon', 3235: 'Binder_1', 3236: 'Binder_2', 3238: 'tencent.mm:push', 3239: 'default', 3240: 'WifiManager', 3257: 'pool-3-thread-1', 3260: 'com.baidu.searchbox:bdmoservice', 3264: 'Heap thread poo', 3265: 'Heap thread poo', 3266: 'Heap thread poo', 3269: 'Signal Catcher', 3270: 'JDWP', 3271: 'ReferenceQueueD', 3272: 'FinalizerDaemon', 3273: 'FinalizerWatchd', 3274: 'HeapTrimmerDaem', 3275: 'GCDaemon', 3276: 'Binder_1', 3277: 'Binder_2', 3303: 'AsyncTask #1', 3304: 'AsyncTask #2', 11634: 'kworker/u:0', 13754: 'Heap thread poo', 3518: 'PushService-Pus', 3519: 'PushService-Pus', 11779: 'kworker/0:3H', 3633: 'com.tencent.portfolio:push', 3636: 'Heap thread poo', 3638: 'Heap thread poo', 3639: 'Heap thread poo', 3642: 'Signal Catcher', 3643: 'JDWP', 3645: 'ReferenceQueueD', 3646: 'FinalizerDaemon', 3647: 'FinalizerWatchd', 3648: 'HeapTrimmerDaem', 3649: 'GCDaemon', 3650: 'Binder_1', 3651: 'Binder_2', 3661: 'TPPluginCenter ', 3663: 'pool-1-thread-1', 3665: 'MidService', 13761: 'HeapTrimmerDaem', 3667: 'pool-2-thread-1', 3668: 'push core threa', 3670: '.ProcessManager', 3672: 'Binder_3', 11865: 'pool-37-thread-', 3674: 'pool-4-thread-1', 3675: 'pool-3-thread-1', 3680: 'WifiManager', 13762: 'GCDaemon', 11928: 'kworker/0:1', 3738: 'NanoHttpd Main ', 12039: 'pool-1-thread-1', 12193: 'Background Bloc', 12207: 'User-Facing Blo', 12211: 'WorkerPool/1221', 12232: 'Background Non-', 12235: 'Background Bloc', 12236: 'Background Bloc', 12237: 'Background Non-', 4135: 'AsyncTask #2', 4159: 'AsyncTask #3', 4180: 'AsyncTask #2', 4184: 'AsyncTask #4', 12988: 'Binder_2', 4210: 'AsyncTask #5', 4221: 'AsyncTask #3', 4223: 'AsyncTask #4', 4226: 'AsyncTask #4', 4227: 'com.android.bluetooth', 4231: 'Heap thread poo', 4233: 'Heap thread poo', 4235: 'Heap thread poo', 4236: 'Signal Catcher', 4237: 'JDWP', 4238: 'ReferenceQueueD', 12431: 'kworker/u:2', 4240: 'FinalizerWatchd', 4241: 'HeapTrimmerDaem', 4242: 'GCDaemon', 4243: 'Binder_1', 4244: 'Binder_2', 12448: 'OkHttp Connecti', 12449: 'OkHttp Connecti', 4265: 'UsbDebuggingMan', 12484: 'AsyncTask #4', 13782: 'Heap thread poo', 4308: 'BluetoothAdapte', 4309: 'droid.bluetooth', 4311: 'bluedroid wake/', 4312: 'BT Service Call', 4315: 'BondStateMachin', 4316: 'Binder_3', 4317: 'Binder_4', 4318: 'HeadsetStateMac', 4320: 'BluetoothAvrcpH', 4321: 'A2dpStateMachin', 4322: 'A2DP-MEDIA', 4323: 'uipc-main', 4324: 'BluetoothHdpHan', 4325: 'droid.bluetooth', 4326: 'BluetoothAdvert', 4327: 'BluetoothScanMa', 4331: 'bluedroid wake/', 4333: 'bt_hc_worker', 4338: 'userial_read', 13784: 'Signal Catcher', 12576: 'AsyncTask #5', 13785: 'JDWP', 4390: 'pool-13-thread-', 4391: 'pool-18-thread-', 4392: 'pool-11-thread-', 4394: 'pool-25-thread-', 4395: 'pool-25-thread-', 4396: 'pool-25-thread-', 4397: 'pool-25-thread-', 4398: 'pool-14-thread-', 13786: 'ReferenceQueueD', 13787: 'FinalizerDaemon', 13788: 'FinalizerWatchd', 4478: 'BT Service Call', 4479: 'bt_hc_worker', 4480: 'Binder_4', 4481: 'bt_hc_worker', 4482: 'BluetoothMapAcc', 13789: 'HeapTrimmerDaem', 4521: 'MediaTracker bu', 14407: 'Binder_3', 6217: 'HeapTrimmerDaem', 4541: 'RefQueueWorker@', 13791: 'Binder_1', 4571: 'Stk App Service', 4597: 'com.qualcomm.qcrilmsgtunnel', 4603: 'Heap thread poo', 4604: 'Heap thread poo', 4605: 'Heap thread poo', 4606: 'Signal Catcher', 4607: 'JDWP', 4608: 'ReferenceQueueD', 4609: 'FinalizerDaemon', 4610: 'FinalizerWatchd', 4611: 'HeapTrimmerDaem', 4612: 'GCDaemon', 4613: 'Binder_1', 4614: 'Binder_2', 4615: 'QcRilReceiver', 4616: 'QcRilSender', 4735: 'pool-8-thread-1', 4749: 'CopresenceEvent', 4766: 'Icing-Pool-0', 4770: 'Binder_6', 4771: 'Icing-Worker-0', 12971: 'com.life360.android.safetymapd:service', 12977: 'Heap thread poo', 12978: 'Heap thread poo', 12979: 'Heap thread poo', 12980: 'Signal Catcher', 12981: 'JDWP', 12982: 'ReferenceQueueD', 12983: 'FinalizerDaemon', 12984: 'FinalizerWatchd', 12985: 'HeapTrimmerDaem', 12986: 'GCDaemon', 12987: 'Binder_1', 4796: 'Thread-200', 4797: 'Thread-201', 4798: 'Thread-202', 4799: 'Thread-203', 4800: 'Thread-204', 9001: 'Gservices', 13071: 'com.xianguo.tingguo', 13075: 'Heap thread poo', 13076: 'Heap thread poo', 13077: 'Heap thread poo', 13080: 'Signal Catcher', 13081: 'JDWP', 13082: 'ReferenceQueueD', 13083: 'FinalizerDaemon', 13084: 'FinalizerWatchd', 13085: 'HeapTrimmerDaem', 13086: 'GCDaemon', 13087: 'Binder_1', 13088: 'Binder_2', 13090: 'SoundPool', 13091: 'SoundPoolThread', 13099: 'WifiManager', 4917: 'Binder_3', 9024: 'GamesProviderWo', 13276: 'WifiManager', 13345: 'com.google.android.apps.photos', 13351: 'Heap thread poo', 13352: 'Heap thread poo', 13353: 'Heap thread poo', 13354: 'Signal Catcher', 13355: 'JDWP', 13356: 'ReferenceQueueD', 13357: 'FinalizerDaemon', 13358: 'FinalizerWatchd', 13359: 'HeapTrimmerDaem', 13360: 'GCDaemon', 13361: 'Binder_1', 13362: 'Binder_2', 5239: '.iqiyipushserviceGlobal', 5242: 'Heap thread poo', 5244: 'Heap thread poo', 5245: 'Heap thread poo', 5248: 'Signal Catcher', 5249: 'JDWP', 5250: 'ReferenceQueueD', 5251: 'FinalizerDaemon', 5252: 'FinalizerWatchd', 5253: 'HeapTrimmerDaem', 5254: 'GCDaemon', 5255: 'Binder_1', 5257: 'Binder_2', 5280: 'RefQueueWorker@', 5281: 'Binder_3', 5285: '.iqiyipushserviceGlobal', 13491: 'com.google.android.apps.plus', 13493: 'Heap thread poo', 13494: 'Heap thread poo', 13495: 'Heap thread poo', 13497: 'Signal Catcher', 13499: 'JDWP', 13502: 'ReferenceQueueD', 13503: 'FinalizerDaemon', 13504: 'FinalizerWatchd', 13505: 'HeapTrimmerDaem', 13506: 'GCDaemon', 13507: 'Binder_1', 13508: 'Binder_2', 13512: 'picasa-photo-pr', 5323: 'com.strava', 13516: 'android.process.acore', 5327: 'Heap thread poo', 13520: 'Heap thread poo', 5329: 'Heap thread poo', 13522: 'Heap thread poo', 5332: 'Signal Catcher', 5333: 'JDWP', 5334: 'ReferenceQueueD', 13527: 'ReferenceQueueD', 5336: 'FinalizerWatchd', 13529: 'FinalizerDaemon', 13530: 'FinalizerWatchd', 13531: 'HeapTrimmerDaem', 13532: 'GCDaemon', 13533: 'Binder_1', 13534: 'Binder_2', 13536: 'ContactsProvide', 13537: 'CallLogProvider', 13538: 'pool-2-thread-1', 5347: 'Queue', 5348: 'Queue', 5349: 'Queue', 5352: 'Crashlytics Exc', 5354: 'pool-3-thread-1', 5361: 'Micro Client Co', 5362: 'Micro Client Co', 5363: 'Micro Client Ca', 5364: 'Thread-584', 5365: 'Thread-585', 5366: 'Thread-586', 5367: 'pool-4-thread-1', 5369: 'Crashlytics Tra', 5372: 'Thread-593', 5373: 'Thread-594', 5374: 'Thread-595', 5375: 'Thread-596', 5376: 'pool-6-thread-1', 5377: 'Thread-598', 5378: 'Thread-599', 5379: 'Thread-600', 5381: 'Thread-602', 5383: 'Thread #1', 5384: 'AsyncTask #1', 5387: 'Thread-605', 5388: 'Thread-606', 5389: 'Thread-607', 5390: 'Thread-608', 5391: 'Thread-609', 5393: 'eNowAuthService', 5394: 'Thread #2', 5395: 'com.pandora.android', 5397: 'Heap thread poo', 5398: 'Heap thread poo', 5399: 'Heap thread poo', 5401: 'Signal Catcher', 5403: 'JDWP', 5407: 'ReferenceQueueD', 5408: 'FinalizerDaemon', 5409: 'FinalizerWatchd', 5410: 'HeapTrimmerDaem', 5411: 'GCDaemon', 5414: 'Binder_1', 5416: 'Binder_2', 13613: 'com.sohu.inputmethod.sogou:classic', 5422: 'Crashlytics Exc', 13616: 'Heap thread poo', 13617: 'Heap thread poo', 13618: 'Heap thread poo', 13620: 'Signal Catcher', 5429: 'pool-2-thread-1', 5430: 'AsyncTask #1', 13623: 'JDWP', 13624: 'ReferenceQueueD', 13625: 'FinalizerDaemon', 13626: 'FinalizerWatchd', 13627: 'HeapTrimmerDaem', 13628: 'GCDaemon', 5437: 'Crashlytics Tra', 13630: 'Binder_2', 5439: 'AsyncTask #2', 5440: 'pool-4-thread-1', 5443: 'PurchasingManag', 13636: 'Thread-1444', 5445: 'AsyncTask #3', 13638: 'Thread-1446', 13639: 'Thread-1447', 13641: 'WifiManager', 13647: 'com.sohu.inputmethod.sogou', 13651: 'Heap thread poo', 13652: 'Heap thread poo', 13653: 'Heap thread poo', 13656: 'Signal Catcher', 13657: 'JDWP', 13658: 'ReferenceQueueD', 13659: 'FinalizerDaemon', 5468: 'Okio Watchdog', 13661: 'HeapTrimmerDaem', 13662: 'GCDaemon', 13663: 'Binder_1', 13664: 'Binder_2', 5474: 'tunein.player', 5479: 'Heap thread poo', 5480: 'Heap thread poo', 5481: 'Heap thread poo', 5483: 'Signal Catcher', 5484: 'JDWP', 5485: 'ReferenceQueueD', 5486: 'FinalizerDaemon', 5487: 'FinalizerWatchd', 5488: 'HeapTrimmerDaem', 5489: 'GCDaemon', 5490: 'Binder_1', 13683: 'FinalizerDaemon', 5492: 'Binder_2', 13685: 'HeapTrimmerDaem', 13686: 'GCDaemon', 13687: 'Binder_1', 13688: 'Binder_2', 5498: 'Thread #3', 13691: 'pool-1-thread-2', 13692: 'pool-1-thread-3', 13694: 'Timer-0', 5503: 'geHandlerThread', 5504: 'GAThread', 5507: 'Crashlytics Exc', 13701: 'Heap thread poo', 5510: 'AsyncTask #1', 13703: 'Heap thread poo', 13704: 'Signal Catcher', 13705: 'JDWP', 13706: 'ReferenceQueueD', 5515: 'Crashlytics Tra', 13708: 'FinalizerWatchd', 13709: 'HeapTrimmerDaem', 5518: 'AsyncTask #2', 5519: 'com.dropbox.android', 13712: 'Binder_2', 13713: 'Binder_3', 13715: 'com.rolocule.motiontennis', 5525: 'Heap thread poo', 5526: 'Heap thread poo', 5527: 'Heap thread poo', 5528: 'Signal Catcher', 5529: 'JDWP', 5530: 'ReferenceQueueD', 5531: 'FinalizerDaemon', 5532: 'FinalizerWatchd', 5533: 'HeapTrimmerDaem', 5534: 'GCDaemon', 5535: 'Binder_1', 5536: 'Binder_2', 13731: 'ReferenceQueueD', 13732: 'FinalizerDaemon', 13733: 'FinalizerWatchd', 13734: 'HeapTrimmerDaem', 13735: 'GCDaemon', 13736: 'Binder_1', 13737: 'Binder_2', 13747: 'com.google.android.apps.chrome', 13751: 'Heap thread poo', 13752: 'Heap thread poo', 5562: 'Dropbox log upl', 5563: 'gandalf updater', 13756: 'Signal Catcher', 13757: 'JDWP', 13758: 'ReferenceQueueD', 13759: 'FinalizerDaemon', 5568: 'pool-10-thread-', 5569: 'DbxFileObserver', 5570: 'LocalThumbManag', 13763: 'Binder_1', 13764: 'Binder_2', 5574: 'local AsyncTask', 5575: 'remote AsyncTas', 5576: 'local AsyncTask', 5577: 'remote AsyncTas', 5578: 'Dropbox notif o', 5579: 'Dropbox notif s', 5580: 'Picasso-Stats', 5581: 'Picasso-Dispatc', 5582: 'Picasso-refQueu', 5583: 'gandalf updater', 5587: 'AcceptThreadSec', 13780: 'Heap thread poo', 13781: 'Heap thread poo', 5590: 'Binder_3', 13783: 'pool-1-thread-1', 5592: 'DbxFileObserver', 5593: 'dbxpool-34:r-th', 5594: 'dbxpool-32:au-t', 5595: 'dbxpool-38:a-th', 5596: 'Timer-0', 5597: 'dbxpool-6:a-thr', 13790: 'GCDaemon', 5599: 'Timer-1', 13792: 'Binder_2', 13796: 'rotating_file-t', 13801: 'com.android.vending', 5610: 'AsyncTask #4', 13807: 'Heap thread poo', 13808: 'Heap thread poo', 13809: 'Heap thread poo', 13811: 'Signal Catcher', 13812: 'JDWP', 13813: 'ReferenceQueueD', 13814: 'FinalizerDaemon', 13815: 'FinalizerWatchd', 13816: 'HeapTrimmerDaem', 13817: 'GCDaemon', 13818: 'Binder_1', 5627: 'Binder_3', 5633: 'Binder_5', 13828: 'Gservices', 5638: 'Timer-0', 13833: 'pool-1-thread-1', 13834: 'RefQueueWorker@', 13837: 'RefQueueWorker@', 13838: 'Thread-1482', 13839: 'Thread-1483', 13840: 'Thread-1484', 13843: 'download-manage', 13844: 'NetworkQualityQ', 13845: 'RefQueueWorker@', 13846: 'Thread-1489', 13847: 'Thread-1490', 13848: 'Thread-1491', 13849: 'Thread-1492', 13850: 'Thread-1493', 13851: 'PlayEventLogger', 13852: 'tentative-gc-ru', 13853: 'com.google.android.gms:car', 13856: 'Heap thread poo', 13857: 'Heap thread poo', 13858: 'Heap thread poo', 13862: 'libraries-threa', 13863: 'Signal Catcher', 13864: 'JDWP', 13865: 'ReferenceQueueD', 13866: 'FinalizerDaemon', 13867: 'FinalizerWatchd', 13868: 'HeapTrimmerDaem', 13869: 'GCDaemon', 13870: 'Binder_1', 13871: 'Binder_2', 13872: 'AsyncTask #1', 13873: 'Gservices', 13876: 'AsyncTask #2', 13877: 'AsyncTask #3', 13878: 'PlayEventLogger', 5688: 'com.devuni.flashlight:remote', 13881: 'Gservices', 5693: 'Heap thread poo', 5694: 'Heap thread poo', 5695: 'Heap thread poo', 5697: 'Signal Catcher', 5698: 'JDWP', 5699: 'ReferenceQueueD', 5700: 'FinalizerDaemon', 5701: 'FinalizerWatchd', 5702: 'HeapTrimmerDaem', 5703: 'GCDaemon', 5704: 'Binder_1', 5705: 'Binder_2', 13898: 'FinalizerWatchd', 13899: 'HeapTrimmerDaem', 13900: 'GCDaemon', 13901: 'Binder_1', 13902: 'Binder_2', 13903: 'Gservices', 13904: 'Binder_3', 13905: 'Binder_3', 5717: 'GL updater', 5718: 'RefQueueWorker@', 5719: 'AsyncTask #5', 13913: 'Binder_3', 13677: 'Heap thread poo', 5750: 'Thread-625', 5783: 'WifiManager', 5793: 'Binder_4', 5816: 'Binder_4', 5818: 'Binder_3', 14061: 'kworker/u:3', 14136: 'kworker/0:0H', 14356: 'com.google.android.apps.gcs', 14357: 'ReferenceQueueD', 14358: 'FinalizerDaemon', 14359: 'FinalizerWatchd', 14360: 'HeapTrimmerDaem', 14361: 'GCDaemon', 14362: 'Heap thread poo', 14363: 'Heap thread poo', 14364: 'Heap thread poo', 14365: 'Signal Catcher', 14366: 'JDWP', 14367: 'ReferenceQueueD', 14368: 'FinalizerDaemon', 14369: 'FinalizerWatchd', 14370: 'HeapTrimmerDaem', 14371: 'GCDaemon', 14372: 'Binder_1', 14373: 'Binder_2', 14375: 'Gservices', 14376: 'RefQueueWorker@', 14377: 'Thread-1495', 14378: 'Thread-1496', 14379: 'Thread-1497', 14380: 'Thread-1498', 14381: 'Thread-1499', 6201: 'pool-1-thread-1', 6202: 'com.tencent.mm', 6206: 'Heap thread poo', 6207: 'Heap thread poo', 6208: 'Heap thread poo', 14401: 'Thread-233', 6211: 'Signal Catcher', 6212: 'JDWP', 6213: 'ReferenceQueueD', 6214: 'FinalizerDaemon', 6215: 'FinalizerWatchd', 14409: 'OkHttp Connecti', 6218: 'GCDaemon', 6220: 'Binder_1', 6221: 'Binder_2', 6236: 'THREAD_POOL_HAN', 6237: 'com.tencent.mm', 6239: 'com.tencent.mm', 6240: 'MonitorHandlerT', 6241: '.ProcessManager', 6243: 'Binder_3', 6245: 'default', 6246: 'com.tencent.mm', 6247: 'MMHandlerThread', 14444: '/system/bin/sh', 14445: 'adbd', 14448: 'ps', 6257: 'Icing-Pool-1', 6258: 'Icing-Pool-2', 6259: 'Icing-Pool-3', 6269: 'MM_Thread_Pool_', 6270: 'MM_Thread_Pool_', 6271: 'MM_Thread_Pool_', 6272: 'MM_Thread_Pool_', 6273: 'MM_Thread_Pool_', 6274: 'MM_Thread_Pool_', 6276: 'ExdeviceHandler', 6277: 'MM_Thread_Pool_', 6279: 'RWCache_timeout', 6280: 'RWCache_timeout', 6282: 'MM_Thread_Pool_', 6284: 'downloadStateCh', 6288: 'WifiManager', 6289: 'refresh Notific', 6292: 'MM_Thread_Pool_', 6293: 'MM_Thread_Pool_', 6294: 'MM_Thread_Pool_', 6295: 'SearchDaemon', 6303: 'Binder_4', 6313: 'pool-2-thread-1', 6326: 'AsyncTask #5', 6373: 'RWCache_timeout', 6408: 'h', 6459: 'BluetoothPbapAc', 6473: 'pool-1-thread-1', 6477: 'BtOppRfcommList', 13682: 'ReferenceQueueD', 6481: 'AsyncTask #5', 6672: 'AsyncTask #2', 6673: 'pool-22-thread-', 6709: 'Binder_B', 6740: 'Binder_4', 6752: 'AsyncTask #3', 6917: 'Binder_6', 7091: 'Binder_5', 7150: 'default', 7173: 'Binder_3', 7196: 'UlrDispatchingS', 7230: 'default', 7231: 'MMHandlerThread', 7260: 'Thread-174', 7261: 'Thread-175', 7262: 'Thread-176', 7263: 'Thread-177', 7264: 'Thread-178', 13885: 'com.google.android.gms.wearable', 5328: 'Heap thread poo', 13521: 'Heap thread poo', 13525: 'Signal Catcher', 13526: 'JDWP', 5335: 'FinalizerDaemon', 13528: 'iu-sync-manager', 5337: 'HeapTrimmerDaem', 5338: 'GCDaemon', 5339: 'Binder_1', 5340: 'Binder_2', 5345: 'Queue', 5346: 'Queue', 7557: 'Binder_6', 13891: 'Heap thread poo', 7586: 'pool-4-thread-1', 13895: 'JDWP', 13896: 'ReferenceQueueD', 13897: 'FinalizerDaemon', 13629: 'Binder_1', 13635: 'Thread-1443', 5444: 'BluetoothServer', 13637: 'Thread-1445', 5446: 'AsyncTask #4'}
\ No newline at end of file
diff --git a/update.py b/update.py
index abcad9e..8a66066 100755
--- a/update.py
+++ b/update.py
@@ -28,17 +28,21 @@
   if p.wait() != 0:
     print 'Failed to checkout source from upstream git.'
     sys.exit(1)
-  
-  shutil.rmtree(os.path.join(trace_viewer_dir, '.git'), True)
 
+  trace_viewer_git_dir = os.path.join(trace_viewer_dir, '.git')
   # Update the UPSTREAM_REVISION file
   git_args = ['git', 'rev-parse', 'HEAD']
-  p = subprocess.Popen(git_args, stdout=subprocess.PIPE, cwd=trace_viewer_dir)
+  p = subprocess.Popen(git_args,
+                       stdout=subprocess.PIPE,
+                       cwd=trace_viewer_dir,
+                       env={"GIT_DIR":trace_viewer_git_dir})
   out, err = p.communicate()
   if p.wait() != 0:
     print 'Failed to get revision.'
     sys.exit(1)
 
+  shutil.rmtree(trace_viewer_git_dir, True)
+
   rev = out.strip()
   with open('UPSTREAM_REVISION', 'wt') as f:
     f.write(rev + '\n')
diff --git a/util.py b/util.py
new file mode 100644
index 0000000..5fb61a8
--- /dev/null
+++ b/util.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2015 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 optparse
+import subprocess
+import sys
+
+
+class OptionParserIgnoreErrors(optparse.OptionParser):
+  """Wrapper for OptionParser that ignores errors and produces no output."""
+
+  def error(self, msg):
+    pass
+
+  def exit(self):
+    pass
+
+  def print_usage(self):
+    pass
+
+  def print_help(self):
+    pass
+
+  def print_version(self):
+    pass
+
+
+def add_adb_serial(adb_command, device_serial):
+  if device_serial is not None:
+    adb_command.insert(1, device_serial)
+    adb_command.insert(1, '-s')
+
+
+def construct_adb_shell_command(shell_args, device_serial):
+  adb_command = ['adb', 'shell', ' '.join(shell_args)]
+  add_adb_serial(adb_command, device_serial)
+  return adb_command
+
+
+def run_adb_shell(shell_args, device_serial):
+  """Runs "adb shell" with the given arguments.
+
+  Args:
+    shell_args: array of arguments to pass to adb shell.
+    device_serial: if not empty, will add the appropriate command-line
+        parameters so that adb targets the given device.
+  Returns:
+    A tuple containing the adb output (stdout & stderr) and the return code
+    from adb.  Will exit if adb fails to start.
+  """
+  adb_command = construct_adb_shell_command(shell_args, device_serial)
+
+  adb_output = []
+  adb_return_code = 0
+  try:
+    adb_output = subprocess.check_output(adb_command, stderr=subprocess.STDOUT,
+                                         shell=False, universal_newlines=True)
+  except OSError as error:
+    # This usually means that the adb executable was not found in the path.
+    print >> sys.stderr, ('\nThe command "%s" failed with the following error:'
+                          % ' '.join(adb_command))
+    print >> sys.stderr, '    %s\n' % str(error)
+    print >> sys.stderr, 'Is adb in your path?'
+    sys.exit(1)
+  except subprocess.CalledProcessError as error:
+    # The process exited with an error.
+    adb_return_code = error.returncode
+    adb_output = error.output
+
+  return (adb_output, adb_return_code)
+
+
+def get_device_sdk_version():
+  """Uses adb to attempt to determine the SDK version of a running device."""
+
+  getprop_args = ['getprop', 'ro.build.version.sdk']
+
+  # get_device_sdk_version() is called before we even parse our command-line
+  # args.  Therefore, parse just the device serial number part of the
+  # command-line so we can send the adb command to the correct device.
+  parser = OptionParserIgnoreErrors()
+  parser.add_option('-e', '--serial', dest='device_serial', type='string')
+  options, unused_args = parser.parse_args()
+
+  success = False
+
+  adb_output, adb_return_code = run_adb_shell(getprop_args,
+                                              options.device_serial)
+
+  if adb_return_code == 0:
+    # ADB may print output other than the version number (e.g. it chould
+    # print a message about starting the ADB server).
+    # Break the ADB output into white-space delimited segments.
+    parsed_output = str.split(adb_output)
+    if parsed_output:
+      # Assume that the version number is the last thing printed by ADB.
+      version_string = parsed_output[-1]
+      if version_string:
+        try:
+          # Try to convert the text into an integer.
+          version = int(version_string)
+        except ValueError:
+          version = -1
+        else:
+          success = True
+
+  if not success:
+    print >> sys.stderr, (
+        '\nThe command "%s" failed with the following message:'
+        % ' '.join(getprop_args))
+    print >> sys.stderr, adb_output
+    sys.exit(1)
+
+  return version