-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for 'perf record and 'perf report' to devlib #382
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,8 @@ | |
from devlib.utils.misc import ensure_file_directory_exists as _f | ||
|
||
|
||
PERF_COMMAND_TEMPLATE = '{} stat {} {} sleep 1000 > {} 2>&1 ' | ||
PERF_COMMAND_TEMPLATE = '{} {} {} {} sleep 1000 > {} 2>&1 ' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @setrofim & @marcbonnici : This might be a standard approach in If we don't want that, isn't Second question (tiny detail, out of curiosity), what is the final space in the string for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically, this is to save having to pull two files instead of one. Given that the output is generally parsed with a line parser looking for specific patterns, mixing in
The
Erm. Aesthetics? A subtle reference to the TBS animated show? </joke> Seriously, I have no idea. It might be a hold-over form an earlier WA2 implementation where it might have mattered. But I don't see it serving a purpose in the current code. Well spotted. |
||
PERF_REPORT_COMMAND_TEMPLATE = '{} report {} -i {} > {} 2>&1 ' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't we modify |
||
|
||
PERF_COUNT_REGEX = re.compile(r'^(CPU\d+)?\s*(\d+)\s*(.*?)\s*(\[\s*\d+\.\d+%\s*\])?\s*$') | ||
|
||
|
@@ -69,6 +70,8 @@ def __init__(self, target, | |
events=None, | ||
optionstring=None, | ||
labels=None, | ||
mode='stat', | ||
report_optionstring=None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @setrofim & @marcbonnici : could we consider extending There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you elaborate on what you mean (maybe raise a separate issue, if inappropriate for this PR)? |
||
force_install=False): | ||
super(PerfCollector, self).__init__(target) | ||
self.events = events if events else DEFAULT_EVENTS | ||
|
@@ -82,10 +85,20 @@ def __init__(self, target, | |
self.optionstrings = [optionstring] | ||
if self.events and isinstance(self.events, basestring): | ||
self.events = [self.events] | ||
if isinstance(report_optionstring, list): | ||
self.report_optionstrings = report_optionstring | ||
else: | ||
self.report_optionstrings = [report_optionstring] | ||
if not self.labels: | ||
self.labels = ['perf_{}'.format(i) for i in range(len(self.optionstrings))] | ||
if len(self.labels) != len(self.optionstrings): | ||
raise ValueError('The number of labels must match the number of optstrings provided for perf.') | ||
if len(self.optionstrings) != len(self.report_optionstrings): | ||
raise ValueError('The number of report_optionstrings must match the number of optionstrings provided for perf.') | ||
if mode in ['stat', 'record']: | ||
self.mode = mode | ||
elif mode not in ['stat', 'record']: | ||
raise ValueError('Invalid mode setting, must be stat or record') | ||
|
||
self.binary = self.target.get_installed('perf') | ||
if self.force_install or not self.binary: | ||
|
@@ -98,6 +111,8 @@ def reset(self): | |
for label in self.labels: | ||
filepath = self._get_target_outfile(label) | ||
self.target.remove(filepath) | ||
filepath = self._get_target_reportfile(label) | ||
self.target.remove(filepath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We remove a report file event if we're not going to call |
||
|
||
def start(self): | ||
for command in self.commands: | ||
|
@@ -108,7 +123,23 @@ def stop(self): | |
|
||
# pylint: disable=arguments-differ | ||
def get_trace(self, outdir): | ||
for label in self.labels: | ||
for label, report_opt in zip(self.labels, self.report_optionstrings): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overall, as a reader that hasn't written the code, I find the fact that Going one step further, I notice that Another way to do this, would be to implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those are all excellent points. The "trace" interface hasn't been particularly well thought out and probably needs some serious though put into redesigning it. (And as has been pointed out elsewhere, "trace" is not a good name for what is being handled by this interface at the moment (see #378.) This a much bigger issue that this PR, and I don't think it should block it. There is already precedent for directories being passed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have raised an issue to track this: #383. If you have any suggestions for changes to the collector API, please feel free to add them to the issue. |
||
#in record mode generate report and copy | ||
if self.mode == 'record': | ||
# .rpt | ||
command = self._build_perf_report_command(report_opt, label) | ||
self.target.execute(command, as_root=True) | ||
target_file = self._get_target_reportfile(label) | ||
host_relpath = os.path.basename(target_file) | ||
host_file = _f(os.path.join(outdir, host_relpath)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you seem to be doing this process of:
Could we consider instead having a |
||
self.target.pull(target_file, host_file) | ||
# .trace | ||
target_file = self._get_target_tracefile(label) | ||
host_relpath = os.path.basename(target_file) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like a cumbersome way of getting |
||
host_file = _f(os.path.join(outdir, host_relpath)) | ||
target_file = self._get_target_perfdatafile() | ||
self.target.pull(target_file, host_file) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The previous 5 lines seem to be pulling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be the cause of the permission errors: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, what happens to the file pointed to by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The file never exists on the device because a mixture of perf behavior and how it is executed by WA. WA executes perf to run sleep and then kills it after a certain time. This seems to break the perf -o option and perf just outputs to a default place/file name ({current directory}/perf.data). So the -o option isn't used to run perf record and we do a rename of perf.data. Seemed easiest just to do it as part of the copy rather than directly on the device. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Interesting, I didn't know that. It's probably because |
||
# .out | ||
target_file = self._get_target_outfile(label) | ||
host_relpath = os.path.basename(target_file) | ||
host_file = _f(os.path.join(outdir, host_relpath)) | ||
|
@@ -131,7 +162,25 @@ def _get_target_outfile(self, label): | |
def _build_perf_command(self, options, events, label): | ||
event_string = ' '.join(['-e {}'.format(e) for e in events]) | ||
command = PERF_COMMAND_TEMPLATE.format(self.binary, | ||
self.mode, | ||
options or '', | ||
event_string, | ||
self._get_target_outfile(label)) | ||
return command | ||
|
||
def _get_target_perfdatafile(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these functions really necessary? |
||
return self.target.get_workpath('perf.data') | ||
|
||
def _get_target_reportfile(self, label): | ||
return self.target.get_workpath('{}.rpt'.format(label)) | ||
|
||
def _get_target_tracefile(self, label): | ||
return self.target.get_workpath('{}.trace'.format(label)) | ||
|
||
def _build_perf_report_command(self, reportoptions, label): | ||
reportoptions_string = reportoptions | ||
command = PERF_REPORT_COMMAND_TEMPLATE.format(self.binary, | ||
reportoptions_string, | ||
self._get_target_perfdatafile(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When testing this on my linux device the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On Android it seems to include a I'd assume that it is the same on Linux but haven't confirmed. I can look at running on Linux target but will take me a little time. Do you happen to have the run.log file from your testing to confirm the command used on Linux? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes I see the same behaviour on my android device however on linux it produces: For some reason the |
||
self._get_target_reportfile(label)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And when testing this on my android device the fails with error code 244 caused by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have seen error code 244 on a few occasions but haven't tracked it down. It has been working reliable for a while for me on the Android device I am using. I was guessing it was related to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tested on a few Android devices and see error code 244 or just failures on some but not others. It seems to be consistent behavior on the devices, so a device either works or doesn't. The error message seems to be related to some check of the perf.data file and likely finding an corruption issue with perf.data. So I think this is a result of the approach WA uses for running (aka killing) perf and some platforms behavior writing to the perf.data file to storage when killed. Apart from a re-write of perf support in WA I think the only thing that could be done here is to try identify the behavior that causes the file corruption and see if we can override it from the device command prompt (e.g. setprop ....) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's interesting, I've tried running this again with the latest version and no longer see this problem on my device. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hadn't notice @marcbonnici had replied to this: could we try rebasing on #384 to see if the error message still appears? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tried #384 and sleep(10) and still seeing exit code 244. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... I'm wondering why I received this initially and haven't been able to reproduce it since. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just in case, could you give #386 a try? |
||
return command |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could take the opportunity here to introduce named replacement format fields:
allowing more readable and reliable calls to
PERF_COMMAND_TEMPLATE.format()
as they don't use position-dependant inputs.