Emma Lagier | a96327f | 2020-08-14 14:06:58 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright (C) 2020 The Android Open Source Project |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | """This script annotates a CFG file with profiling information from simpleperf |
| 16 | record files. |
| 17 | |
| 18 | Example: |
| 19 | perf2cfg --cfg bench.cfg --perf-data perf.data |
| 20 | """ |
| 21 | |
| 22 | import argparse |
| 23 | import logging |
| 24 | import os |
| 25 | import sys |
| 26 | import textwrap |
| 27 | |
| 28 | from perf2cfg import analyze |
| 29 | from perf2cfg import edit |
| 30 | |
| 31 | |
| 32 | def parse_arguments() -> argparse.Namespace: |
| 33 | """Parses program arguments. |
| 34 | |
| 35 | Returns: |
| 36 | argparse.Namespace: A populated argument namespace. |
| 37 | """ |
| 38 | parser = argparse.ArgumentParser( |
| 39 | # Hardcode the usage string as argparse does not display long options |
| 40 | # if short ones are specified |
| 41 | usage=textwrap.dedent("""\ |
| 42 | perf2cfg [-h|--help] --cfg CFG --perf-data PERF_DATA [PERF_DATA ...] |
| 43 | [--output-file OUTPUT_FILE] [-e|--events EVENTS] |
| 44 | [--primary-event PRIMARY_EVENT]"""), |
| 45 | description='Annotates a CFG file with profiling information from ' |
| 46 | 'simpleperf data files.', |
| 47 | add_help=False) |
| 48 | required = parser.add_argument_group('required arguments') |
| 49 | required.add_argument('--cfg', |
| 50 | required=True, |
| 51 | help='The CFG file to annotate.') |
| 52 | required.add_argument( |
| 53 | '--perf-data', |
| 54 | nargs='+', |
| 55 | required=True, |
| 56 | help='The perf data files to extract information from.') |
| 57 | parser.add_argument('-h', |
| 58 | '--help', |
| 59 | action='help', |
| 60 | default=argparse.SUPPRESS, |
| 61 | help='Show this help message and exit.') |
| 62 | parser.add_argument('--output-file', help='A path to the output CFG file.') |
| 63 | parser.add_argument( |
| 64 | '-e', |
| 65 | '--events', |
| 66 | type=lambda events: events.split(',') if events else [], |
| 67 | help='A comma-separated list of events only to use for annotating a ' |
| 68 | 'CFG (default: use all events found in perf data). An error is ' |
| 69 | 'reported if the events are not present in perf data.') |
| 70 | parser.add_argument( |
| 71 | '--primary-event', |
| 72 | default='cpu-cycles', |
| 73 | help='The event to be used for basic blocks hotness analysis ' |
| 74 | '(default: %(default)s). Basic blocks are color highlighted according ' |
| 75 | 'to their hotness. An error is reported if the primary event is not ' |
| 76 | 'present in perf data.') |
| 77 | args = parser.parse_args() |
| 78 | |
| 79 | if not args.output_file: |
| 80 | root, ext = os.path.splitext(args.cfg) |
| 81 | args.output_file = f'{root}-annotated{ext}' |
| 82 | |
| 83 | return args |
| 84 | |
| 85 | |
| 86 | def analyze_record_files(args: argparse.Namespace) -> analyze.RecordAnalyzer: |
| 87 | """Analyzes simpleperf record files. |
| 88 | |
| 89 | Args: |
| 90 | args (argparse.Namespace): An argument namespace. |
| 91 | |
| 92 | Returns: |
| 93 | analyze.RecordAnalyzer: A RecordAnalyzer object. |
| 94 | """ |
| 95 | analyzer = analyze.RecordAnalyzer(args.events) |
| 96 | for record_file in args.perf_data: |
| 97 | analyzer.analyze(record_file) |
| 98 | |
| 99 | return analyzer |
| 100 | |
| 101 | |
| 102 | def validate_events(analyzer: analyze.RecordAnalyzer, |
| 103 | args: argparse.Namespace) -> None: |
| 104 | """Validates event names given on the command line. |
| 105 | |
| 106 | Args: |
| 107 | analyzer (analyze.RecordAnalyzer): A RecordAnalyzer object. |
| 108 | args (argparse.Namespace): An argument namespace. |
| 109 | """ |
| 110 | if not analyzer.event_counts: |
| 111 | logging.error('The selected events are not present in perf data') |
| 112 | sys.exit(1) |
| 113 | |
| 114 | if args.primary_event not in analyzer.event_counts: |
| 115 | logging.error( |
| 116 | 'The selected primary event %s is not present in perf data', |
| 117 | args.primary_event) |
| 118 | sys.exit(1) |
| 119 | |
| 120 | |
| 121 | def annotate_cfg_file(analyzer: analyze.RecordAnalyzer, |
| 122 | args: argparse.Namespace) -> None: |
| 123 | """Annotates a CFG file. |
| 124 | |
| 125 | Args: |
| 126 | analyzer (analyze.RecordAnalyzer): A RecordAnalyzer object. |
| 127 | args (argparse.Namespace): An argument namespace. |
| 128 | """ |
| 129 | input_stream = open(args.cfg, 'r') |
| 130 | output_stream = open(args.output_file, 'w') |
| 131 | |
| 132 | editor = edit.CfgEditor(analyzer, input_stream, output_stream, |
| 133 | args.primary_event) |
| 134 | editor.edit() |
| 135 | |
| 136 | input_stream.close() |
| 137 | output_stream.close() |
| 138 | |
| 139 | |
| 140 | def main() -> None: |
| 141 | """Annotates a CFG file with information from simpleperf record files.""" |
| 142 | args = parse_arguments() |
| 143 | analyzer = analyze_record_files(args) |
| 144 | validate_events(analyzer, args) |
| 145 | annotate_cfg_file(analyzer, args) |
| 146 | |
| 147 | |
| 148 | if __name__ == '__main__': |
| 149 | main() |