blob: 4f5d6dc1bac2a14b93215701ecba3c38a12b1e22 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import tempfile
import shutil
import subprocess
import logging
PATH_SYSTRACE_SCRIPT = os.path.join('tools/external/chromium-trace',
'systrace.py')
EXPECTED_START_STDOUT = 'Starting tracing'
class SystraceController(object):
'''A util to start/stop systrace through shell command.
Attributes:
_android_vts_path: string, path to android-vts
_path_output: string, systrace temporally output path
_path_systrace_script: string, path to systrace controller python script
_subprocess: subprocess.Popen, a subprocess objects of systrace shell command
is_valid: boolean, whether the current environment setting for
systrace is valid
process_name: string, process name to trace
'''
def __init__(self, android_vts_path, process_name):
self._android_vts_path = android_vts_path
self._path_output = None
self._path_systrace_script = os.path.join(android_vts_path,
PATH_SYSTRACE_SCRIPT)
self.is_valid = os.path.exists(self._path_systrace_script)
if not self.is_valid:
logging.error('invalid systrace script path: %s',
self._path_systrace_script)
self.process_name = process_name
@property
def is_valid(self):
''''returns whether the current environment setting is valid'''
return self._is_valid
@is_valid.setter
def is_valid(self, is_valid):
''''Set valid status'''
self._is_valid = is_valid
def Start(self):
'''Start systrace process.
Use shell command to start a python systrace script
Returns:
True if successfully started systrace; False otherwise.
'''
self._subprocess = None
self._path_output = None
if not self.is_valid:
logging.error(
'Cannot start systrace: configuration is not correct for %s.',
self.process_name)
return False
# TODO: check target device for compatibility (e.g. has systrace hooks)
self._path_output = os.path.join(tempfile.mkdtemp(),
self.process_name + '.html')
cmd = ('python -u {script} hal sched '
'-a {process_name} -o {output}').format(
script=self._path_systrace_script,
process_name=self.process_name,
output=self._path_output)
process = subprocess.Popen(
str(cmd),
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
line = ''
success = False
while process.poll() is None:
line += process.stdout.read(1)
if not line:
break
elif EXPECTED_START_STDOUT in line:
success = True
break
if not success:
logging.error('Failed to start systrace on process %s',
process_name)
stdout, stderr = process.communicate()
logging.error('stdout: %s', line + stdout)
logging.error('stderr: %s', stderr)
logging.error('ret_code: %s', process.returncode)
return False
self._subprocess = process
logging.info('Systrace started for %s', self.process_name)
return True
def Stop(self):
'''Stop systrace process.
Returns:
True if successfully stopped systrace; False otherwise.
'''
if not self.is_valid or not self._subprocess:
logging.warn(
'Cannot stop systrace: systrace was not started for %s.',
self.process_name)
return False
# Press enter to stop systrace script
self._subprocess.stdin.write('\n')
self._subprocess.stdin.flush()
# Wait for output to be written down
self._subprocess.communicate()
logging.info('Systrace stopped for %s', self.process_name)
return True
def ReadLastOutput(self):
'''Read systrace output html.
Returns:
string, data of systrace html output. None if failed to read.
'''
if not self.is_valid or not self._subprocess:
logging.warn(
'Cannot read output: systrace was not started for %s.',
self.process_name)
return None
try:
with open(self._outputs[process_name], 'r') as f:
data = f.read()
logging.info('Systrace output length for %s: %s', process_name,
len(data))
return data
except Exception as e:
logging.error('Cannot read output: file open failed, %s', e)
return None
def SaveLastOutput(self, report_path=None):
if not report_path:
logging.error('report path supplied is None')
return False
if not self._path_output:
logging.error(
'systrace did not started correctly. Output path is empty.')
return False
parent_dir = os.path.dirname(report_path)
if not os.path.exists(parent_dir):
try:
os.makedirs(parent_dir)
except Exception as e:
logging.error('error happened while creating directory: %s', e)
return False
try:
shutil.copy(self._path_output, report_path)
except Exception as e: # TODO(yuexima): more specific error catch
logging.error('failed to copy output to report path: %s', e)
return False
return True
def ClearLastOutput(self):
'''Clear systrace output html.
Since output are created in temp directories, this step is optional.
Returns:
True if successfully deleted temp output file; False otherwise.
'''
if self._path_output:
try:
shutil.rmtree(self._path_output)
except Exception as e:
logging.error('failed to remove systrace output file. %s', e)
return False
return True