• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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