| 4954 |
tlittlef |
1 |
import json, sys, datetime
|
|
|
2 |
from urllib.request import urlopen, Request
|
| 4380 |
tlittlef |
3 |
|
| 5258 |
tlittlef |
4 |
CRUCIBLE_URL_TEMPLATE = 'http://cloudasvn03:8060/rest-service/reviews-v1'
|
| 4866 |
tlittlef |
5 |
RESULTS_KEY = 'results'
|
|
|
6 |
GEOMETRY_KEY = 'geometry'
|
|
|
7 |
LOCATION_KEY = 'location'
|
|
|
8 |
LAT_KEY = 'lat'
|
|
|
9 |
LNG_KEY = 'lng'
|
| 4380 |
tlittlef |
10 |
|
| 5144 |
tlittlef |
11 |
def trace(*objs) :
|
| 5298 |
tlittlef |
12 |
# print(*objs,file=sys.stderr)
|
|
|
13 |
# sys.stderr.flush()
|
|
|
14 |
pass
|
| 5144 |
tlittlef |
15 |
|
| 4380 |
tlittlef |
16 |
class CrucibleExtractor :
|
|
|
17 |
def __init__(self) :
|
|
|
18 |
pass
|
|
|
19 |
self.selected_reviews = {}
|
|
|
20 |
self.runtime_defects = {}
|
|
|
21 |
self.maintenance_defects = {}
|
|
|
22 |
self.security_defects = {}
|
|
|
23 |
self.unclassified_defect_reviews = [ ]
|
|
|
24 |
self.total_defects = 0
|
| 4420 |
tlittlef |
25 |
self.verbose = False
|
| 4380 |
tlittlef |
26 |
def url_to_object(self, url, top=None) :
|
| 5144 |
tlittlef |
27 |
trace(url)
|
| 4954 |
tlittlef |
28 |
req = Request(url)
|
| 4380 |
tlittlef |
29 |
req.add_header('Accept','application/json')
|
|
|
30 |
req.add_header('Authorization', 'Basic dGxpdHRsZWY6MCRtb3JQRVRI')
|
| 4954 |
tlittlef |
31 |
resp = urlopen(req).read()
|
|
|
32 |
retval = json.loads(str(resp,'utf-8'))
|
| 4380 |
tlittlef |
33 |
if top :
|
|
|
34 |
retval = retval[top]
|
|
|
35 |
return retval
|
|
|
36 |
def get_selected_review_ids(self,min_create_date, max_create_date) :
|
|
|
37 |
url = CRUCIBLE_URL_TEMPLATE
|
| 4866 |
tlittlef |
38 |
reviewData = self.url_to_object(url,"reviewData")
|
| 4380 |
tlittlef |
39 |
for r in reviewData :
|
|
|
40 |
review_id = r["permaId"]["id"]
|
|
|
41 |
review_create_date = r["createDate"]
|
|
|
42 |
if review_create_date>=min_create_date and review_create_date<=max_create_date :
|
| 4866 |
tlittlef |
43 |
self.selected_reviews[review_id] = [ r['projectKey'], None, ]
|
| 4380 |
tlittlef |
44 |
else :
|
| 5144 |
tlittlef |
45 |
trace("Excluding review ",review_id)
|
| 4380 |
tlittlef |
46 |
pass
|
|
|
47 |
return self.selected_reviews.keys()
|
|
|
48 |
def get_review_summary(self,review_id) :
|
|
|
49 |
url = CRUCIBLE_URL_TEMPLATE + "/" + review_id + "/comments"
|
|
|
50 |
projectKey = str(self.selected_reviews[review_id][0])
|
| 4866 |
tlittlef |
51 |
reviewDetails = self.url_to_object(url,'comments')
|
| 4380 |
tlittlef |
52 |
retval = [
|
|
|
53 |
projectKey,
|
|
|
54 |
0, # non-defect comments
|
|
|
55 |
0, # runtime defects
|
|
|
56 |
0, # maintenance defects
|
|
|
57 |
0, # security defects
|
|
|
58 |
0, # unclassified defects
|
|
|
59 |
]
|
|
|
60 |
for c in reviewDetails:
|
| 4954 |
tlittlef |
61 |
metrics_keys = list(c['metrics'].keys())
|
| 4866 |
tlittlef |
62 |
if c['defectRaised'] is False :
|
| 4380 |
tlittlef |
63 |
retval[1] += 1
|
| 4954 |
tlittlef |
64 |
elif len(metrics_keys) != 1 : # unclassified - either none of the drop downs or more than one selected
|
| 4380 |
tlittlef |
65 |
retval[5] += 1
|
|
|
66 |
self.unclassified_defect_reviews += [ review_id ]
|
|
|
67 |
self.total_defects += 1
|
|
|
68 |
else :
|
| 4954 |
tlittlef |
69 |
defect_category_code = metrics_keys[0]
|
| 4866 |
tlittlef |
70 |
defect_type = c['metrics'][defect_category_code]['value']
|
| 5144 |
tlittlef |
71 |
trace(defect_category_code, defect_type)
|
| 4380 |
tlittlef |
72 |
category_defect_map = None
|
| 5144 |
tlittlef |
73 |
if defect_category_code == 'metric-0' : # maintenance
|
| 4380 |
tlittlef |
74 |
retval[3] += 1
|
|
|
75 |
category_defect_map = self.maintenance_defects
|
| 5144 |
tlittlef |
76 |
elif defect_category_code == 'metric-1' : # runtime
|
| 4380 |
tlittlef |
77 |
retval[2] += 1
|
|
|
78 |
category_defect_map = self.runtime_defects
|
| 4866 |
tlittlef |
79 |
elif defect_category_code == 'metric-97' : # security
|
| 4380 |
tlittlef |
80 |
retval[4] += 1
|
|
|
81 |
category_defect_map = self.security_defects
|
| 4420 |
tlittlef |
82 |
else :
|
| 5144 |
tlittlef |
83 |
trace("Unexpected defect category %s (defect type is %s)" % ( defect_category_code, defect_type))
|
| 4420 |
tlittlef |
84 |
retval[5] += 1
|
|
|
85 |
self.unclassified_defect_reviews += [ review_id ]
|
|
|
86 |
self.total_defects += 1
|
| 4765 |
tlittlef |
87 |
if category_defect_map is None :
|
|
|
88 |
pass
|
|
|
89 |
elif defect_type in category_defect_map.keys() :
|
| 4380 |
tlittlef |
90 |
category_defect_map[defect_type] += 1
|
|
|
91 |
else :
|
|
|
92 |
category_defect_map[defect_type] = 1
|
|
|
93 |
self.total_defects += 1
|
|
|
94 |
self.selected_reviews[review_id][1] = retval[2:]
|
|
|
95 |
return retval
|
| 4381 |
tlittlef |
96 |
def summarize_by_project(self, month_prefix) :
|
|
|
97 |
start_date = month_prefix + "-00"
|
|
|
98 |
end_date = month_prefix + "-99"
|
| 4420 |
tlittlef |
99 |
if self.verbose :
|
| 5144 |
tlittlef |
100 |
trace("Getting review ids")
|
| 4380 |
tlittlef |
101 |
review_ids = self.get_selected_review_ids(start_date, end_date)
|
|
|
102 |
project_summaries = {}
|
|
|
103 |
overall_summary = [ 0, ] * 6
|
| 5144 |
tlittlef |
104 |
for review_id in review_ids :
|
| 4380 |
tlittlef |
105 |
if True :
|
|
|
106 |
review_summary = self.get_review_summary(review_id)
|
| 4420 |
tlittlef |
107 |
if self.verbose :
|
| 5144 |
tlittlef |
108 |
trace("%-12s: %s" % ( review_id, self.selected_reviews[review_id][1], ))
|
| 4380 |
tlittlef |
109 |
project_id = review_summary[0]
|
|
|
110 |
if project_id in project_summaries.keys() :
|
|
|
111 |
project_summary = project_summaries[project_id]
|
|
|
112 |
else :
|
|
|
113 |
project_summary = [ 0,] * 6
|
|
|
114 |
project_summary[0] += 1
|
|
|
115 |
overall_summary[0] += 1
|
|
|
116 |
for i in range(1,6) :
|
|
|
117 |
project_summary[i] += review_summary[i]
|
|
|
118 |
overall_summary[i] += review_summary[i]
|
|
|
119 |
project_summaries[project_id] = project_summary
|
|
|
120 |
REPORT_PRINTF_FORMAT = "%-12s %4s %4s %4s %4s %4s %4s"
|
|
|
121 |
REPORT_FORMAT_SEPARATOR = ( '-------','---','---','---','---','---','---')
|
| 4866 |
tlittlef |
122 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
|
|
123 |
print ( REPORT_PRINTF_FORMAT % ( "Project", "rev", "com", "run", "mnt", "sec", "oth" ) )
|
|
|
124 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4380 |
tlittlef |
125 |
for project_id in sorted(project_summaries.keys()) :
|
|
|
126 |
ps = project_summaries[project_id]
|
| 4866 |
tlittlef |
127 |
print ( REPORT_PRINTF_FORMAT % ( project_id, ps[0], ps[1], ps[2], ps[3], ps[4], ps[5] ) )
|
|
|
128 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4380 |
tlittlef |
129 |
ps = overall_summary
|
| 4866 |
tlittlef |
130 |
print ( REPORT_PRINTF_FORMAT % ( "TOTAL", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5] ) )
|
| 4380 |
tlittlef |
131 |
def summarize_for_category(self,category_name, category_types) :
|
|
|
132 |
REPORT_PRINTF_FORMAT = "%-75s %6s %6s"
|
|
|
133 |
REPORT_FORMAT_SEPARATOR = ( '-----------------------','------','------')
|
| 4866 |
tlittlef |
134 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
|
|
135 |
print ( REPORT_PRINTF_FORMAT % ( "%s defects" % (category_name),"count","%") )
|
|
|
136 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4380 |
tlittlef |
137 |
count_for_category = 0
|
|
|
138 |
percent_for_category = 0.0
|
|
|
139 |
for d in sorted(category_types.keys()) :
|
|
|
140 |
count_for_type = category_types[d]
|
|
|
141 |
percent_for_type = int( (1000.0 * count_for_type)/self.total_defects) * 0.1
|
|
|
142 |
count_for_category += count_for_type
|
|
|
143 |
percent_for_category += percent_for_type
|
| 4866 |
tlittlef |
144 |
d = d.replace("\u2013","-")
|
| 4954 |
tlittlef |
145 |
print ( REPORT_PRINTF_FORMAT % (
|
|
|
146 |
d,
|
|
|
147 |
"%6d" % (count_for_type,),
|
|
|
148 |
"%6.1f" % (percent_for_type,),
|
|
|
149 |
) )
|
| 4866 |
tlittlef |
150 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4954 |
tlittlef |
151 |
print ( REPORT_PRINTF_FORMAT % (
|
|
|
152 |
"Total %s" % (category_name,),
|
|
|
153 |
"%6d" % (count_for_category,),
|
|
|
154 |
"%6.1f" % (percent_for_category,),
|
|
|
155 |
) )
|
| 4866 |
tlittlef |
156 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4380 |
tlittlef |
157 |
def summarize_by_defect_type(self) :
|
|
|
158 |
self.summarize_for_category("maintenance",self.maintenance_defects)
|
|
|
159 |
self.summarize_for_category("runtime",self.runtime_defects)
|
|
|
160 |
self.summarize_for_category("security",self.security_defects)
|
|
|
161 |
def report_on_unclassified_defects(self) :
|
| 5144 |
tlittlef |
162 |
trace ( "Reviews with unclassified defects: %s" % (self.unclassified_defect_reviews,) )
|
| 4380 |
tlittlef |
163 |
|
|
|
164 |
if __name__ == "__main__" :
|
|
|
165 |
extractor = CrucibleExtractor()
|
| 4420 |
tlittlef |
166 |
|
| 5298 |
tlittlef |
167 |
if len(sys.argv)>=2 :
|
|
|
168 |
if sys.argv[1] == "--usage" :
|
|
|
169 |
trace ( "crucible_reporting.py YYYY-MM" )
|
|
|
170 |
trace ( " report stats for specified month" )
|
|
|
171 |
sys.exit(0)
|
|
|
172 |
elif sys.argv[1] == "--verbose" :
|
|
|
173 |
extractor.verbose = True
|
|
|
174 |
sys.argv = sys.argv[1:]
|
|
|
175 |
|
|
|
176 |
if len(sys.argv)<2 :
|
| 4381 |
tlittlef |
177 |
today = datetime.date.today()
|
|
|
178 |
if today.month!= 1 :
|
|
|
179 |
month_prefix = "%04d-%02d" % (today.year, today.month-1)
|
|
|
180 |
else :
|
|
|
181 |
month_prefix = "%04d-%02d" % (today.year-1, 12)
|
| 5144 |
tlittlef |
182 |
trace ( "Generating report for previous month (" + month_prefix + ")" )
|
| 4381 |
tlittlef |
183 |
else :
|
|
|
184 |
month_prefix = sys.argv[1]
|
| 5298 |
tlittlef |
185 |
trace ( "Generating report for " + month_prefix )
|
| 4381 |
tlittlef |
186 |
|
| 5298 |
tlittlef |
187 |
sys.stdout = open("%s.txt"%(month_prefix,),"w")
|
| 4381 |
tlittlef |
188 |
extractor.summarize_by_project(month_prefix)
|
| 4380 |
tlittlef |
189 |
extractor.summarize_by_defect_type()
|
|
|
190 |
extractor.report_on_unclassified_defects()
|
|
|
191 |
|
|
|
192 |
|