# Copyright 2017 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. # Tracing agent that captures periodic per-process memory dumps and other # useful information from ProcFS like utime, stime, OOM stats, etc. import json import logging import optparse import py_utils from devil.android import device_utils from devil.android.device_errors import AdbShellCommandFailedError from py_trace_event import trace_time as trace_time_module from systrace import tracing_agents from systrace import trace_result TRACE_HEADER = 'ATRACE_PROCESS_DUMP' TRACE_RESULT_NAME = 'atraceProcessDump' HELPER_COMMAND = '/data/local/tmp/atrace_helper' HELPER_STOP_COMMAND = 'kill -TERM `pidof atrace_helper`' HELPER_DUMP_JSON = '/data/local/tmp/procdump.json' class AtraceProcessDumpAgent(tracing_agents.TracingAgent): def __init__(self): super(AtraceProcessDumpAgent, self).__init__() self._device = None self._dump = None self._clock_sync_markers = {} @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) def StartAgentTracing(self, config, timeout=None): self._device = device_utils.DeviceUtils(config.device_serial_number) cmd = [HELPER_COMMAND, '-b', '-g', '-t', str(config.dump_interval_ms), '-o', HELPER_DUMP_JSON] if config.full_dump_config: cmd += ['-m', config.full_dump_config] if config.enable_mmaps: cmd += ['-s'] self._device.RunShellCommand(cmd, check_return=True, as_root=True) return True @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) def StopAgentTracing(self, timeout=None): self._device.RunShellCommand( HELPER_STOP_COMMAND, shell=True, check_return=True, as_root=True) try: self._device.RunShellCommand(['test', '-f', HELPER_DUMP_JSON], check_return=True, as_root=True) self._dump = self._device.ReadFile(HELPER_DUMP_JSON, force_pull=True) self._device.RunShellCommand(['rm', HELPER_DUMP_JSON], check_return=True, as_root=True) except AdbShellCommandFailedError: logging.error('AtraceProcessDumpAgent failed to pull data. Check device storage.') return False return True @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT) def GetResults(self, timeout=None): result = TRACE_HEADER + '\n' + self._dump cs = json.dumps(self._clock_sync_markers) result = TRACE_HEADER + \ '\n{\"clock_sync_markers\":' + cs + ',\n\"dump\":' + self._dump + '}' return trace_result.TraceResult(TRACE_RESULT_NAME, result) def SupportsExplicitClockSync(self): return True def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback): with self._device.adb.PersistentShell(self._device.serial) as shell: ts_in_controller_domain = trace_time_module.Now() output = shell.RunCommand(HELPER_COMMAND + ' --echo-ts', close=True) ts_in_agent_domain = int(output[0][0]) self._clock_sync_markers[sync_id] = ts_in_agent_domain did_record_sync_marker_callback(ts_in_controller_domain, sync_id) class AtraceProcessDumpConfig(tracing_agents.TracingConfig): def __init__(self, enabled, device_serial_number, dump_interval_ms, full_dump_config, enable_mmaps): tracing_agents.TracingConfig.__init__(self) self.enabled = enabled self.device_serial_number = device_serial_number self.dump_interval_ms = dump_interval_ms self.full_dump_config = full_dump_config self.enable_mmaps = enable_mmaps def add_options(parser): options = optparse.OptionGroup(parser, 'Atrace process dump options') options.add_option('--process-dump', dest='process_dump_enable', default=False, action='store_true', help='Capture periodic per-process memory dumps.') options.add_option('--process-dump-interval', dest='process_dump_interval_ms', default=5000, help='Interval between memory dumps in milliseconds.') options.add_option('--process-dump-full', dest='process_dump_full_config', default=None, help='Capture full memory dumps for some processes.\n' \ 'Value: all, apps or comma-separated process names.') options.add_option('--process-dump-mmaps', dest='process_dump_mmaps', default=False, action='store_true', help='Capture VM regions and memory-mapped files.\n' \ 'It increases dump size dramatically, hence only ' \ 'has effect if --process-dump-full is a whitelist.') return options def get_config(options): can_enable = (options.target == 'android') and (not options.from_file) return AtraceProcessDumpConfig( enabled=(options.process_dump_enable and can_enable), device_serial_number=options.device_serial_number, dump_interval_ms=options.process_dump_interval_ms, full_dump_config=options.process_dump_full_config, enable_mmaps=options.process_dump_mmaps ) def try_create_agent(config): if config.enabled: return AtraceProcessDumpAgent() return None