Chromium Code Reviews| Index: systrace/systrace/tracing_agents/atrace_agent.py |
| diff --git a/systrace/systrace/agents/atrace_agent.py b/systrace/systrace/tracing_agents/atrace_agent.py |
| similarity index 43% |
| rename from systrace/systrace/agents/atrace_agent.py |
| rename to systrace/systrace/tracing_agents/atrace_agent.py |
| index 3125821b22a743c0a58ad2f35620a4aead772037..72cbe4223cf2edeb3cf892d7e5210f3c47ece7fd 100644 |
| --- a/systrace/systrace/agents/atrace_agent.py |
| +++ b/systrace/systrace/tracing_agents/atrace_agent.py |
| @@ -2,16 +2,18 @@ |
| # 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 |
| -from systrace import systrace_agent |
| +from devil.android import device_utils |
| from systrace import util |
| +from systrace.tracing_agents import TracingAgent, TraceResults |
| + |
| +#TODO(alexandermont): add "from py_trace_event import trace_time" |
| +#after persistent shell CL lands |
| # Text that ADB sends, but does not need to be displayed to the user. |
| ADB_IGNORE_REGEXP = r'^capturing trace\.\.\. done|^capturing trace\.\.\.' |
| @@ -50,65 +52,95 @@ LEGACY_TRACE_TAG_BITS = ( |
| ('camera', 1 << 10), |
| ) |
| +def list_categories(options): |
| + devutils = device_utils.DeviceUtils(options.device_serial) |
| + for line in devutils.RunShellCommand(LIST_CATEGORIES_ARGS): |
| + print line |
| def try_create_agent(options, categories): |
| + """Create an Atrace agent. |
| + |
| + Args: |
| + options: Command line options. |
| + categories: Trace categories to capture. |
| + """ |
| if options.target != 'android': |
| return False |
| if options.from_file is not None: |
| - return AtraceAgent(options, categories) |
| + return False |
| + # Check for supported versions |
| device_sdk_version = util.get_device_sdk_version() |
| - if device_sdk_version >= 18: |
| - if options.boot: |
| - # atrace --async_stop, which is used by BootAgent, does not work properly |
| - # on the device SDK version 22 or before. |
| - if device_sdk_version <= 22: |
| - print >> sys.stderr, ('--boot option does not work on the device SDK ' |
| - 'version 22 or before.\nYour device SDK version ' |
| - 'is %d.' % device_sdk_version) |
| - sys.exit(1) |
| - return BootAgent(options, categories) |
| - else: |
| - return AtraceAgent(options, categories) |
| - elif device_sdk_version >= 16: |
| - return AtraceLegacyAgent(options, categories) |
| + if device_sdk_version <= 17: |
| + print >> sys.stderr, ('Device SDK versions <= 17 not supported.\n' |
| + 'Your device SDK version is %d.' % device_sdk_version) |
| + elif device_sdk_version <= 22 and options.boot: |
| + print >> sys.stderr, ( |
| + 'Device SDK versions <= 22 do not support boot tracing.\n' |
| + 'Your device SDK version is %d.' % device_sdk_version) |
| + if options.boot: |
| + return BootAgent(options, categories) |
| + else: |
| + return AtraceAgent(options, categories) |
| -class AtraceAgent(systrace_agent.SystraceAgent): |
| +class AtraceAgent(TracingAgent): |
| def __init__(self, options, categories): |
|
Zhen Wang
2016/03/29 18:53:40
Do not pass in options and categories. Only device
alexandermont
2016/03/30 01:04:24
Done
|
| super(AtraceAgent, self).__init__(options, categories) |
| - self._expect_trace = False |
| - self._adb = None |
| self._trace_data = None |
| self._tracer_args = None |
| + self._adb = None |
| + self._collection_process = None |
| + self._device_utils = device_utils.DeviceUtils(self._options.device_serial) |
| if not self._categories: |
| self._categories = get_default_categories(self._options.device_serial) |
| - def start(self): |
| - self._tracer_args = self._construct_trace_command() |
| - |
| - self._adb = do_popen(self._tracer_args) |
| + def _StartAgentTracing_func(self): |
| + """Starts tracing.""" |
| + self._tracer_args = self._construct_trace_args() |
| + self._device_utils.RunShellCommand(self._tracer_args + ['--async_start']) |
| + return True |
| - def collect_result(self): |
| + def _collect_and_preprocess(self): |
| + """Collects and preprocesses trace data.""" |
| trace_data = self._collect_trace_data() |
| - if self._expect_trace: |
| - self._trace_data = self._preprocess_trace_data(trace_data) |
| + self._trace_data = self._preprocess_trace_data(trace_data) |
| - def expect_trace(self): |
| - return self._expect_trace |
| + def _StopAgentTracing_func(self): |
| + """Stops tracing and collect results asynchronously. |
| - def get_trace_data(self): |
| - return self._trace_data |
| + Creates a new process that stops tracing, collects results, and |
| + records them in self._trace_data. Returns immediately, without |
| + waiting for data collection to finish. |
| + """ |
| + self._collection_process = threading.Thread( |
| + target=self._collect_and_preprocess) |
| + self._collection_process.start() |
| + return True |
| + |
| + def _GetResults_func(self): |
| + """Returns trace results.""" |
| + self._collection_process.join() |
| + self._collection_process = None |
| + return TraceResults('systemTraceEvents', self._trace_data) |
| + |
| + def SupportsExplicitClockSync(self): |
| + """Returns whether or not this supports explicit clock sync.""" |
| + return False |
| + #TODO(alexandermont): return True after persistent shell CL lands |
| - def get_class_name(self): |
| - return 'trace-data' |
| + def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback): |
| + """Records a clock sync marker. |
| - def _construct_list_categories_command(self): |
| - return util.construct_adb_shell_command( |
| - LIST_CATEGORIES_ARGS, self._options.device_serial) |
| + Args: |
| + sync_id: ID string for clock sync marker. |
| + """ |
| + return |
| + #TODO(alexandermont): put the functoin back after persistent shell CL lands |
| - def _construct_extra_trace_command(self): |
| + def _construct_extra_trace_args(self): |
| + """Construct extra arguments (-a and -k) for trace command.""" |
| extra_args = [] |
| if self._options.app_name is not None: |
| extra_args.extend(['-a', self._options.app_name]) |
| @@ -119,161 +151,59 @@ class AtraceAgent(systrace_agent.SystraceAgent): |
| extra_args.extend(self._categories) |
| return extra_args |
| - def _construct_trace_command(self): |
| + def _construct_trace_args(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 |
| + the second element is a boolean which will be true if the command 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)]) |
| - elif 'sched' in self._categories: |
| - # 'sched' is a high-volume tag, double the default buffer size |
| - # to accommodate that |
| - atrace_args.extend(['-b', '4096']) |
| - extra_args = self._construct_extra_trace_command() |
| - atrace_args.extend(extra_args) |
| - |
| - tracer_args = util.construct_adb_shell_command( |
| - atrace_args, self._options.device_serial) |
| - |
| - return tracer_args |
| + atrace_args = ATRACE_BASE_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(self._tracer_args), self._adb.returncode)) |
| - sys.exit(1) |
| + if self._options.compress_trace_data: |
| + atrace_args.extend(['-z']) |
| - return trace_data |
| + 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)]) |
| + elif 'sched' in self._categories: |
| + # 'sched' is a high-volume tag, double the default buffer size |
| + # to accommodate that |
| + atrace_args.extend(['-b', '4096']) |
| + extra_args = self._construct_extra_trace_args() |
| + atrace_args.extend(extra_args) |
| + |
| + return atrace_args |
| + |
| + def _dump_trace(self): |
| + """Dumps the atrace buffer and returns the dumped buffer.""" |
| + dump_cmd = self._tracer_args + ['--async_dump'] |
| + return self._device_utils.RunShellCommand(dump_cmd, split_lines=False) |
| + |
| + def _stop_trace(self): |
| + """Stops the atrace trace. |
| + |
| + Tries to stop the atrace trace asynchronously and uses the fallback |
| + method of running a zero-length synchronous trace if that fails. |
| + """ |
| + self._device_utils.RunShellCommand(self._tracer_args + ['--async_stop']) |
| + cmd = ['cat', '/sys/kernel/debug/tracing/tracing_on'] |
| + trace_on = int(self._device_utils.RunShellCommand(cmd)[0]) |
| + if trace_on: |
| + self._device_utils.RunShellCommand(self._tracer_args + ['-t 0']) |
| + |
| + def _collect_trace_data(self): |
| + """Reads the output from atrace and stops the trace.""" |
| + result = self._dump_trace() |
| + data_start = re.search(TRACE_START_REGEXP, result).end(0) |
| + output = re.sub(ADB_IGNORE_REGEXP, '', result[data_start:]) |
| + self._stop_trace() |
| + return output |
| def _preprocess_trace_data(self, trace_data): |
| """Performs various processing on atrace data. |
| @@ -283,7 +213,6 @@ class AtraceAgent(systrace_agent.SystraceAgent): |
| Returns: |
| The processed trace data. |
| """ |
| - trace_data = ''.join(trace_data) |
| if trace_data: |
| trace_data = strip_and_decompress_trace(trace_data) |
| @@ -314,178 +243,8 @@ class AtraceAgent(systrace_agent.SystraceAgent): |
| 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) |
| - if return_code != 0: |
| - print >> sys.stderr, ( |
| - '\nThe command "%s" failed with the following message:' |
| - % ' '.join(SHELL_ARGS)) |
| - print >> sys.stderr, str(output) |
| - sys.exit(1) |
| - |
| - flags = 0 |
| - try: |
| - if output.startswith('0x'): |
| - flags = int(output, 16) |
| - elif output.startswith('0'): |
| - flags = int(output, 8) |
| - else: |
| - flags = int(output) |
| - except ValueError: |
| - 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 BootAgent(AtraceAgent): |
| - """AtraceAgent that specializes in tracing the boot sequence.""" |
| - |
| - def __init__(self, options, categories): |
| - super(BootAgent, self).__init__(options, categories) |
| - |
| - def start(self): |
| - try: |
| - setup_args = self._construct_setup_command() |
| - try: |
| - subprocess.check_call(setup_args) |
| - print 'Hit Ctrl+C once the device has booted up.' |
| - while True: |
| - time.sleep(1) |
| - except KeyboardInterrupt: |
| - pass |
| - tracer_args = self._construct_trace_command() |
| - 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 _construct_setup_command(self): |
| - echo_args = ['echo'] + self._categories + ['>', BOOTTRACE_CATEGORIES] |
| - setprop_args = ['setprop', BOOTTRACE_PROP, '1'] |
| - reboot_args = ['reboot'] |
| - return util.construct_adb_shell_command( |
| - echo_args + ['&&'] + setprop_args + ['&&'] + reboot_args, |
| - self._options.device_serial) |
| - |
| - def _construct_trace_command(self): |
| - self._expect_trace = True |
| - atrace_args = ['atrace', '--async_stop'] |
| - setprop_args = ['setprop', BOOTTRACE_PROP, '0'] |
| - rm_args = ['rm', BOOTTRACE_CATEGORIES] |
| - return util.construct_adb_shell_command( |
| - atrace_args + ['&&'] + setprop_args + ['&&'] + rm_args, |
| - self._options.device_serial) |
| - |
| - |
| -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): |
| + """Gets the list of atrace categories available for tracing.""" |
| categories_output, return_code = util.run_adb_shell(LIST_CATEGORIES_ARGS, |
| device_serial) |
| @@ -497,20 +256,9 @@ def get_default_categories(device_serial): |
| 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_text): |
| """Removes the thread list from the given trace data. |
| + |
| Args: |
| trace_text: The text portion of the trace |
| Returns: |
| @@ -532,6 +280,7 @@ def extract_thread_list(trace_text): |
| def extract_tgids(trace_text): |
| """Removes the procfs dump from the given trace text |
| + |
| Args: |
| trace_text: The text portion of the trace |
| Returns: |
| @@ -564,7 +313,8 @@ def strip_and_decompress_trace(trace_data): |
| trace_data = trace_data.replace('\r\r\n', '\n') |
| # Skip the initial newline. |
| - trace_data = trace_data[1:] |
| + if trace_data[0] == '\n': |
| + trace_data = trace_data[1:] |
| if not trace_data.startswith(TRACE_TEXT_HEADER): |
| # No header found, so assume the data is compressed. |
| @@ -612,6 +362,7 @@ def fix_thread_names(trace_data, thread_names): |
| def fix_missing_tgids(trace_data, pid2_tgid): |
| """Replaces missing TGIDs from the trace data with those found in procfs |
| + |
| Args: |
| trace_data: the atrace data |
| Returns: |
| @@ -636,7 +387,7 @@ def fix_missing_tgids(trace_data, pid2_tgid): |
| def fix_circular_traces(out): |
| - """Fix inconsistentcies in traces due to circular buffering. |
| + """Fix inconsistencies 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 |
| @@ -671,22 +422,16 @@ def fix_circular_traces(out): |
| out = out[:end_of_header] + out[start_of_full_trace:] |
| return out |
| +def do_preprocess_adb_cmd(command, serial): |
| + """Run an ADB command for preprocessing of output. |
| -def do_popen(args): |
| - try: |
| - adb = subprocess.Popen(args, stdout=subprocess.PIPE, |
| - stderr=subprocess.PIPE) |
| - except OSError as error: |
| - print >> sys.stderr, ( |
| - 'The command "%s" failed with the following error:' % |
| - ' '.join(args)) |
| - print >> sys.stderr, ' ', error |
| - sys.exit(1) |
| - |
| - return adb |
| - |
| + Run an ADB command and get the result. This function is used |
| + for running commands relating to preprocessing of output data. |
| -def do_preprocess_adb_cmd(command, serial): |
| + Args: |
| + command: Command to run. |
| + serial: Serial number of device. |
| + """ |
| args = [command] |
| dump, ret_code = util.run_adb_shell(args, serial) |
| if ret_code != 0: |
| @@ -694,3 +439,44 @@ def do_preprocess_adb_cmd(command, serial): |
| dump = ''.join(dump) |
| return dump |
| + |
| + |
| +class BootAgent(AtraceAgent): |
| + """AtraceAgent that specializes in tracing the boot sequence.""" |
| + |
| + def __init__(self, options, categories): |
| + super(BootAgent, self).__init__(options, categories) |
| + |
| + def StartAgentTracing(self): |
| + try: |
| + setup_args = self._construct_setup_command() |
| + subprocess.check_call(setup_args) |
| + except OSError as error: |
| + print >> sys.stderr, ( |
| + 'The command "%s" failed with the following error:' % |
| + ' '.join(setup_args)) |
| + print >> sys.stderr, ' ', error |
| + sys.exit(1) |
| + |
| + def _dump_trace(self): #called by StopAgentTracing |
| + """Dumps the running trace asynchronously and returns the dumped trace.""" |
| + dump_cmd = get_boot_dump_cmd() |
| + return self._device_utils.RunShellCommand(dump_cmd, split_lines=False) |
| + |
| + def _stop_trace(self): # called by _collect_trace_data via StopAgentTracing |
| + pass # don't need to stop separately; already done in dump_trace |
| + |
| + def _construct_setup_command(self): |
| + """Constructs the command that is used to start up the boot trace.""" |
| + echo_args = ['echo'] + self._categories + ['>', BOOTTRACE_CATEGORIES] |
| + setprop_args = ['setprop', BOOTTRACE_PROP, '1'] |
| + reboot_args = ['reboot'] |
| + return util.construct_adb_shell_command( |
| + echo_args + ['&&'] + setprop_args + ['&&'] + reboot_args, |
| + self._options.device_serial) |
| + |
| +def get_boot_dump_cmd(): |
| + """Constructs the command that is used to dump the boot trace.""" |
| + return ['atrace', '--async_stop', '&&', |
| + 'setprop', BOOTTRACE_PROP, '0', '&&', |
| + 'rm', BOOTTRACE_CATEGORIES] |