-
-
Notifications
You must be signed in to change notification settings - Fork 585
/
metrics.py
109 lines (86 loc) · 3.76 KB
/
metrics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# -*- coding:utf-8 -*-
#
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# SPDX-License-Identifier: Apache-2.0
import collections
from bandit.core import constants
class Metrics(object):
"""Bandit metric gathering.
This class is a singleton used to gather and process metrics collected when
processing a code base with bandit. Metric collection is stateful, that
is, an active metric block will be set when requested and all subsequent
operations will effect that metric block until it is replaced by a setting
a new one.
"""
def __init__(self):
self.data = dict()
self.data['_totals'] = {'loc': 0, 'nosec': 0, 'nosec_by_test': 0,
'failed_nosec_by_test': 0}
# initialize 0 totals for criteria and rank; this will be reset later
for rank in constants.RANKING:
for criteria in constants.CRITERIA:
self.data['_totals']['{0}.{1}'.format(criteria[0], rank)] = 0
def begin(self, fname):
"""Begin a new metric block.
This starts a new metric collection name "fname" and makes is active.
:param fname: the metrics unique name, normally the file name.
"""
self.data[fname] = {'loc': 0, 'nosec': 0, 'nosec_by_test': 0,
'failed_nosec_by_test': 0}
self.current = self.data[fname]
def note_nosec(self, num=1):
"""Note a "nosec" comment.
Increment the currently active metrics nosec count.
:param num: number of nosecs seen, defaults to 1
"""
self.current['nosec'] += num
def note_nosec_by_test(self, num=1):
"""Note a "nosec BXXX, BYYY, ..." comment.
Increment the currently active metrics nosec_by_test count.
:param num: number of nosecs seen, defaults to 1
"""
self.current['nosec_by_test'] += num
def note_failed_nosec_by_test(self, num=1):
"""Note a test failed not caught when specific tests in comment used
Increment the currently active metrics failed_nosec_by_test count.
:param num: number of failed nosecs seen, defaults to 1
"""
self.current['failed_nosec_by_test'] += num
def count_locs(self, lines):
"""Count lines of code.
We count lines that are not empty and are not comments. The result is
added to our currently active metrics loc count (normally this is 0).
:param lines: lines in the file to process
"""
def proc(line):
tmp = line.strip()
return bool(tmp and not tmp.startswith(b'#'))
self.current['loc'] += sum(proc(line) for line in lines)
def count_issues(self, scores):
self.current.update(self._get_issue_counts(scores))
def aggregate(self):
"""Do final aggregation of metrics."""
c = collections.Counter()
for fname in self.data:
c.update(self.data[fname])
self.data['_totals'] = dict(c)
@staticmethod
def _get_issue_counts(scores):
"""Get issue counts aggregated by confidence/severity rankings.
:param scores: list of scores to aggregate / count
:return: aggregated total (count) of issues identified
"""
issue_counts = {}
for score in scores:
for (criteria, _) in constants.CRITERIA:
for i, rank in enumerate(constants.RANKING):
label = '{0}.{1}'.format(criteria, rank)
if label not in issue_counts:
issue_counts[label] = 0
count = (
score[criteria][i] /
constants.RANKING_VALUES[rank]
)
issue_counts[label] += count
return issue_counts