xref: /btstack/tool/metrics/metrics_ccsm.py (revision 1d6958009fc4c0630c04aa67fcd162c8eb50c8fd)
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            metric_list(metric_name + '_LIST', function_name)
83
84
85def analyze_folders(btstack_root, folders, metrics_file):
86    global excluded_functions
87
88    # File,Name,"'goto' keyword count (raw source)","Return points","Statement count (raw source)(local)",
89    # "Statement count (raw source)(cumulative)","Comment density","McCabe complexity (raw source)",
90    # "Number of paths through the function","No. different functions called","Function Parameters",
91    # "Nesting Level","VOCF","Number of functions which call this function",
92    fields = [ 'file','function','GOTO','RETURN','_','STMT' ,'_','CCN','PATH','CALLS','PARAM','LEVEL','_','_','_']
93
94    # init deviations
95    for key in fields:
96        metrics[key + '_DEVIATIONS'] = 0
97
98    # for now, just read the file
99    with open(metrics_file) as fd:
100        rd = csv.reader(fd, delimiter="\t")
101        last_function_name = ''
102        for row in rd:
103            file = ''
104            function_metrics = {}
105            # skip optional header
106            if row[0].startswith('#'):
107                continue
108
109            for key, value in zip(fields, row):
110                if key == 'file':
111                    # get rid of directory traversal on buildbot
112                    pos_metrics_folder = value.find('tool/metrics/')
113                    if pos_metrics_folder > 0:
114                        value = value[pos_metrics_folder+13:]
115                    # streamline path
116                    file = value.replace('../../','')
117                    continue
118                if key == 'function':
119                    function_name = value
120                    continue
121                if key == '_':
122                    continue
123                function_metrics[key] = value
124                histogram_add_data(key,int(value))
125
126            if file.endswith('.h'):
127                continue
128            qualified_function_name = file+':'+function_name
129            # excluded functions
130            if qualified_function_name in excluded_functions:
131                continue
132            metric_sum('FUNC', 1)
133            for key,value in function_metrics.items():
134                metric_measure(key, qualified_function_name, int(function_metrics[key]))
135
136def analyze(folders, metrics_file):
137    # print ("\nAnalyzing:")
138    # for path in folders:
139    #     print('- %s' % path)
140    #     analyze_folder(btstack_root + "/" + path)
141    btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/../..')
142    analyze_folders(btstack_root, folders, metrics_file)
143
144def list_targets():
145    print ("Targets:")
146    for key,value in sorted(targets.items()):
147        print ('- %-20s: %u' % (key, value))
148
149def list_metrics():
150    print ("\nResult:")
151    num_funcs = metrics['FUNC']
152    for key,value in sorted(metrics.items()):
153        if key.endswith('LIST'):
154            continue
155        if key.endswith('_SUM'):
156            average = 1.0 * value / num_funcs
157            metric = key.replace('_SUM','_AVERAGE')
158            print ('- %-20s: %4.3f' % (metric, average))
159        else:
160            print ('- %-20s: %5u' % (key, value))
161
162def list_metrics_table():
163    row = "%-11s |%11s |%11s |%11s"
164
165    print( row % ('Name', 'Target', 'Deviations', 'Max value'))
166    print("------------|------------|------------|------------")
167
168    ordered_metrics = [ 'PATH', 'GOTO', 'CCN', 'CALLS', 'PARAM', 'STMT', 'LEVEL', 'RETURN', 'FUNC'];
169    for metric_name in ordered_metrics:
170        if metric_name in targets:
171            target = targets[metric_name]
172            deviations = metrics[metric_name + '_DEVIATIONS']
173            max = metrics[metric_name + '_MAX']
174            print ( row % ( metric_name, target, deviations, max))
175        else:
176            max = metrics[metric_name]
177            print ( row % ( metric_name, '', '', max))
178
179def list_deviations():
180    global metrics
181    for key,value in sorted(metrics.items()):
182        if not key.endswith('LIST'):
183            continue
184        print ("\n%s" % key)
185        print ('\n'.join(value))
186
187def main(argv):
188    analyze(folders, "metrics.tsv")
189    list_metrics_table()
190    # list_targets()
191    # list_metrics()
192    # list_deviations()
193
194if __name__ == "__main__":
195   main(sys.argv[1:])
196