1#!/usr/bin/env python3 2 3# requires https://github.com/bright-tools/ccsm 4 5import os 6import sys 7import csv 8 9folders = [ 10'src', 11'src/ble', 12'src/ble/gatt-service', 13'src/classic', 14] 15 16metrics = {} 17targets = {} 18histogram_data = {} 19 20targets['PATH'] = 1000 21targets['GOTO'] = 0 22targets['CCN'] = 20 23targets['CALLS'] = 12 24targets['PARAM'] = 7 25targets['STMT'] = 100 26targets['LEVEL'] = 6 27targets['RETURN'] = 1 28 29excluded_functions = [ 30 # deprecated functions 31 'src/l2cap.c:l2cap_le_register_service', 32 'src/l2cap.c:l2cap_le_unregister_service', 33 'src/l2cap.c:l2cap_le_accept_connection', 34 'src/l2cap.c:l2cap_le_decline_connection', 35 'src/l2cap.c:l2cap_le_provide_credits', 36 'src/l2cap.c:l2cap_le_create_channel', 37 'src/l2cap.c:l2cap_le_can_send_now', 38 'src/l2cap.c:l2cap_le_request_can_send_now_event', 39 'src/l2cap.c:l2cap_le_send_data', 40 'src/l2cap.c:l2cap_le_disconnect', 41 'src/l2cap.c:l2cap_cbm_can_send_now', 42 'src/l2cap.c:l2cap_cbm_request_can_send_now_even' 43] 44 45def histogram_add_data(key, value): 46 global histogram_data 47 if key not in histogram_data.keys(): 48 histogram_data[key] = [] 49 histogram_data[key].append(value) 50 51 52def metric_sum(name, value): 53 global metrics 54 old = 0 55 if name in metrics: 56 old = metrics[name] 57 metrics[name] = old + value 58 59 60def metric_list(name, item): 61 global metrics 62 value = [] 63 if name in metrics: 64 value = metrics[name] 65 value.append(item) 66 metrics[name] = value 67 68def metric_max(name, max): 69 global metrics 70 if name in metrics: 71 if metrics[name] > max: 72 return 73 metrics[name] = max 74 75def metric_measure(metric_name, function_name, actual): 76 metric_max(metric_name + '_MAX', actual) 77 if metric_name in targets: 78 metric_sum(metric_name + '_SUM', actual) 79 if actual > targets[metric_name]: 80 metric_sum(metric_name + '_DEVIATIONS', 1) 81 metric_list(metric_name + '_LIST', function_name + ' - %u' % actual) 82 83 84def analyze_folders(btstack_root, folders, metrics_file): 85 global excluded_functions 86 87 # File,Name,"'goto' keyword count (raw source)","Return points","Statement count (raw source)(local)", 88 # "Statement count (raw source)(cumulative)","Comment density","McCabe complexity (raw source)", 89 # "Number of paths through the function","No. different functions called","Function Parameters", 90 # "Nesting Level","VOCF","Number of functions which call this function", 91 fields = [ 'file','function','GOTO','RETURN','_','STMT' ,'_','CCN','PATH','CALLS','PARAM','LEVEL','_','_','_'] 92 93 # init deviations 94 for key in fields: 95 metrics[key + '_DEVIATIONS'] = 0 96 97 # for now, just read the file 98 with open(metrics_file) as fd: 99 rd = csv.reader(fd, delimiter="\t") 100 last_function_name = '' 101 for row in rd: 102 file = '' 103 function_metrics = {} 104 # skip optional header 105 if row[0].startswith('#'): 106 continue 107 108 for key, value in zip(fields, row): 109 if key == 'file': 110 # get rid of directory traversal on buildbot 111 pos_metrics_folder = value.find('tool/metrics/') 112 if pos_metrics_folder > 0: 113 value = value[pos_metrics_folder+13:] 114 # streamline path 115 file = value.replace('../../','') 116 continue 117 if key == 'function': 118 function_name = value 119 continue 120 if key == '_': 121 continue 122 function_metrics[key] = value 123 histogram_add_data(key,int(value)) 124 125 if file.endswith('.h'): 126 continue 127 qualified_function_name = file+':'+function_name 128 # excluded functions 129 if qualified_function_name in excluded_functions: 130 continue 131 metric_sum('FUNC', 1) 132 for key,value in function_metrics.items(): 133 metric_measure(key, qualified_function_name, int(function_metrics[key])) 134 135def analyze(folders, metrics_file): 136 # print ("\nAnalyzing:") 137 # for path in folders: 138 # print('- %s' % path) 139 # analyze_folder(btstack_root + "/" + path) 140 btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/../..') 141 analyze_folders(btstack_root, folders, metrics_file) 142 143def list_targets(): 144 print ("Targets:") 145 for key,value in sorted(targets.items()): 146 print ('- %-20s: %u' % (key, value)) 147 148def list_metrics(): 149 print ("\nResult:") 150 num_funcs = metrics['FUNC'] 151 for key,value in sorted(metrics.items()): 152 if key.endswith('LIST'): 153 continue 154 if key.endswith('_SUM'): 155 average = 1.0 * value / num_funcs 156 metric = key.replace('_SUM','_AVERAGE') 157 print ('- %-20s: %4.3f' % (metric, average)) 158 else: 159 print ('- %-20s: %5u' % (key, value)) 160 161def list_metrics_table(): 162 row = "%-11s |%11s |%11s |%11s" 163 164 print( row % ('Name', 'Target', 'Deviations', 'Max value')) 165 print("------------|------------|------------|------------") 166 167 ordered_metrics = [ 'PATH', 'GOTO', 'CCN', 'CALLS', 'PARAM', 'STMT', 'LEVEL', 'RETURN', 'FUNC']; 168 for metric_name in ordered_metrics: 169 if metric_name in targets: 170 target = targets[metric_name] 171 deviations = metrics[metric_name + '_DEVIATIONS'] 172 max = metrics[metric_name + '_MAX'] 173 print ( row % ( metric_name, target, deviations, max)) 174 else: 175 max = metrics[metric_name] 176 print ( row % ( metric_name, '', '', max)) 177 178def list_deviations(): 179 global metrics 180 for key,value in sorted(metrics.items()): 181 if not key.endswith('LIST'): 182 continue 183 print ("\n%s" % key) 184 print ('\n'.join(value)) 185 186def main(argv): 187 analyze(folders, "metrics.tsv") 188 list_metrics_table() 189 # list_targets() 190 # list_metrics() 191 # list_deviations() 192 193if __name__ == "__main__": 194 main(sys.argv[1:]) 195