summaryrefslogtreecommitdiffstats
path: root/scripts/buildstats-diff
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/buildstats-diff')
-rwxr-xr-xscripts/buildstats-diff99
1 files changed, 73 insertions, 26 deletions
diff --git a/scripts/buildstats-diff b/scripts/buildstats-diff
index c5c48d36b5..f918a6d5e0 100755
--- a/scripts/buildstats-diff
+++ b/scripts/buildstats-diff
@@ -158,26 +158,15 @@ def read_buildstats_dir(bs_dir):
158 epoch = match.group('epoch') 158 epoch = match.group('epoch')
159 return name, epoch, version, revision 159 return name, epoch, version, revision
160 160
161 if os.path.isfile(os.path.join(bs_dir, 'build_stats')): 161 if not os.path.isfile(os.path.join(bs_dir, 'build_stats')):
162 top_dir = bs_dir 162 raise ScriptError("{} does not look like a buildstats directory".format(bs_dir))
163 elif os.path.exists(bs_dir):
164 subdirs = sorted(glob.glob(bs_dir + '/*'))
165 if len(subdirs) > 1:
166 log.warning("Multiple buildstats found, using the first one")
167 top_dir = subdirs[0]
168 else:
169 raise ScriptError("No such directory: {}".format(bs_dir))
170 log.debug("Reading buildstats directory %s", top_dir)
171 subdirs = os.listdir(top_dir)
172 163
173 # Handle "old" style directory structure 164 log.debug("Reading buildstats directory %s", bs_dir)
174 if len(subdirs) == 1 and re.match('^20[0-9]{12}$', subdirs[0]):
175 top_dir = os.path.join(top_dir, subdirs[0])
176 subdirs = os.listdir(top_dir)
177 165
178 buildstats = {} 166 buildstats = {}
167 subdirs = os.listdir(bs_dir)
179 for dirname in subdirs: 168 for dirname in subdirs:
180 recipe_dir = os.path.join(top_dir, dirname) 169 recipe_dir = os.path.join(bs_dir, dirname)
181 if not os.path.isdir(recipe_dir): 170 if not os.path.isdir(recipe_dir):
182 continue 171 continue
183 name, epoch, version, revision = split_nevr(dirname) 172 name, epoch, version, revision = split_nevr(dirname)
@@ -188,8 +177,8 @@ def read_buildstats_dir(bs_dir):
188 'revision': revision, 177 'revision': revision,
189 'tasks': {}} 178 'tasks': {}}
190 for task in os.listdir(recipe_dir): 179 for task in os.listdir(recipe_dir):
191 recipe_bs['tasks'][task] = read_buildstats_file( 180 recipe_bs['tasks'][task] = [read_buildstats_file(
192 os.path.join(recipe_dir, task)) 181 os.path.join(recipe_dir, task))]
193 if name in buildstats: 182 if name in buildstats:
194 raise ScriptError("Cannot handle multiple versions of the same " 183 raise ScriptError("Cannot handle multiple versions of the same "
195 "package ({})".format(name)) 184 "package ({})".format(name))
@@ -198,6 +187,22 @@ def read_buildstats_dir(bs_dir):
198 return buildstats 187 return buildstats
199 188
200 189
190def bs_append(dst, src):
191 """Append data from another buildstats"""
192 if set(dst.keys()) != set(src.keys()):
193 raise ScriptError("Refusing to join buildstats, set of packages is "
194 "different")
195 for pkg, data in dst.items():
196 if data['nevr'] != src[pkg]['nevr']:
197 raise ScriptError("Refusing to join buildstats, package version "
198 "differs: {} vs. {}".format(data['nevr'], src[pkg]['nevr']))
199 if set(data['tasks'].keys()) != set(src[pkg]['tasks'].keys()):
200 raise ScriptError("Refusing to join buildstats, set of tasks "
201 "in {} differ".format(pkg))
202 for taskname, taskdata in data['tasks'].items():
203 taskdata.extend(src[pkg]['tasks'][taskname])
204
205
201def read_buildstats_json(path): 206def read_buildstats_json(path):
202 """Read buildstats from JSON file""" 207 """Read buildstats from JSON file"""
203 buildstats = {} 208 buildstats = {}
@@ -214,20 +219,50 @@ def read_buildstats_json(path):
214 recipe_bs['nevr'] = "{}-{}_{}-{}".format(recipe_bs['name'], recipe_bs['epoch'], recipe_bs['version'], recipe_bs['revision']) 219 recipe_bs['nevr'] = "{}-{}_{}-{}".format(recipe_bs['name'], recipe_bs['epoch'], recipe_bs['version'], recipe_bs['revision'])
215 220
216 for task, data in recipe_bs['tasks'].copy().items(): 221 for task, data in recipe_bs['tasks'].copy().items():
217 recipe_bs['tasks'][task] = BSTask(data) 222 recipe_bs['tasks'][task] = [BSTask(data)]
218 223
219 buildstats[recipe_bs['name']] = recipe_bs 224 buildstats[recipe_bs['name']] = recipe_bs
220 225
221 return buildstats 226 return buildstats
222 227
223 228
224def read_buildstats(path): 229def read_buildstats(path, multi):
225 """Read buildstats""" 230 """Read buildstats"""
231 if not os.path.exists(path):
232 raise ScriptError("No such file or directory: {}".format(path))
233
226 if os.path.isfile(path): 234 if os.path.isfile(path):
227 return read_buildstats_json(path) 235 return read_buildstats_json(path)
228 else: 236
237 if os.path.isfile(os.path.join(path, 'build_stats')):
229 return read_buildstats_dir(path) 238 return read_buildstats_dir(path)
230 239
240 # Handle a non-buildstat directory
241 subpaths = sorted(glob.glob(path + '/*'))
242 if len(subpaths) > 1:
243 if multi:
244 log.info("Averaging over {} buildstats from {}".format(
245 len(subpaths), path))
246 else:
247 raise ScriptError("Multiple buildstats found in '{}'. Please give "
248 "a single buildstat directory of use the --multi "
249 "option".format(path))
250 bs = None
251 for subpath in subpaths:
252 if os.path.isfile(subpath):
253 tmpbs = read_buildstats_json(subpath)
254 else:
255 tmpbs = read_buildstats_dir(subpath)
256 if not bs:
257 bs = tmpbs
258 else:
259 log.debug("Joining buildstats")
260 bs_append(bs, tmpbs)
261
262 if not bs:
263 raise ScriptError("No buildstats found under {}".format(path))
264 return bs
265
231 266
232def print_ver_diff(bs1, bs2): 267def print_ver_diff(bs1, bs2):
233 """Print package version differences""" 268 """Print package version differences"""
@@ -337,7 +372,7 @@ def print_task_diff(bs1, bs2, val_type, min_val=0, min_absdiff=0, sort_by=('absd
337 total = 0.0 372 total = 0.0
338 for recipe_data in buildstats.values(): 373 for recipe_data in buildstats.values():
339 for bs_task in recipe_data['tasks'].values(): 374 for bs_task in recipe_data['tasks'].values():
340 total += getattr(bs_task, val_type) 375 total += sum([getattr(b, val_type) for b in bs_task]) / len(bs_task)
341 return total 376 return total
342 377
343 tasks_diff = [] 378 tasks_diff = []
@@ -364,12 +399,16 @@ def print_task_diff(bs1, bs2, val_type, min_val=0, min_absdiff=0, sort_by=('absd
364 for task in set(tasks1.keys()).union(set(tasks2.keys())): 399 for task in set(tasks1.keys()).union(set(tasks2.keys())):
365 task_op = ' ' 400 task_op = ' '
366 if task in tasks1: 401 if task in tasks1:
367 val1 = getattr(bs1[pkg]['tasks'][task], val_type) 402 # Average over all values
403 val1 = [getattr(b, val_type) for b in bs1[pkg]['tasks'][task]]
404 val1 = sum(val1) / len(val1)
368 else: 405 else:
369 task_op = '+ ' 406 task_op = '+ '
370 val1 = 0 407 val1 = 0
371 if task in tasks2: 408 if task in tasks2:
372 val2 = getattr(bs2[pkg]['tasks'][task], val_type) 409 # Average over all values
410 val2 = [getattr(b, val_type) for b in bs2[pkg]['tasks'][task]]
411 val2 = sum(val2) / len(val2)
373 else: 412 else:
374 val2 = 0 413 val2 = 0
375 task_op = '- ' 414 task_op = '- '
@@ -470,11 +509,19 @@ Script for comparing buildstats of two separate builds."""
470 help="Comma-separated list of field sort order. " 509 help="Comma-separated list of field sort order. "
471 "Prepend the field name with '-' for reversed sort. " 510 "Prepend the field name with '-' for reversed sort. "
472 "Available fields are: {}".format(', '.join(taskdiff_fields))) 511 "Available fields are: {}".format(', '.join(taskdiff_fields)))
512 parser.add_argument('--multi', action='store_true',
513 help="Read all buildstats from the given paths and "
514 "average over them")
473 parser.add_argument('buildstats1', metavar='BUILDSTATS1', help="'Left' buildstat") 515 parser.add_argument('buildstats1', metavar='BUILDSTATS1', help="'Left' buildstat")
474 parser.add_argument('buildstats2', metavar='BUILDSTATS2', help="'Right' buildstat") 516 parser.add_argument('buildstats2', metavar='BUILDSTATS2', help="'Right' buildstat")
475 517
476 args = parser.parse_args(argv) 518 args = parser.parse_args(argv)
477 519
520 # We do not nedd/want to read all buildstats if we just want to look at the
521 # package versions
522 if args.ver_diff:
523 args.multi = False
524
478 # Handle defaults for the filter arguments 525 # Handle defaults for the filter arguments
479 if args.min_val is min_val_defaults: 526 if args.min_val is min_val_defaults:
480 args.min_val = min_val_defaults[args.diff_attr] 527 args.min_val = min_val_defaults[args.diff_attr]
@@ -500,8 +547,8 @@ def main(argv=None):
500 sort_by.append(field) 547 sort_by.append(field)
501 548
502 try: 549 try:
503 bs1 = read_buildstats(args.buildstats1) 550 bs1 = read_buildstats(args.buildstats1, args.multi)
504 bs2 = read_buildstats(args.buildstats2) 551 bs2 = read_buildstats(args.buildstats2, args.multi)
505 552
506 if args.ver_diff: 553 if args.ver_diff:
507 print_ver_diff(bs1, bs2) 554 print_ver_diff(bs1, bs2)