Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # This is a tool that works like debug location coverage calculator. |
| 4 | # It parses the llvm-dwarfdump --statistics output by reporting it |
| 5 | # in a more human readable way. |
| 6 | # |
| 7 | |
| 8 | from __future__ import print_function |
| 9 | import argparse |
| 10 | import os |
| 11 | import sys |
| 12 | from json import loads |
| 13 | from math import ceil |
| 14 | from subprocess import Popen, PIPE |
| 15 | |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 16 | # Holds the debug location statistics. |
| 17 | class LocationStats: |
| 18 | def __init__(self, file_name, variables_total, variables_total_locstats, |
| 19 | variables_with_loc, variables_scope_bytes_covered, variables_scope_bytes, |
| 20 | variables_coverage_map): |
| 21 | self.file_name = file_name |
| 22 | self.variables_total = variables_total |
| 23 | self.variables_total_locstats = variables_total_locstats |
| 24 | self.variables_with_loc = variables_with_loc |
| 25 | self.scope_bytes_covered = variables_scope_bytes_covered |
| 26 | self.scope_bytes = variables_scope_bytes |
| 27 | self.variables_coverage_map = variables_coverage_map |
| 28 | |
| 29 | # Pretty print the debug location buckets. |
| 30 | def pretty_print(self): |
| 31 | if self.scope_bytes == 0: |
| 32 | print ('No scope bytes found.') |
| 33 | return -1 |
| 34 | |
| 35 | pc_ranges_covered = int(ceil(self.scope_bytes_covered * 100.0) \ |
| 36 | / self.scope_bytes) |
| 37 | variables_coverage_per_map = {} |
| 38 | for cov_bucket in coverage_buckets(): |
| 39 | variables_coverage_per_map[cov_bucket] = \ |
| 40 | int(ceil(self.variables_coverage_map[cov_bucket] * 100.0) \ |
| 41 | / self.variables_total_locstats) |
| 42 | |
| 43 | print (' =================================================') |
| 44 | print (' Debug Location Statistics ') |
| 45 | print (' =================================================') |
| 46 | print (' cov% samples percentage(~) ') |
| 47 | print (' -------------------------------------------------') |
| 48 | for cov_bucket in coverage_buckets(): |
| 49 | print (' {0:10} {1:8d} {2:3d}%'. \ |
| 50 | format(cov_bucket, self.variables_coverage_map[cov_bucket], \ |
| 51 | variables_coverage_per_map[cov_bucket])) |
| 52 | print (' =================================================') |
| 53 | print (' -the number of debug variables processed: ' \ |
| 54 | + str(self.variables_total_locstats)) |
| 55 | print (' -PC ranges covered: ' + str(pc_ranges_covered) + '%') |
| 56 | |
| 57 | # Only if we are processing all the variables output the total |
| 58 | # availability. |
| 59 | if self.variables_total and self.variables_with_loc: |
| 60 | total_availability = int(ceil(self.variables_with_loc * 100.0) \ |
| 61 | / self.variables_total) |
| 62 | print (' -------------------------------------------------') |
| 63 | print (' -total availability: ' + str(total_availability) + '%') |
| 64 | print (' =================================================') |
| 65 | |
| 66 | return 0 |
| 67 | |
| 68 | # Define the location buckets. |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 69 | def coverage_buckets(): |
| 70 | yield '0%' |
Kristina Bessonova | d5655c4 | 2019-12-05 16:45:57 +0300 | [diff] [blame] | 71 | yield '(0%,10%)' |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 72 | for start in range(10, 91, 10): |
Kristina Bessonova | 1cc4b60 | 2019-12-11 20:52:49 +0300 | [diff] [blame] | 73 | yield '[{0}%,{1}%)'.format(start, start + 10) |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 74 | yield '100%' |
| 75 | |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 76 | # Parse the JSON representing the debug statistics, and create a |
| 77 | # LocationStats object. |
| 78 | def parse_locstats(opts, binary): |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 79 | # These will be different due to different options enabled. |
| 80 | variables_total = None |
| 81 | variables_total_locstats = None |
| 82 | variables_with_loc = None |
| 83 | variables_scope_bytes_covered = None |
Kristina Bessonova | 68f464a | 2019-11-19 13:28:21 +0300 | [diff] [blame] | 84 | variables_scope_bytes = None |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 85 | variables_scope_bytes_entry_values = None |
| 86 | variables_coverage_map = {} |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 87 | |
| 88 | # Get the directory of the LLVM tools. |
| 89 | llvm_dwarfdump_cmd = os.path.join(os.path.dirname(__file__), \ |
| 90 | "llvm-dwarfdump") |
| 91 | # The statistics llvm-dwarfdump option. |
| 92 | llvm_dwarfdump_stats_opt = "--statistics" |
| 93 | |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 94 | # Generate the stats with the llvm-dwarfdump. |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 95 | subproc = Popen([llvm_dwarfdump_cmd, llvm_dwarfdump_stats_opt, binary], \ |
| 96 | stdin=PIPE, stdout=PIPE, stderr=PIPE, \ |
| 97 | universal_newlines = True) |
| 98 | cmd_stdout, cmd_stderr = subproc.communicate() |
| 99 | |
| 100 | # Get the JSON and parse it. |
| 101 | json_parsed = None |
| 102 | |
| 103 | try: |
| 104 | json_parsed = loads(cmd_stdout) |
| 105 | except: |
| 106 | print ('error: No valid llvm-dwarfdump statistics found.') |
| 107 | sys.exit(1) |
| 108 | |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 109 | if opts.only_variables: |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 110 | # Read the JSON only for local variables. |
| 111 | variables_total_locstats = \ |
| 112 | json_parsed['total vars procesed by location statistics'] |
| 113 | variables_scope_bytes_covered = \ |
| 114 | json_parsed['vars scope bytes covered'] |
Kristina Bessonova | 68f464a | 2019-11-19 13:28:21 +0300 | [diff] [blame] | 115 | variables_scope_bytes = \ |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 116 | json_parsed['vars scope bytes total'] |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 117 | if not opts.ignore_debug_entry_values: |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 118 | for cov_bucket in coverage_buckets(): |
| 119 | cov_category = "vars with {} of its scope covered".format(cov_bucket) |
| 120 | variables_coverage_map[cov_bucket] = json_parsed[cov_category] |
| 121 | else: |
| 122 | variables_scope_bytes_entry_values = \ |
| 123 | json_parsed['vars entry value scope bytes covered'] |
| 124 | variables_scope_bytes_covered = variables_scope_bytes_covered \ |
| 125 | - variables_scope_bytes_entry_values |
| 126 | for cov_bucket in coverage_buckets(): |
| 127 | cov_category = \ |
| 128 | "vars (excluding the debug entry values) " \ |
| 129 | "with {} of its scope covered".format(cov_bucket) |
| 130 | variables_coverage_map[cov_bucket] = json_parsed[cov_category] |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 131 | elif opts.only_formal_parameters: |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 132 | # Read the JSON only for formal parameters. |
| 133 | variables_total_locstats = \ |
| 134 | json_parsed['total params procesed by location statistics'] |
| 135 | variables_scope_bytes_covered = \ |
| 136 | json_parsed['formal params scope bytes covered'] |
Kristina Bessonova | 68f464a | 2019-11-19 13:28:21 +0300 | [diff] [blame] | 137 | variables_scope_bytes = \ |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 138 | json_parsed['formal params scope bytes total'] |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 139 | if not opts.ignore_debug_entry_values: |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 140 | for cov_bucket in coverage_buckets(): |
| 141 | cov_category = "params with {} of its scope covered".format(cov_bucket) |
| 142 | variables_coverage_map[cov_bucket] = json_parsed[cov_category] |
| 143 | else: |
| 144 | variables_scope_bytes_entry_values = \ |
| 145 | json_parsed['formal params entry value scope bytes covered'] |
| 146 | variables_scope_bytes_covered = variables_scope_bytes_covered \ |
| 147 | - variables_scope_bytes_entry_values |
| 148 | for cov_bucket in coverage_buckets(): |
| 149 | cov_category = \ |
| 150 | "params (excluding the debug entry values) " \ |
| 151 | "with {} of its scope covered".format(cov_bucket) |
Djordje Todorovic | 095531e | 2019-10-15 10:12:14 +0000 | [diff] [blame] | 152 | variables_coverage_map[cov_bucket] = json_parsed[cov_category] |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 153 | else: |
| 154 | # Read the JSON for both local variables and formal parameters. |
| 155 | variables_total = \ |
| 156 | json_parsed['source variables'] |
| 157 | variables_with_loc = json_parsed['variables with location'] |
| 158 | variables_total_locstats = \ |
| 159 | json_parsed['total variables procesed by location statistics'] |
| 160 | variables_scope_bytes_covered = \ |
| 161 | json_parsed['scope bytes covered'] |
Kristina Bessonova | 68f464a | 2019-11-19 13:28:21 +0300 | [diff] [blame] | 162 | variables_scope_bytes = \ |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 163 | json_parsed['scope bytes total'] |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 164 | if not opts.ignore_debug_entry_values: |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 165 | for cov_bucket in coverage_buckets(): |
| 166 | cov_category = "variables with {} of its scope covered". \ |
| 167 | format(cov_bucket) |
| 168 | variables_coverage_map[cov_bucket] = json_parsed[cov_category] |
| 169 | else: |
| 170 | variables_scope_bytes_entry_values = \ |
| 171 | json_parsed['entry value scope bytes covered'] |
| 172 | variables_scope_bytes_covered = variables_scope_bytes_covered \ |
| 173 | - variables_scope_bytes_entry_values |
| 174 | for cov_bucket in coverage_buckets(): |
| 175 | cov_category = "variables (excluding the debug entry values) " \ |
| 176 | "with {} of its scope covered". format(cov_bucket) |
| 177 | variables_coverage_map[cov_bucket] = json_parsed[cov_category] |
| 178 | |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 179 | return LocationStats(binary, variables_total, variables_total_locstats, |
| 180 | variables_with_loc, variables_scope_bytes_covered, |
| 181 | variables_scope_bytes, variables_coverage_map) |
| 182 | |
| 183 | # Parse the program arguments. |
| 184 | def parse_program_args(parser): |
| 185 | parser.add_argument('--only-variables', action='store_true', default=False, |
| 186 | help='calculate the location statistics only for local variables') |
| 187 | parser.add_argument('--only-formal-parameters', action='store_true', |
| 188 | default=False, |
| 189 | help='calculate the location statistics only for formal parameters') |
| 190 | parser.add_argument('--ignore-debug-entry-values', action='store_true', |
| 191 | default=False, |
| 192 | help='ignore the location statistics on locations with ' |
| 193 | 'entry values') |
| 194 | parser.add_argument('file_name', type=str, help='file to process') |
| 195 | |
| 196 | return parser.parse_args() |
| 197 | |
| 198 | # Verify that the program inputs meet the requirements. |
| 199 | def verify_program_inputs(opts): |
| 200 | if len(sys.argv) < 2: |
| 201 | print ('error: Too few arguments.') |
| 202 | return False |
| 203 | |
| 204 | if opts.only_variables and opts.only_formal_parameters: |
| 205 | print ('error: Please use just one --only* option.') |
| 206 | return False |
| 207 | |
| 208 | return True |
| 209 | |
| 210 | def Main(): |
| 211 | parser = argparse.ArgumentParser() |
| 212 | opts = parse_program_args(parser) |
| 213 | |
| 214 | if not verify_program_inputs(opts): |
| 215 | parser.print_help() |
| 216 | sys.exit(1) |
| 217 | |
| 218 | binary = opts.file_name |
| 219 | locstats = parse_locstats(opts, binary) |
| 220 | |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 221 | # Pretty print collected info. |
Djordje Todorovic | a3ebc40 | 2020-01-13 12:31:28 +0100 | [diff] [blame] | 222 | if locstats.pretty_print() == -1: |
| 223 | sys.exit(0) |
Djordje Todorovic | 2ef18fb | 2019-10-02 07:00:01 +0000 | [diff] [blame] | 224 | |
| 225 | if __name__ == '__main__': |
| 226 | Main() |
| 227 | sys.exit(0) |