mirror of
				https://github.com/ARM-software/workload-automation.git
				synced 2025-10-31 07:04:17 +00:00 
			
		
		
		
	Add simpleperf type to perf instrument
* Added simpleperf type to perf instrument as it's more stable on Android devices. * Added record command to instrument * Added output processing for simpleperf
This commit is contained in:
		
				
					committed by
					
						 Marc Bonnici
						Marc Bonnici
					
				
			
			
				
	
			
			
			
						parent
						
							2f231b5ce5
						
					
				
				
					commit
					6beac11ee2
				
			| @@ -15,13 +15,14 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
| # pylint: disable=unused-argument | # pylint: disable=unused-argument | ||||||
|  | import csv | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
|  |  | ||||||
| from devlib.trace.perf import PerfCollector | from devlib.trace.perf import PerfCollector | ||||||
|  |  | ||||||
| from wa import Instrument, Parameter | from wa import Instrument, Parameter | ||||||
| from wa.utils.types import list_or_string, list_of_strs | from wa.utils.types import list_or_string, list_of_strs, numeric | ||||||
|  |  | ||||||
| PERF_COUNT_REGEX = re.compile(r'^(CPU\d+)?\s*(\d+)\s*(.*?)\s*(\[\s*\d+\.\d+%\s*\])?\s*$') | PERF_COUNT_REGEX = re.compile(r'^(CPU\d+)?\s*(\d+)\s*(.*?)\s*(\[\s*\d+\.\d+%\s*\])?\s*$') | ||||||
|  |  | ||||||
| @@ -31,29 +32,40 @@ class PerfInstrument(Instrument): | |||||||
|     name = 'perf' |     name = 'perf' | ||||||
|     description = """ |     description = """ | ||||||
|     Perf is a Linux profiling with performance counters. |     Perf is a Linux profiling with performance counters. | ||||||
|  |     Simpleperf is an Android profiling tool with performance counters. | ||||||
|  |  | ||||||
|  |     It is highly recomended to use perf_type = simpleperf when using this instrument | ||||||
|  |     on android devices since it recognises android symbols in record mode and is much more stable | ||||||
|  |     when reporting record .data files. For more information see simpleperf documentation at: | ||||||
|  |     https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md | ||||||
|  |  | ||||||
|     Performance counters are CPU hardware registers that count hardware events |     Performance counters are CPU hardware registers that count hardware events | ||||||
|     such as instructions executed, cache-misses suffered, or branches |     such as instructions executed, cache-misses suffered, or branches | ||||||
|     mispredicted. They form a basis for profiling applications to trace dynamic |     mispredicted. They form a basis for profiling applications to trace dynamic | ||||||
|     control flow and identify hotspots. |     control flow and identify hotspots. | ||||||
|  |  | ||||||
|     pref accepts options and events. If no option is given the default '-a' is |     perf accepts options and events. If no option is given the default '-a' is | ||||||
|     used. For events, the default events are migrations and cs. They both can |     used. For events, the default events for perf are migrations and cs. The default | ||||||
|     be specified in the config file. |     events for simpleperf are raw-cpu-cycles, raw-l1-dcache, raw-l1-dcache-refill, raw-instructions-retired. | ||||||
|  |     They both can be specified in the config file. | ||||||
|  |  | ||||||
|     Events must be provided as a list that contains them and they will look like |     Events must be provided as a list that contains them and they will look like | ||||||
|     this :: |     this :: | ||||||
|  |  | ||||||
|         perf_events = ['migrations', 'cs'] |         (for perf_type = perf ) perf_events = ['migrations', 'cs'] | ||||||
|  |         (for perf_type = simpleperf) perf_events = ['raw-cpu-cycles', 'raw-l1-dcache'] | ||||||
|  |  | ||||||
|  |  | ||||||
|     Events can be obtained by typing the following in the command line on the |     Events can be obtained by typing the following in the command line on the | ||||||
|     device :: |     device :: | ||||||
|  |  | ||||||
|         perf list |         perf list | ||||||
|  |         simpleperf list | ||||||
|  |  | ||||||
|     Whereas options, they can be provided as a single string as following :: |     Whereas options, they can be provided as a single string as following :: | ||||||
|  |  | ||||||
|         perf_options = '-a -i' |         perf_options = '-a -i' | ||||||
|  |         perf_options = '--app com.adobe.reader' | ||||||
|  |  | ||||||
|     Options can be obtained by running the following in the command line :: |     Options can be obtained by running the following in the command line :: | ||||||
|  |  | ||||||
| @@ -61,21 +73,32 @@ class PerfInstrument(Instrument): | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     parameters = [ |     parameters = [ | ||||||
|         Parameter('events', kind=list_of_strs, default=['migrations', 'cs'], |         Parameter('perf_type', kind=str, allowed_values=['perf', 'simpleperf'], default='perf', | ||||||
|                   global_alias='perf_events', |                   global_alias='perf_type', description="""Specifies which type of perf binaries | ||||||
|                   constraint=(lambda x: x, 'must not be empty.'), |                   to install. Use simpleperf for collecting perf data on android systems."""), | ||||||
|  |         Parameter('command', kind=str, default='stat', allowed_values=['stat', 'record'], | ||||||
|  |                   global_alias='perf_command', description="""Specifies which perf command to use. If in record mode | ||||||
|  |                   report command will also be executed and results pulled from target along with raw data | ||||||
|  |                   file"""), | ||||||
|  |         Parameter('events', kind=list_of_strs, global_alias='perf_events', | ||||||
|                   description="""Specifies the events to be counted."""), |                   description="""Specifies the events to be counted."""), | ||||||
|         Parameter('optionstring', kind=list_or_string, default='-a', |         Parameter('optionstring', kind=list_or_string, default='-a', | ||||||
|                   global_alias='perf_options', |                   global_alias='perf_options', | ||||||
|                   description="""Specifies options to be used for the perf command. This |                   description="""Specifies options to be used for the perf command. This | ||||||
|                   may be a list of option strings, in which case, multiple instances of perf |                   may be a list of option strings, in which case, multiple instances of perf | ||||||
|                   will be kicked off -- one for each option string. This may be used to e.g. |                   will be kicked off -- one for each option string. This may be used to e.g. | ||||||
|                   collected different events from different big.LITTLE clusters. |                   collected different events from different big.LITTLE clusters. In order to | ||||||
|  |                   profile a particular application process for android with simpleperf use | ||||||
|  |                   the --app option e.g. --app com.adobe.reader | ||||||
|                   """), |                   """), | ||||||
|  |         Parameter('report_option_string', kind=str, global_alias='perf_report_options', default=None, | ||||||
|  |                   description="""Specifies options to be used to gather report when record command | ||||||
|  |                   is used. It's highly recommended to use perf_type simpleperf when running on | ||||||
|  |                   android devices as reporting options are unstable with perf"""), | ||||||
|         Parameter('labels', kind=list_of_strs, default=None, |         Parameter('labels', kind=list_of_strs, default=None, | ||||||
|                   global_alias='perf_labels', |                   global_alias='perf_labels', | ||||||
|                   description="""Provides labels for pref output. If specified, the number of |                   description="""Provides labels for perf/simpleperf output for each optionstring. | ||||||
|                   labels must match the number of ``optionstring``\ s. |                   If specified, the number of labels must match the number of ``optionstring``\ s. | ||||||
|                   """), |                   """), | ||||||
|         Parameter('force_install', kind=bool, default=False, |         Parameter('force_install', kind=bool, default=False, | ||||||
|                   description=""" |                   description=""" | ||||||
| @@ -89,8 +112,11 @@ class PerfInstrument(Instrument): | |||||||
|  |  | ||||||
|     def initialize(self, context): |     def initialize(self, context): | ||||||
|         self.collector = PerfCollector(self.target, |         self.collector = PerfCollector(self.target, | ||||||
|  |                                        self.perf_type, | ||||||
|  |                                        self.command, | ||||||
|                                        self.events, |                                        self.events, | ||||||
|                                        self.optionstring, |                                        self.optionstring, | ||||||
|  |                                        self.report_option_string, | ||||||
|                                        self.labels, |                                        self.labels, | ||||||
|                                        self.force_install) |                                        self.force_install) | ||||||
|  |  | ||||||
| @@ -105,9 +131,30 @@ class PerfInstrument(Instrument): | |||||||
|  |  | ||||||
|     def update_output(self, context): |     def update_output(self, context): | ||||||
|         self.logger.info('Extracting reports from target...') |         self.logger.info('Extracting reports from target...') | ||||||
|         outdir = os.path.join(context.output_directory, 'perf') |         outdir = os.path.join(context.output_directory, self.perf_type) | ||||||
|         self.collector.get_trace(outdir) |         self.collector.get_trace(outdir) | ||||||
|  |  | ||||||
|  |         if self.perf_type == 'perf': | ||||||
|  |             self._process_perf_output(context, outdir) | ||||||
|  |         else: | ||||||
|  |             self._process_simpleperf_output(context, outdir) | ||||||
|  |  | ||||||
|  |     def teardown(self, context): | ||||||
|  |         self.collector.reset() | ||||||
|  |  | ||||||
|  |     def _process_perf_output(self, context, outdir): | ||||||
|  |         if self.command == 'stat': | ||||||
|  |             self._process_perf_stat_output(context, outdir) | ||||||
|  |         elif self.command == 'record': | ||||||
|  |             self._process_perf_record_output(context, outdir) | ||||||
|  |  | ||||||
|  |     def _process_simpleperf_output(self, context, outdir): | ||||||
|  |         if self.command == 'stat': | ||||||
|  |             self._process_simpleperf_stat_output(context, outdir) | ||||||
|  |         elif self.command == 'record': | ||||||
|  |             self._process_simpleperf_record_output(context, outdir) | ||||||
|  |  | ||||||
|  |     def _process_perf_stat_output(self, context, outdir): | ||||||
|         for host_file in os.listdir(outdir): |         for host_file in os.listdir(outdir): | ||||||
|             label = host_file.split('.out')[0] |             label = host_file.split('.out')[0] | ||||||
|             host_file_path = os.path.join(outdir, host_file) |             host_file_path = os.path.join(outdir, host_file) | ||||||
| @@ -118,21 +165,150 @@ class PerfInstrument(Instrument): | |||||||
|                     if 'Performance counter stats' in line: |                     if 'Performance counter stats' in line: | ||||||
|                         in_results_section = True |                         in_results_section = True | ||||||
|                         next(fh)  # skip the following blank line |                         next(fh)  # skip the following blank line | ||||||
|                     if in_results_section: |                     if not in_results_section: | ||||||
|                         if not line.strip():  # blank line |                         continue | ||||||
|                             in_results_section = False |                     if not line.strip():  # blank line | ||||||
|                             break |                         in_results_section = False | ||||||
|                         else: |                         break | ||||||
|                             line = line.split('#')[0]  # comment |                     else: | ||||||
|                             match = PERF_COUNT_REGEX.search(line) |                         self._add_perf_stat_metric(line, label, context) | ||||||
|                             if match: |  | ||||||
|                                 classifiers = {} |  | ||||||
|                                 cpu = match.group(1) |  | ||||||
|                                 if cpu is not None: |  | ||||||
|                                     classifiers['cpu'] = int(cpu.replace('CPU', '')) |  | ||||||
|                                 count = int(match.group(2)) |  | ||||||
|                                 metric = '{}_{}'.format(label, match.group(3)) |  | ||||||
|                                 context.add_metric(metric, count, classifiers=classifiers) |  | ||||||
|  |  | ||||||
|     def teardown(self, context): |     @staticmethod | ||||||
|         self.collector.reset() |     def _add_perf_stat_metric(line, label, context): | ||||||
|  |         line = line.split('#')[0]  # comment | ||||||
|  |         match = PERF_COUNT_REGEX.search(line) | ||||||
|  |         if not match: | ||||||
|  |             return | ||||||
|  |         classifiers = {} | ||||||
|  |         cpu = match.group(1) | ||||||
|  |         if cpu is not None: | ||||||
|  |             classifiers['cpu'] = int(cpu.replace('CPU', '')) | ||||||
|  |         count = int(match.group(2)) | ||||||
|  |         metric = '{}_{}'.format(label, match.group(3)) | ||||||
|  |         context.add_metric(metric, count, classifiers=classifiers) | ||||||
|  |  | ||||||
|  |     def _process_perf_record_output(self, context, outdir): | ||||||
|  |         for host_file in os.listdir(outdir): | ||||||
|  |             label, ext = os.path.splitext(host_file) | ||||||
|  |             context.add_artifact(label, os.path.join(outdir, host_file), 'raw') | ||||||
|  |             column_headers = [] | ||||||
|  |             column_header_indeces = [] | ||||||
|  |             event_type = '' | ||||||
|  |             if ext == '.rpt': | ||||||
|  |                 with open(os.path.join(outdir, host_file)) as fh: | ||||||
|  |                     for line in fh: | ||||||
|  |                         words = line.split() | ||||||
|  |                         if not words: | ||||||
|  |                             continue | ||||||
|  |                         event_type = self._get_report_event_type(words, event_type) | ||||||
|  |                         column_headers = self._get_report_column_headers(column_headers, words, 'perf') | ||||||
|  |                         for column_header in column_headers: | ||||||
|  |                             column_header_indeces.append(line.find(column_header)) | ||||||
|  |                         self._add_report_metric(column_headers, | ||||||
|  |                                                 column_header_indeces, | ||||||
|  |                                                 line, | ||||||
|  |                                                 words, | ||||||
|  |                                                 context, | ||||||
|  |                                                 event_type, | ||||||
|  |                                                 label) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _get_report_event_type(words, event_type): | ||||||
|  |         if words[0] != '#': | ||||||
|  |             return event_type | ||||||
|  |         if len(words) == 6 and words[4] == 'event': | ||||||
|  |             event_type = words[5] | ||||||
|  |             event_type = event_type.strip("'") | ||||||
|  |         return event_type | ||||||
|  |  | ||||||
|  |     def _process_simpleperf_stat_output(self, context, outdir): | ||||||
|  |         labels = [] | ||||||
|  |         for host_file in os.listdir(outdir): | ||||||
|  |             labels.append(host_file.split('.out')[0]) | ||||||
|  |         for opts, label in zip(self.optionstring, labels): | ||||||
|  |             stat_file = os.path.join(outdir, '{}{}'.format(label, '.out')) | ||||||
|  |             if '--csv' in opts: | ||||||
|  |                 self._process_simpleperf_stat_from_csv(stat_file, context, label) | ||||||
|  |             else: | ||||||
|  |                 self._process_simpleperf_stat_from_raw(stat_file, context, label) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _process_simpleperf_stat_from_csv(stat_file, context, label): | ||||||
|  |         with open(stat_file) as csv_file: | ||||||
|  |             readCSV = csv.reader(csv_file, delimiter=',') | ||||||
|  |             line_num = 0 | ||||||
|  |             for row in readCSV: | ||||||
|  |                 if line_num > 0 and 'Total test time' not in row: | ||||||
|  |                     classifiers = {'scaled from(%)': row[len(row) - 2].replace('(', '').replace(')', '').replace('%', '')} | ||||||
|  |                     context.add_metric('{}_{}'.format(label, row[1]), row[0], 'count', classifiers=classifiers) | ||||||
|  |                 line_num += 1 | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _process_simpleperf_stat_from_raw(stat_file, context, label): | ||||||
|  |         with open(stat_file) as fh: | ||||||
|  |             for line in fh: | ||||||
|  |                 if '#' in line: | ||||||
|  |                     tmp_line = line.split('#')[0] | ||||||
|  |                     tmp_line = line.strip() | ||||||
|  |                     count, metric = tmp_line.split(' ')[0], tmp_line.split(' ')[2] | ||||||
|  |                     count = int(count.replace(',', '')) | ||||||
|  |                     scaled_percentage = line.split('(')[1].strip().replace(')', '').replace('%', '') | ||||||
|  |                     scaled_percentage = int(scaled_percentage) | ||||||
|  |                     metric = '{}_{}'.format(label, metric) | ||||||
|  |                     context.add_metric(metric, count, 'count', classifiers={'scaled from(%)': scaled_percentage}) | ||||||
|  |  | ||||||
|  |     def _process_simpleperf_record_output(self, context, outdir): | ||||||
|  |         for host_file in os.listdir(outdir): | ||||||
|  |             label, ext = os.path.splitext(host_file) | ||||||
|  |             context.add_artifact(label, os.path.join(outdir, host_file), 'raw') | ||||||
|  |             if ext != '.rpt': | ||||||
|  |                 continue | ||||||
|  |             column_headers = [] | ||||||
|  |             column_header_indeces = [] | ||||||
|  |             event_type = '' | ||||||
|  |             with open(os.path.join(outdir, host_file)) as fh: | ||||||
|  |                 for line in fh: | ||||||
|  |                     words = line.split() | ||||||
|  |                     if not words: | ||||||
|  |                         continue | ||||||
|  |                     if words[0] == 'Event:': | ||||||
|  |                         event_type = words[1] | ||||||
|  |                     column_headers = self._get_report_column_headers(column_headers, | ||||||
|  |                                                                      words, | ||||||
|  |                                                                      'simpleperf') | ||||||
|  |                     for column_header in column_headers: | ||||||
|  |                         column_header_indeces.append(line.find(column_header)) | ||||||
|  |                     self._add_report_metric(column_headers, | ||||||
|  |                                             column_header_indeces, | ||||||
|  |                                             line, | ||||||
|  |                                             words, | ||||||
|  |                                             context, | ||||||
|  |                                             event_type, | ||||||
|  |                                             label) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _get_report_column_headers(column_headers, words, perf_type): | ||||||
|  |         if 'Overhead' not in words: | ||||||
|  |             return column_headers | ||||||
|  |         if perf_type == 'perf': | ||||||
|  |             words.remove('#') | ||||||
|  |         column_headers = words | ||||||
|  |         # Concatonate Shared Objects header | ||||||
|  |         if 'Shared' in column_headers: | ||||||
|  |             shared_index = column_headers.index('Shared') | ||||||
|  |             column_headers[shared_index:shared_index + 2] = ['{} {}'.format(column_headers[shared_index], | ||||||
|  |                                                                             column_headers[shared_index + 1])] | ||||||
|  |         return column_headers | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _add_report_metric(column_headers, column_header_indeces, line, words, context, event_type, label): | ||||||
|  |         if '%' not in words[0]: | ||||||
|  |             return | ||||||
|  |         classifiers = {} | ||||||
|  |         for i in range(1, len(column_headers)): | ||||||
|  |             classifiers[column_headers[i]] = line[column_header_indeces[i]:column_header_indeces[i + 1]].strip() | ||||||
|  |  | ||||||
|  |         context.add_metric('{}_{}_Overhead'.format(label, event_type), | ||||||
|  |                            numeric(words[0].strip('%')), | ||||||
|  |                            'percent', | ||||||
|  |                            classifiers=classifiers) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user