summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorMarkus Lehtonen <markus.lehtonen@linux.intel.com>2017-03-31 17:07:30 +0300
committerRichard Purdie <richard.purdie@linuxfoundation.org>2017-04-01 08:20:26 +0100
commit9e97ff174458f7245fc27a4c407f21a9d2e317ab (patch)
treec6ffafd3e4698787e897110583ee48ead663d49c /scripts
parent3b25404f0f99b72f222bdca815929be1cf1cee35 (diff)
downloadopenembedded-core-9e97ff174458f7245fc27a4c407f21a9d2e317ab.tar.gz
openembedded-core-9e97ff174458f7245fc27a4c407f21a9d2e317ab.tar.bz2
openembedded-core-9e97ff174458f7245fc27a4c407f21a9d2e317ab.zip
scripts/contrib: add oe-build-perf-report-email
Script for sending build perf test reports as an email. Mangles an html report, generated by oe-build-perf-report, into a format suitable for html emails. Supports multipart emails where a plaintext alternative can be included in the same email. Dependencies required to be installed on the host: - phantomjs - optipng [YOCTO #10931] Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/contrib/oe-build-perf-report-email.py266
-rw-r--r--scripts/lib/build_perf/scrape-html-report.js56
2 files changed, 322 insertions, 0 deletions
diff --git a/scripts/contrib/oe-build-perf-report-email.py b/scripts/contrib/oe-build-perf-report-email.py
new file mode 100755
index 0000000000..7f4274efed
--- /dev/null
+++ b/scripts/contrib/oe-build-perf-report-email.py
@@ -0,0 +1,266 @@
+#!/usr/bin/python3
+#
+# Send build performance test report emails
+#
+# Copyright (c) 2017, Intel Corporation.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU General Public License,
+# version 2, as published by the Free Software Foundation.
+#
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+import argparse
+import base64
+import logging
+import os
+import pwd
+import re
+import shutil
+import smtplib
+import subprocess
+import sys
+import tempfile
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+
+
+# Setup logging
+logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
+log = logging.getLogger('oe-build-perf-report')
+
+
+# Find js scaper script
+SCRAPE_JS = os.path.join(os.path.dirname(__file__), '..', 'lib', 'build_perf',
+ 'scrape-html-report.js')
+if not os.path.isfile(SCRAPE_JS):
+ log.error("Unableto find oe-build-perf-report-scrape.js")
+ sys.exit(1)
+
+
+class ReportError(Exception):
+ """Local errors"""
+ pass
+
+
+def check_utils():
+ """Check that all needed utils are installed in the system"""
+ missing = []
+ for cmd in ('phantomjs', 'optipng'):
+ if not shutil.which(cmd):
+ missing.append(cmd)
+ if missing:
+ log.error("The following tools are missing: %s", ' '.join(missing))
+ sys.exit(1)
+
+
+def parse_args(argv):
+ """Parse command line arguments"""
+ description = """Email build perf test report"""
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ description=description)
+
+ parser.add_argument('--debug', '-d', action='store_true',
+ help="Verbose logging")
+ parser.add_argument('--quiet', '-q', action='store_true',
+ help="Only print errors")
+ parser.add_argument('--to', action='append',
+ help="Recipients of the email")
+ parser.add_argument('--subject', default="Yocto build perf test report",
+ help="Email subject")
+ parser.add_argument('--outdir', '-o',
+ help="Store files in OUTDIR. Can be used to preserve "
+ "the email parts")
+ parser.add_argument('--text',
+ help="Plain text message")
+ parser.add_argument('--html',
+ help="HTML peport generated by oe-build-perf-report")
+ parser.add_argument('--phantomjs-args', action='append',
+ help="Extra command line arguments passed to PhantomJS")
+
+ args = parser.parse_args(argv)
+
+ if not args.html and not args.text:
+ parser.error("Please specify --html and/or --text")
+
+ return args
+
+
+def decode_png(infile, outfile):
+ """Parse/decode/optimize png data from a html element"""
+ with open(infile) as f:
+ raw_data = f.read()
+
+ # Grab raw base64 data
+ b64_data = re.sub('^.*href="data:image/png;base64,', '', raw_data, 1)
+ b64_data = re.sub('">.+$', '', b64_data, 1)
+
+ # Replace file with proper decoded png
+ with open(outfile, 'wb') as f:
+ f.write(base64.b64decode(b64_data))
+
+ subprocess.check_output(['optipng', outfile], stderr=subprocess.STDOUT)
+
+
+def encode_png(pngfile):
+ """Encode png into a <img> html element"""
+ with open(pngfile, 'rb') as f:
+ data = f.read()
+
+ b64_data = base64.b64encode(data)
+ return '<img src="data:image/png;base64,' + b64_data.decode('utf-8') + '">\n'
+
+
+def mangle_html_report(infile, outfile, pngs):
+ """Mangle html file into a email compatible format"""
+ paste = True
+ png_dir = os.path.dirname(outfile)
+ with open(infile) as f_in:
+ with open(outfile, 'w') as f_out:
+ for line in f_in.readlines():
+ stripped = line.strip()
+ # Strip out scripts
+ if stripped == '<!--START-OF-SCRIPTS-->':
+ paste = False
+ elif stripped == '<!--END-OF-SCRIPTS-->':
+ paste = True
+ elif paste:
+ if re.match('^.+href="data:image/png;base64', stripped):
+ # Strip out encoded pngs (as they're huge in size)
+ continue
+ elif 'www.gstatic.com' in stripped:
+ # HACK: drop references to external static pages
+ continue
+
+ # Replace charts with <img> elements
+ match = re.match('<div id="(?P<id>\w+)"', stripped)
+ if match and match.group('id') in pngs:
+ #f_out.write('<img src="{}">\n'.format(match.group('id') + '.png'))
+ png_file = os.path.join(png_dir, match.group('id') + '.png')
+ f_out.write(encode_png(png_file))
+ else:
+ f_out.write(line)
+
+
+def scrape_html_report(report, outdir, phantomjs_extra_args=None):
+ """Scrape html report into a format sendable by email"""
+ tmpdir = tempfile.mkdtemp(dir='.')
+ log.debug("Using tmpdir %s for phantomjs output", tmpdir)
+
+ if not os.path.isdir(outdir):
+ os.mkdir(outdir)
+ if os.path.splitext(report)[1] not in ('.html', '.htm'):
+ raise ReportError("Invalid file extension for report, needs to be "
+ "'.html' or '.htm'")
+
+ try:
+ log.info("Scraping HTML report with PhangomJS")
+ extra_args = phantomjs_extra_args if phantomjs_extra_args else []
+ subprocess.check_output(['phantomjs', '--debug=true'] + extra_args +
+ [SCRAPE_JS, report, tmpdir],
+ stderr=subprocess.STDOUT)
+
+ pngs = []
+ attachments = []
+ for fname in os.listdir(tmpdir):
+ base, ext = os.path.splitext(fname)
+ if ext == '.png':
+ log.debug("Decoding %s", fname)
+ decode_png(os.path.join(tmpdir, fname),
+ os.path.join(outdir, fname))
+ pngs.append(base)
+ attachments.append(fname)
+ elif ext in ('.html', '.htm'):
+ report_file = fname
+ else:
+ log.warning("Unknown file extension: '%s'", ext)
+ #shutil.move(os.path.join(tmpdir, fname), outdir)
+
+ log.debug("Mangling html report file %s", report_file)
+ mangle_html_report(os.path.join(tmpdir, report_file),
+ os.path.join(outdir, report_file), pngs)
+ return report_file, attachments
+ finally:
+ shutil.rmtree(tmpdir)
+
+def send_email(text_fn, html_fn, subject, recipients):
+ """Send email"""
+ # Generate email message
+ text_msg = html_msg = None
+ if text_fn:
+ with open(text_fn) as f:
+ text_msg = MIMEText("Yocto build performance test report.\n" +
+ f.read(), 'plain')
+ if html_fn:
+ with open(html_fn) as f:
+ html_msg = MIMEText(f.read(), 'html')
+
+ if text_msg and html_msg:
+ msg = MIMEMultipart('alternative')
+ msg.attach(text_msg)
+ msg.attach(html_msg)
+ elif text_msg:
+ msg = text_msg
+ elif html_msg:
+ msg = html_msg
+ else:
+ raise ReportError("Neither plain text nor html body specified")
+
+ full_name = pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+ email = os.environ.get('EMAIL', os.getlogin())
+ msg['From'] = "{} <{}>".format(full_name, email)
+ msg['To'] = ', '.join(recipients)
+ msg['Subject'] = subject
+
+ # Send email
+ with smtplib.SMTP('localhost') as smtp:
+ smtp.send_message(msg)
+
+
+def main(argv=None):
+ """Script entry point"""
+ args = parse_args(argv)
+ if args.quiet:
+ log.setLevel(logging.ERROR)
+ if args.debug:
+ log.setLevel(logging.DEBUG)
+
+ check_utils()
+
+ if args.outdir:
+ outdir = args.outdir
+ if not os.path.exists(outdir):
+ os.mkdir(outdir)
+ else:
+ outdir = tempfile.mkdtemp(dir='.')
+
+ try:
+ log.debug("Storing email parts in %s", outdir)
+ html_report = None
+ if args.html:
+ scrape_html_report(args.html, outdir, args.phantomjs_args)
+ html_report = os.path.join(outdir, args.html)
+
+ if args.to:
+ log.info("Sending email to %s", ', '.join(args.to))
+ send_email(args.text, html_report, args.subject, args.to)
+ except subprocess.CalledProcessError as err:
+ log.error("%s, with output:\n%s", str(err), err.output.decode())
+ return 1
+ except ReportError as err:
+ log.error(err)
+ return 1
+ finally:
+ if not args.outdir:
+ log.debug("Wiping %s", outdir)
+ shutil.rmtree(outdir)
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/lib/build_perf/scrape-html-report.js b/scripts/lib/build_perf/scrape-html-report.js
new file mode 100644
index 0000000000..05a1f57001
--- /dev/null
+++ b/scripts/lib/build_perf/scrape-html-report.js
@@ -0,0 +1,56 @@
+var fs = require('fs');
+var system = require('system');
+var page = require('webpage').create();
+
+// Examine console log for message from chart drawing
+page.onConsoleMessage = function(msg) {
+ console.log(msg);
+ if (msg === "ALL CHARTS READY") {
+ window.charts_ready = true;
+ }
+ else if (msg.slice(0, 11) === "CHART READY") {
+ var chart_id = msg.split(" ")[2];
+ console.log('grabbing ' + chart_id);
+ var png_data = page.evaluate(function (chart_id) {
+ var chart_div = document.getElementById(chart_id + '_png');
+ return chart_div.outerHTML;
+ }, chart_id);
+ fs.write(args[2] + '/' + chart_id + '.png', png_data, 'w');
+ }
+};
+
+// Check command line arguments
+var args = system.args;
+if (args.length != 3) {
+ console.log("USAGE: " + args[0] + " REPORT_HTML OUT_DIR\n");
+ phantom.exit(1);
+}
+
+// Open the web page
+page.open(args[1], function(status) {
+ if (status == 'fail') {
+ console.log("Failed to open file '" + args[1] + "'");
+ phantom.exit(1);
+ }
+});
+
+// Check status every 100 ms
+interval = window.setInterval(function () {
+ //console.log('waiting');
+ if (window.charts_ready) {
+ clearTimeout(timer);
+ clearInterval(interval);
+
+ var fname = args[1].replace(/\/+$/, "").split("/").pop()
+ console.log("saving " + fname);
+ fs.write(args[2] + '/' + fname, page.content, 'w');
+ phantom.exit(0);
+ }
+}, 100);
+
+// Time-out after 10 seconds
+timer = window.setTimeout(function () {
+ clearInterval(interval);
+ console.log("ERROR: timeout");
+ phantom.exit(1);
+}, 10000);