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 16record files. 17 18Example: 19 perf2cfg --cfg bench.cfg --perf-data perf.data 20""" 21 22import argparse 23import logging 24import os 25import sys 26import textwrap 27 28from perf2cfg import analyze 29from perf2cfg import edit 30 31 32def 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 86def 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 102def 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 121def 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 140def 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 148if __name__ == '__main__': 149 main() 150