Skip to content

Commit 7d90286

Browse files
authored
Merge pull request #136 from Pennycook/code-utilization
Add ability to compute code utilization
2 parents 060c70d + 83106f0 commit 7d90286

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

codebasin/report.py

+71
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import itertools as it
88
import logging
99
import warnings
10+
from collections import defaultdict
1011

1112
from codebasin import util
1213

@@ -81,6 +82,74 @@ def divergence(setmap):
8182
return d / float(npairs)
8283

8384

85+
def utilization(setmap: defaultdict[frozenset[str], int]) -> float:
86+
"""
87+
Compute the average code utilization for all lines in the setmap.
88+
i.e., (reused SLOC / total SLOC)
89+
90+
Parameters
91+
----------
92+
setmap: defaultdict[frozenset[str], int]
93+
The mapping from platform sets to SLOC.
94+
95+
Returns
96+
-------
97+
float
98+
The average code utilization, in the range [0, NumPlatforms].
99+
If the number of total SLOC is 0, returns NaN.
100+
"""
101+
reused_sloc = 0
102+
total_sloc = 0
103+
for k, v in setmap.items():
104+
reused_sloc += len(k) * v
105+
total_sloc += v
106+
if total_sloc == 0:
107+
return float("nan")
108+
109+
return reused_sloc / total_sloc
110+
111+
112+
def normalized_utilization(
113+
setmap: defaultdict[frozenset[str], int],
114+
total_platforms: int | None = None,
115+
) -> float:
116+
"""
117+
Compute the average code utilization, normalized for a specific number of
118+
platforms.
119+
120+
Parameters
121+
----------
122+
setmap: defaultdict[frozenset[str,int]
123+
The mapping from platform sets to SLOC.
124+
125+
total_platforms: int, optional
126+
The total number of platforms to use as the denominator.
127+
By default, the denominator will be derived from the setmap.
128+
129+
Returns
130+
-------
131+
float
132+
The average code utilization, in the range [0, 1].
133+
134+
Raises
135+
------
136+
ValueError
137+
If `total_platforms` < the number of platforms in `setmap`.
138+
"""
139+
original_platforms = len(extract_platforms(setmap))
140+
if total_platforms is None:
141+
total_platforms = original_platforms
142+
if total_platforms < original_platforms:
143+
raise ValueError(
144+
"Cannot normalize to fewer platforms than the setmap contains.",
145+
)
146+
147+
if total_platforms == 0:
148+
return float("nan")
149+
else:
150+
return utilization(setmap) / total_platforms
151+
152+
84153
def summary(setmap):
85154
"""
86155
Produce a summary report for the platform set
@@ -99,8 +168,10 @@ def summary(setmap):
99168
lines += [table(["Platform Set", "LOC", "% LOC"], data)]
100169

101170
cd = divergence(setmap)
171+
nu = normalized_utilization(setmap)
102172
unused = (setmap[frozenset()] / total_count) * 100.0
103173
lines += [f"Code Divergence: {cd:.2f}"]
174+
lines += [f"Code Utilization: {nu:.2f}"]
104175
lines += [f"Unused Code (%): {unused:.2f}"]
105176
lines += [f"Total SLOC: {total_count}"]
106177

tests/metrics/test_utilization.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright (C) 2019-2024 Intel Corporation
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
4+
import logging
5+
import math
6+
import unittest
7+
import warnings
8+
9+
from codebasin.report import normalized_utilization, utilization
10+
11+
12+
class TestUtilization(unittest.TestCase):
13+
"""
14+
Test computation of code utilization.
15+
"""
16+
17+
def setUp(self):
18+
logging.disable()
19+
warnings.simplefilter("ignore", ResourceWarning)
20+
21+
def test_utilization(self):
22+
"""Check utilization computation for simple setmap."""
23+
setmap = {
24+
frozenset(["A"]): 1,
25+
frozenset(["B"]): 2,
26+
frozenset(["A", "B"]): 3,
27+
frozenset([]): 4,
28+
}
29+
reused_sloc = (1 * 1) + (1 * 2) + (2 * 3) + (0 * 4)
30+
total_sloc = 1 + 2 + 3 + 4
31+
32+
expected_utilization = reused_sloc / total_sloc
33+
self.assertEqual(utilization(setmap), expected_utilization)
34+
35+
expected_normalized = expected_utilization / 2
36+
self.assertEqual(normalized_utilization(setmap), expected_normalized)
37+
38+
expected_normalized = expected_utilization / 4
39+
self.assertEqual(
40+
normalized_utilization(setmap, 4),
41+
expected_normalized,
42+
)
43+
44+
def test_null_utilization(self):
45+
"""Check utilization computation for null cases."""
46+
setmap = {
47+
frozenset(""): 0,
48+
}
49+
self.assertTrue(math.isnan(utilization(setmap)))
50+
self.assertTrue(math.isnan(normalized_utilization(setmap)))
51+
self.assertTrue(math.isnan(normalized_utilization(setmap, 0)))
52+
53+
setmap = {
54+
frozenset("A"): 1,
55+
frozenset("B"): 1,
56+
}
57+
with self.assertRaises(ValueError):
58+
_ = normalized_utilization(setmap, 1)
59+
60+
61+
if __name__ == "__main__":
62+
unittest.main()

0 commit comments

Comments
 (0)