mirror of
				https://github.com/ARM-software/devlib.git
				synced 2025-10-31 14:01:20 +00:00 
			
		
		
		
	Add simpleperf type to perf TraceCollector
* Added simperf type to trace collector * Added record command to allow for perf/simpleperf recording and reporting
This commit is contained in:
		
				
					committed by
					
						 Marc Bonnici
						Marc Bonnici
					
				
			
			
				
	
			
			
			
						parent
						
							9e6cfde832
						
					
				
				
					commit
					5e69f06d77
				
			
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/arm/simpleperf
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/arm/simpleperf
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/arm64/simpleperf
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/arm64/simpleperf
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/x86/simpleperf
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/x86/simpleperf
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								devlib/bin/x86_64/simpleperf
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								devlib/bin/x86_64/simpleperf
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -13,9 +13,9 @@ | |||||||
| # limitations under the License. | # limitations under the License. | ||||||
| # | # | ||||||
|  |  | ||||||
|  |  | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
|  | import time | ||||||
| from past.builtins import basestring, zip | from past.builtins import basestring, zip | ||||||
|  |  | ||||||
| from devlib.host import PACKAGE_BIN_DIRECTORY | from devlib.host import PACKAGE_BIN_DIRECTORY | ||||||
| @@ -23,19 +23,34 @@ from devlib.trace import TraceCollector | |||||||
| from devlib.utils.misc import ensure_file_directory_exists as _f | from devlib.utils.misc import ensure_file_directory_exists as _f | ||||||
|  |  | ||||||
|  |  | ||||||
| PERF_COMMAND_TEMPLATE = '{} stat {} {} sleep 1000 > {} 2>&1 ' | PERF_COMMAND_TEMPLATE = '{binary} {command} {options} {events} sleep 1000 > {outfile} 2>&1 ' | ||||||
|  | PERF_REPORT_COMMAND_TEMPLATE= '{binary} report {options} -i {datafile} > {outfile} 2>&1 ' | ||||||
|  | PERF_RECORD_COMMAND_TEMPLATE= '{binary} record {options} {events} -o {outfile}'  | ||||||
|  |  | ||||||
| PERF_COUNT_REGEX = re.compile(r'^(CPU\d+)?\s*(\d+)\s*(.*?)\s*(\[\s*\d+\.\d+%\s*\])?\s*$') | PERF_DEFAULT_EVENTS = [ | ||||||
|  |     'cpu-migrations', | ||||||
| DEFAULT_EVENTS = [ |     'context-switches', | ||||||
|     'migrations', |  | ||||||
|     'cs', |  | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | SIMPLEPERF_DEFAULT_EVENTS = [ | ||||||
|  |     'raw-cpu-cycles', | ||||||
|  |     'raw-l1-dcache', | ||||||
|  |     'raw-l1-dcache-refill', | ||||||
|  |     'raw-br-mis-pred', | ||||||
|  |     'raw-instruction-retired', | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | DEFAULT_EVENTS = {'perf':PERF_DEFAULT_EVENTS, 'simpleperf':SIMPLEPERF_DEFAULT_EVENTS} | ||||||
|  |  | ||||||
| class PerfCollector(TraceCollector): | class PerfCollector(TraceCollector): | ||||||
|     """ |     """ | ||||||
|     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 | ||||||
| @@ -43,7 +58,8 @@ class PerfCollector(TraceCollector): | |||||||
|     control flow and identify hotspots. |     control flow and identify hotspots. | ||||||
|  |  | ||||||
|     pref accepts options and events. If no option is given the default '-a' is |     pref 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 are migrations and cs for perf and raw-cpu-cycles, | ||||||
|  |     raw-l1-dcache, raw-l1-dcache-refill, raw-instructions-retired. They both can | ||||||
|     be specified in the config file. |     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 | ||||||
| @@ -55,6 +71,7 @@ class PerfCollector(TraceCollector): | |||||||
|     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 :: | ||||||
|  |  | ||||||
| @@ -65,38 +82,59 @@ class PerfCollector(TraceCollector): | |||||||
|         man perf-stat |         man perf-stat | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, target, |     def __init__(self,  | ||||||
|  |                  target, | ||||||
|  |                  perf_type='perf', | ||||||
|  |                  command='stat', | ||||||
|                  events=None, |                  events=None, | ||||||
|                  optionstring=None, |                  optionstring=None, | ||||||
|  |                  report_options=None, | ||||||
|                  labels=None, |                  labels=None, | ||||||
|                  force_install=False): |                  force_install=False): | ||||||
|         super(PerfCollector, self).__init__(target) |         super(PerfCollector, self).__init__(target) | ||||||
|         self.events = events if events else DEFAULT_EVENTS |  | ||||||
|         self.force_install = force_install |         self.force_install = force_install | ||||||
|         self.labels = labels |         self.labels = labels | ||||||
|  |         self.report_options = report_options | ||||||
|  |  | ||||||
|         # Validate parameters |         # Validate parameters | ||||||
|         if isinstance(optionstring, list): |         if isinstance(optionstring, list): | ||||||
|             self.optionstrings = optionstring |             self.optionstrings = optionstring | ||||||
|         else: |         else: | ||||||
|             self.optionstrings = [optionstring] |             self.optionstrings = [optionstring] | ||||||
|         if self.events and isinstance(self.events, basestring): |         if perf_type in ['perf', 'simpleperf']: | ||||||
|  |             self.perf_type = perf_type | ||||||
|  |         else: | ||||||
|  |             raise ValueError('Invalid perf type: {}, must be perf or simpleperf'.format(perf_type)) | ||||||
|  |         if not events: | ||||||
|  |             self.events = DEFAULT_EVENTS[self.perf_type] | ||||||
|  |         else: | ||||||
|  |             self.events = events | ||||||
|  |         if isinstance(self.events, basestring): | ||||||
|             self.events = [self.events] |             self.events = [self.events] | ||||||
|         if not self.labels: |         if not self.labels: | ||||||
|             self.labels = ['perf_{}'.format(i) for i in range(len(self.optionstrings))] |             self.labels = ['perf_{}'.format(i) for i in range(len(self.optionstrings))] | ||||||
|         if len(self.labels) != 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.') |             raise ValueError('The number of labels must match the number of optstrings provided for perf.') | ||||||
|  |         if command in ['stat', 'record']: | ||||||
|  |             self.command = command | ||||||
|  |         else: | ||||||
|  |             raise ValueError('Unsupported perf command, must be stat or record') | ||||||
|  |  | ||||||
|         self.binary = self.target.get_installed('perf') |         self.binary = self.target.get_installed(self.perf_type) | ||||||
|         if self.force_install or not self.binary: |         if self.force_install or not self.binary: | ||||||
|             self.binary = self._deploy_perf() |             self.binary = self._deploy_perf() | ||||||
|  |  | ||||||
|  |         self._validate_events(self.events) | ||||||
|  |  | ||||||
|         self.commands = self._build_commands() |         self.commands = self._build_commands() | ||||||
|  |  | ||||||
|     def reset(self): |     def reset(self): | ||||||
|         self.target.killall('perf', as_root=self.target.is_rooted) |         self.target.killall(self.perf_type, as_root=self.target.is_rooted) | ||||||
|  |         self.target.remove(self.target.get_workpath('TemporaryFile*')) | ||||||
|         for label in self.labels: |         for label in self.labels: | ||||||
|             filepath = self._get_target_outfile(label) |             filepath = self._get_target_file(label, 'data') | ||||||
|  |             self.target.remove(filepath) | ||||||
|  |             filepath = self._get_target_file(label, 'rpt') | ||||||
|             self.target.remove(filepath) |             self.target.remove(filepath) | ||||||
|  |  | ||||||
|     def start(self): |     def start(self): | ||||||
| @@ -104,7 +142,7 @@ class PerfCollector(TraceCollector): | |||||||
|             self.target.kick_off(command) |             self.target.kick_off(command) | ||||||
|  |  | ||||||
|     def stop(self): |     def stop(self): | ||||||
|         self.target.killall('perf', signal='SIGINT', |         self.target.killall(self.perf_type, signal='SIGINT', | ||||||
|                             as_root=self.target.is_rooted) |                             as_root=self.target.is_rooted) | ||||||
|         # perf doesn't transmit the signal to its sleep call so handled here: |         # perf doesn't transmit the signal to its sleep call so handled here: | ||||||
|         self.target.killall('sleep', as_root=self.target.is_rooted) |         self.target.killall('sleep', as_root=self.target.is_rooted) | ||||||
| @@ -113,29 +151,90 @@ class PerfCollector(TraceCollector): | |||||||
|     # pylint: disable=arguments-differ |     # pylint: disable=arguments-differ | ||||||
|     def get_trace(self, outdir): |     def get_trace(self, outdir): | ||||||
|         for label in self.labels: |         for label in self.labels: | ||||||
|             target_file = self._get_target_outfile(label) |             if self.command == 'record': | ||||||
|             host_relpath = os.path.basename(target_file) |                 self._wait_for_data_file_write(label, outdir) | ||||||
|             host_file = _f(os.path.join(outdir, host_relpath)) |                 self._pull_target_file_to_host(label, 'rpt', outdir) | ||||||
|             self.target.pull(target_file, host_file) |             else: | ||||||
|  |                 self._pull_target_file_to_host(label, 'out', outdir) | ||||||
|  |  | ||||||
|     def _deploy_perf(self): |     def _deploy_perf(self): | ||||||
|         host_executable = os.path.join(PACKAGE_BIN_DIRECTORY, |         host_executable = os.path.join(PACKAGE_BIN_DIRECTORY, | ||||||
|                                        self.target.abi, 'perf') |                                        self.target.abi, self.perf_type) | ||||||
|         return self.target.install(host_executable) |         return self.target.install(host_executable) | ||||||
|  |  | ||||||
|  |     def _get_target_file(self, label, extension): | ||||||
|  |         return self.target.get_workpath('{}.{}'.format(label, extension)) | ||||||
|  |  | ||||||
|     def _build_commands(self): |     def _build_commands(self): | ||||||
|         commands = [] |         commands = [] | ||||||
|         for opts, label in zip(self.optionstrings, self.labels): |         for opts, label in zip(self.optionstrings, self.labels): | ||||||
|             commands.append(self._build_perf_command(opts, self.events, label)) |             if self.command == 'stat': | ||||||
|  |                 commands.append(self._build_perf_stat_command(opts, self.events, label)) | ||||||
|  |             else: | ||||||
|  |                 commands.append(self._build_perf_record_command(opts, label)) | ||||||
|         return commands |         return commands | ||||||
|  |  | ||||||
|     def _get_target_outfile(self, label): |     def _build_perf_stat_command(self, options, events, label): | ||||||
|         return self.target.get_workpath('{}.out'.format(label)) |  | ||||||
|  |  | ||||||
|     def _build_perf_command(self, options, events, label): |  | ||||||
|         event_string = ' '.join(['-e {}'.format(e) for e in events]) |         event_string = ' '.join(['-e {}'.format(e) for e in events]) | ||||||
|         command = PERF_COMMAND_TEMPLATE.format(self.binary, |         command = PERF_COMMAND_TEMPLATE.format(binary = self.binary, | ||||||
|                                                options or '', |                                                command = self.command, | ||||||
|                                                event_string, |                                                options = options or '', | ||||||
|                                                self._get_target_outfile(label)) |                                                events = event_string, | ||||||
|  |                                                outfile = self._get_target_file(label, 'out')) | ||||||
|         return command |         return command | ||||||
|  |  | ||||||
|  |     def _build_perf_report_command(self, report_options, label): | ||||||
|  |         command = PERF_REPORT_COMMAND_TEMPLATE.format(binary=self.binary, | ||||||
|  |                                                       options=report_options or '', | ||||||
|  |                                                       datafile=self._get_target_file(label, 'data'), | ||||||
|  |                                                       outfile=self._get_target_file(label, 'rpt')) | ||||||
|  |         return command | ||||||
|  |  | ||||||
|  |     def _build_perf_record_command(self, options, label): | ||||||
|  |         event_string = ' '.join(['-e {}'.format(e) for e in self.events]) | ||||||
|  |         command = PERF_RECORD_COMMAND_TEMPLATE.format(binary=self.binary, | ||||||
|  |                                                       options=options or '', | ||||||
|  |                                                       events=event_string, | ||||||
|  |                                                       outfile=self._get_target_file(label, 'data')) | ||||||
|  |         return command | ||||||
|  |  | ||||||
|  |     def _pull_target_file_to_host(self, label, extension, outdir): | ||||||
|  |         target_file = self._get_target_file(label, extension) | ||||||
|  |         host_relpath = os.path.basename(target_file) | ||||||
|  |         host_file = _f(os.path.join(outdir, host_relpath)) | ||||||
|  |         self.target.pull(target_file, host_file) | ||||||
|  |  | ||||||
|  |     def _wait_for_data_file_write(self, label, outdir): | ||||||
|  |         data_file_finished_writing = False | ||||||
|  |         max_tries = 80 | ||||||
|  |         current_tries = 0 | ||||||
|  |         while not data_file_finished_writing: | ||||||
|  |             files = self.target.execute('cd {} && ls'.format(self.target.get_workpath(''))) | ||||||
|  |             # Perf stores data in tempory files whilst writing to data output file. Check if they have been removed. | ||||||
|  |             if 'TemporaryFile' in files and current_tries <= max_tries: | ||||||
|  |                 time.sleep(0.25) | ||||||
|  |                 current_tries += 1 | ||||||
|  |             else: | ||||||
|  |                 if current_tries >= max_tries: | ||||||
|  |                     self.logger.warning('''writing {}.data file took longer than expected,  | ||||||
|  |                                         file may not have written correctly'''.format(label)) | ||||||
|  |                 data_file_finished_writing = True | ||||||
|  |         report_command = self._build_perf_report_command(self.report_options, label) | ||||||
|  |         self.target.execute(report_command) | ||||||
|  |  | ||||||
|  |     def _validate_events(self, events): | ||||||
|  |         available_events_string = self.target.execute('{} list'.format(self.perf_type)) | ||||||
|  |         available_events = available_events_string.splitlines() | ||||||
|  |         for available_event in available_events: | ||||||
|  |             if available_event == '': | ||||||
|  |                 continue | ||||||
|  |             if 'OR' in available_event: | ||||||
|  |                 available_events.append(available_event.split('OR')[1])  | ||||||
|  |             available_events[available_events.index(available_event)] = available_event.split()[0].strip() | ||||||
|  |         # Raw hex event codes can also be passed in that do not appear on perf/simpleperf list, prefixed with 'r' | ||||||
|  |         raw_event_code_regex = re.compile(r"^r(0x|0X)?[A-Fa-f0-9]+$") | ||||||
|  |         for event in events: | ||||||
|  |             if event in available_events or re.match(raw_event_code_regex, event): | ||||||
|  |                 continue | ||||||
|  |             else: | ||||||
|  |                 raise ValueError('Event: {} is not in available event list for {}'.format(event, self.perf_type)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user