diff options
author | Markus Lehtonen <markus.lehtonen@linux.intel.com> | 2017-09-15 16:04:38 +0300 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2017-09-18 11:07:30 +0100 |
commit | 81aef784fdbd2e8a543475b1892c3d6a1fe97872 (patch) | |
tree | d787e9402d3fa667eee19a4447f46b440e7eda5c | |
parent | b5fb3dd904cd22212c720f3c79d71952ecbaa9c2 (diff) | |
download | poky-81aef784fdbd2e8a543475b1892c3d6a1fe97872.tar.gz |
scripts/oe-build-perf-report: summary of task resource usage
Utilize buildstats, if available, and show a summary of the resource
usage of bitbake tasks in the html report. The details provided are:
- total number of tasks
- top 5 resource-hungry tasks (cputime)
- top 5 increase in resource usage (cputime)
- top 5 decrease in resource usage (cputime)
[YOCTO #11381]
(From OE-Core rev: ddd9443cb2432af2c15b358bfda708393fa3c417)
Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
Signed-off-by: Ross Burton <ross.burton@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r-- | scripts/lib/build_perf/html/report.html | 60 | ||||
-rw-r--r-- | scripts/lib/buildstats.py | 8 | ||||
-rwxr-xr-x | scripts/oe-build-perf-report | 90 |
3 files changed, 133 insertions, 25 deletions
diff --git a/scripts/lib/build_perf/html/report.html b/scripts/lib/build_perf/html/report.html index f983a4a09c..e56186c958 100644 --- a/scripts/lib/build_perf/html/report.html +++ b/scripts/lib/build_perf/html/report.html | |||
@@ -53,9 +53,11 @@ summary th, .meta-table td { | |||
53 | border-collapse: collapse; | 53 | border-collapse: collapse; |
54 | } | 54 | } |
55 | .details th { | 55 | .details th { |
56 | font-weight: normal; | ||
57 | padding-right: 8px; | 56 | padding-right: 8px; |
58 | } | 57 | } |
58 | .details.plain th { | ||
59 | font-weight: normal; | ||
60 | } | ||
59 | .preformatted { | 61 | .preformatted { |
60 | font-family: monospace; | 62 | font-family: monospace; |
61 | white-space: pre-wrap; | 63 | white-space: pre-wrap; |
@@ -168,6 +170,7 @@ h3 { | |||
168 | {{ measurement.absdiff_str }} ({{measurement.reldiff}}) | 170 | {{ measurement.absdiff_str }} ({{measurement.reldiff}}) |
169 | </span></span> | 171 | </span></span> |
170 | </div> | 172 | </div> |
173 | {# Table for trendchart and the statistics #} | ||
171 | <table style="width: 100%"> | 174 | <table style="width: 100%"> |
172 | <tr> | 175 | <tr> |
173 | <td style="width: 75%"> | 176 | <td style="width: 75%"> |
@@ -176,7 +179,7 @@ h3 { | |||
176 | </td> | 179 | </td> |
177 | <td> | 180 | <td> |
178 | {# Measurement statistics #} | 181 | {# Measurement statistics #} |
179 | <table class="details"> | 182 | <table class="details plain"> |
180 | <tr> | 183 | <tr> |
181 | <th>Test runs</th><td>{{ measurement.value.sample_cnt }}</td> | 184 | <th>Test runs</th><td>{{ measurement.value.sample_cnt }}</td> |
182 | </tr><tr> | 185 | </tr><tr> |
@@ -195,6 +198,59 @@ h3 { | |||
195 | </td> | 198 | </td> |
196 | </tr> | 199 | </tr> |
197 | </table> | 200 | </table> |
201 | |||
202 | {# Task and recipe summary from buildstats #} | ||
203 | {% if 'buildstats' in measurement %} | ||
204 | Task resource usage | ||
205 | <table class="details" style="width:100%"> | ||
206 | <tr> | ||
207 | <th>Number of tasks</th> | ||
208 | <th>Top consumers of cputime</th> | ||
209 | </tr> | ||
210 | <tr> | ||
211 | <td style="vertical-align: top">{{ measurement.buildstats.tasks.count }} ({{ measurement.buildstats.tasks.change }})</td> | ||
212 | {# Table of most resource-hungry tasks #} | ||
213 | <td> | ||
214 | <table class="details plain"> | ||
215 | {% for diff in measurement.buildstats.top_consumer|reverse %} | ||
216 | <tr> | ||
217 | <th>{{ diff.pkg }}.{{ diff.task }}</th> | ||
218 | <td>{{ '%0.0f' % diff.value2 }} s</td> | ||
219 | </tr> | ||
220 | {% endfor %} | ||
221 | </table> | ||
222 | </td> | ||
223 | </tr> | ||
224 | <tr> | ||
225 | <th>Biggest increase in cputime</th> | ||
226 | <th>Biggest decrease in cputime</th> | ||
227 | </tr> | ||
228 | <tr> | ||
229 | {# Table biggest increase in resource usage #} | ||
230 | <td> | ||
231 | <table class="details plain"> | ||
232 | {% for diff in measurement.buildstats.top_increase|reverse %} | ||
233 | <tr> | ||
234 | <th>{{ diff.pkg }}.{{ diff.task }}</th> | ||
235 | <td>{{ '%+0.0f' % diff.absdiff }} s</td> | ||
236 | </tr> | ||
237 | {% endfor %} | ||
238 | </table> | ||
239 | </td> | ||
240 | {# Table biggest decrease in resource usage #} | ||
241 | <td> | ||
242 | <table class="details plain"> | ||
243 | {% for diff in measurement.buildstats.top_decrease %} | ||
244 | <tr> | ||
245 | <th>{{ diff.pkg }}.{{ diff.task }}</th> | ||
246 | <td>{{ '%+0.0f' % diff.absdiff }} s</td> | ||
247 | </tr> | ||
248 | {% endfor %} | ||
249 | </table> | ||
250 | </td> | ||
251 | </tr> | ||
252 | </table> | ||
253 | {% endif %} | ||
198 | </div> | 254 | </div> |
199 | {% endfor %} | 255 | {% endfor %} |
200 | {# Unsuccessful test #} | 256 | {# Unsuccessful test #} |
diff --git a/scripts/lib/buildstats.py b/scripts/lib/buildstats.py index 9eb60b1c69..bd6332176a 100644 --- a/scripts/lib/buildstats.py +++ b/scripts/lib/buildstats.py | |||
@@ -180,6 +180,14 @@ class BSRecipe(object): | |||
180 | class BuildStats(dict): | 180 | class BuildStats(dict): |
181 | """Class representing buildstats of one build""" | 181 | """Class representing buildstats of one build""" |
182 | 182 | ||
183 | @property | ||
184 | def num_tasks(self): | ||
185 | """Get number of tasks""" | ||
186 | num = 0 | ||
187 | for recipe in self.values(): | ||
188 | num += len(recipe.tasks) | ||
189 | return num | ||
190 | |||
183 | @classmethod | 191 | @classmethod |
184 | def from_json(cls, bs_json): | 192 | def from_json(cls, bs_json): |
185 | """Create new BuildStats object from JSON object""" | 193 | """Create new BuildStats object from JSON object""" |
diff --git a/scripts/oe-build-perf-report b/scripts/oe-build-perf-report index 8d730cd20a..0b2f730e57 100755 --- a/scripts/oe-build-perf-report +++ b/scripts/oe-build-perf-report | |||
@@ -32,6 +32,7 @@ from build_perf.report import (metadata_xml_to_json, results_xml_to_json, | |||
32 | aggregate_data, aggregate_metadata, measurement_stats, | 32 | aggregate_data, aggregate_metadata, measurement_stats, |
33 | AggregateTestData) | 33 | AggregateTestData) |
34 | from build_perf import html | 34 | from build_perf import html |
35 | from buildstats import BuildStats, diff_buildstats | ||
35 | 36 | ||
36 | scriptpath.add_oe_lib_path() | 37 | scriptpath.add_oe_lib_path() |
37 | 38 | ||
@@ -333,12 +334,31 @@ def print_diff_report(metadata_l, data_l, metadata_r, data_r): | |||
333 | print() | 334 | print() |
334 | 335 | ||
335 | 336 | ||
336 | def print_html_report(data, id_comp): | 337 | class BSSummary(object): |
338 | def __init__(self, bs1, bs2): | ||
339 | self.tasks = {'count': bs2.num_tasks, | ||
340 | 'change': '{:+d}'.format(bs2.num_tasks - bs1.num_tasks)} | ||
341 | self.top_consumer = None | ||
342 | self.top_decrease = None | ||
343 | self.top_increase = None | ||
344 | |||
345 | tasks_diff = diff_buildstats(bs1, bs2, 'cputime') | ||
346 | |||
347 | # Get top consumers of resources | ||
348 | tasks_diff = sorted(tasks_diff, key=attrgetter('value2')) | ||
349 | self.top_consumer = tasks_diff[-5:] | ||
350 | |||
351 | # Get biggest increase and decrease in resource usage | ||
352 | tasks_diff = sorted(tasks_diff, key=attrgetter('absdiff')) | ||
353 | self.top_decrease = tasks_diff[0:5] | ||
354 | self.top_increase = tasks_diff[-5:] | ||
355 | |||
356 | |||
357 | def print_html_report(data, id_comp, buildstats): | ||
337 | """Print report in html format""" | 358 | """Print report in html format""" |
338 | # Handle metadata | 359 | # Handle metadata |
339 | metadata = metadata_diff(data[id_comp].metadata, data[-1].metadata) | 360 | metadata = metadata_diff(data[id_comp].metadata, data[-1].metadata) |
340 | 361 | ||
341 | |||
342 | # Generate list of tests | 362 | # Generate list of tests |
343 | tests = [] | 363 | tests = [] |
344 | for test in data[-1].results['tests'].keys(): | 364 | for test in data[-1].results['tests'].keys(): |
@@ -388,6 +408,16 @@ def print_html_report(data, id_comp): | |||
388 | new_meas['value'] = samples[-1] | 408 | new_meas['value'] = samples[-1] |
389 | new_meas['value_type'] = samples[-1]['val_cls'] | 409 | new_meas['value_type'] = samples[-1]['val_cls'] |
390 | 410 | ||
411 | # Compare buildstats | ||
412 | bs_key = test + '.' + meas | ||
413 | rev = metadata['commit_num']['value'] | ||
414 | comp_rev = metadata['commit_num']['value_old'] | ||
415 | if (rev in buildstats and bs_key in buildstats[rev] and | ||
416 | comp_rev in buildstats and bs_key in buildstats[comp_rev]): | ||
417 | new_meas['buildstats'] = BSSummary(buildstats[comp_rev][bs_key], | ||
418 | buildstats[rev][bs_key]) | ||
419 | |||
420 | |||
391 | new_test['measurements'].append(new_meas) | 421 | new_test['measurements'].append(new_meas) |
392 | tests.append(new_test) | 422 | tests.append(new_test) |
393 | 423 | ||
@@ -401,8 +431,8 @@ def print_html_report(data, id_comp): | |||
401 | chart_opts=chart_opts)) | 431 | chart_opts=chart_opts)) |
402 | 432 | ||
403 | 433 | ||
404 | def dump_buildstats(repo, outdir, notes_ref, revs): | 434 | def get_buildstats(repo, notes_ref, revs, outdir=None): |
405 | """Dump buildstats of test results""" | 435 | """Get the buildstats from git notes""" |
406 | full_ref = 'refs/notes/' + notes_ref | 436 | full_ref = 'refs/notes/' + notes_ref |
407 | if not repo.rev_parse(full_ref): | 437 | if not repo.rev_parse(full_ref): |
408 | log.error("No buildstats found, please try running " | 438 | log.error("No buildstats found, please try running " |
@@ -411,9 +441,10 @@ def dump_buildstats(repo, outdir, notes_ref, revs): | |||
411 | return | 441 | return |
412 | 442 | ||
413 | missing = False | 443 | missing = False |
414 | log.info("Writing out buildstats from 'refs/notes/%s' into '%s'", | 444 | buildstats = {} |
415 | notes_ref, outdir) | 445 | log.info("Parsing buildstats from 'refs/notes/%s'", notes_ref) |
416 | for rev in revs: | 446 | for rev in revs: |
447 | buildstats[rev.commit_number] = {} | ||
417 | log.debug('Dumping buildstats for %s (%s)', rev.commit_number, | 448 | log.debug('Dumping buildstats for %s (%s)', rev.commit_number, |
418 | rev.commit) | 449 | rev.commit) |
419 | for tag in rev.tags: | 450 | for tag in rev.tags: |
@@ -425,19 +456,32 @@ def dump_buildstats(repo, outdir, notes_ref, revs): | |||
425 | log.warning("Buildstats not found for %s", tag) | 456 | log.warning("Buildstats not found for %s", tag) |
426 | bs_all = {} | 457 | bs_all = {} |
427 | missing = True | 458 | missing = True |
428 | for measurement, buildstats in bs_all.items(): | 459 | |
429 | tag_base, run_id = tag.rsplit('/', 1) | 460 | for measurement, bs in bs_all.items(): |
430 | tag_base = tag_base.replace('/', '_') | 461 | # Write out onto disk |
431 | bs_dir = os.path.join(outdir, measurement, tag_base) | 462 | if outdir: |
432 | if not os.path.exists(bs_dir): | 463 | tag_base, run_id = tag.rsplit('/', 1) |
433 | os.makedirs(bs_dir) | 464 | tag_base = tag_base.replace('/', '_') |
434 | with open(os.path.join(bs_dir, run_id + '.json'), 'w') as f: | 465 | bs_dir = os.path.join(outdir, measurement, tag_base) |
435 | json.dump(buildstats, f, indent=2) | 466 | if not os.path.exists(bs_dir): |
467 | os.makedirs(bs_dir) | ||
468 | with open(os.path.join(bs_dir, run_id + '.json'), 'w') as f: | ||
469 | json.dump(bs, f, indent=2) | ||
470 | |||
471 | # Read buildstats into a dict | ||
472 | _bs = BuildStats.from_json(bs) | ||
473 | if measurement not in buildstats[rev.commit_number]: | ||
474 | buildstats[rev.commit_number][measurement] = _bs | ||
475 | else: | ||
476 | buildstats[rev.commit_number][measurement].aggregate(_bs) | ||
477 | |||
436 | if missing: | 478 | if missing: |
437 | log.info("Buildstats were missing for some test runs, please " | 479 | log.info("Buildstats were missing for some test runs, please " |
438 | "run 'git fetch origin %s:%s' and try again", | 480 | "run 'git fetch origin %s:%s' and try again", |
439 | full_ref, full_ref) | 481 | full_ref, full_ref) |
440 | 482 | ||
483 | return buildstats | ||
484 | |||
441 | 485 | ||
442 | def auto_args(repo, args): | 486 | def auto_args(repo, args): |
443 | """Guess arguments, if not defined by the user""" | 487 | """Guess arguments, if not defined by the user""" |
@@ -584,20 +628,20 @@ def main(argv=None): | |||
584 | index_r = index_r - index_0 | 628 | index_r = index_r - index_0 |
585 | index_l = index_l - index_0 | 629 | index_l = index_l - index_0 |
586 | 630 | ||
631 | # Read buildstats only when needed | ||
632 | buildstats = None | ||
633 | if args.dump_buildstats or args.html: | ||
634 | outdir = 'oe-build-perf-buildstats' if args.dump_buildstats else None | ||
635 | notes_ref = 'buildstats/{}/{}/{}'.format(args.hostname, args.branch, | ||
636 | args.machine) | ||
637 | buildstats = get_buildstats(repo, notes_ref, [rev_l, rev_r], outdir) | ||
638 | |||
587 | # Print report | 639 | # Print report |
588 | if not args.html: | 640 | if not args.html: |
589 | print_diff_report(data[index_l].metadata, data[index_l].results, | 641 | print_diff_report(data[index_l].metadata, data[index_l].results, |
590 | data[index_r].metadata, data[index_r].results) | 642 | data[index_r].metadata, data[index_r].results) |
591 | else: | 643 | else: |
592 | print_html_report(data, index_l) | 644 | print_html_report(data, index_l, buildstats) |
593 | |||
594 | # Dump buildstats | ||
595 | if args.dump_buildstats: | ||
596 | notes_ref = 'buildstats/{}/{}/{}'.format(args.hostname, args.branch, | ||
597 | args.machine) | ||
598 | dump_buildstats(repo, 'oe-build-perf-buildstats', notes_ref, | ||
599 | [rev_l, rev_r]) | ||
600 | #revs_l.tags + revs_r.tags) | ||
601 | 645 | ||
602 | return 0 | 646 | return 0 |
603 | 647 | ||