Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(142)

Unified Diff: chrome/test/chromedriver/test/waterfall_builder_monitor.py

Issue 633213002: Waterfall builder monitoring script. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/test/chromedriver/test/waterfall_builder_config_sample.json ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/test/chromedriver/test/waterfall_builder_monitor.py
diff --git a/chrome/test/chromedriver/test/waterfall_builder_monitor.py b/chrome/test/chromedriver/test/waterfall_builder_monitor.py
new file mode 100755
index 0000000000000000000000000000000000000000..13537cdfe2e7a7c548ba439a513e4cd3ea398c52
--- /dev/null
+++ b/chrome/test/chromedriver/test/waterfall_builder_monitor.py
@@ -0,0 +1,226 @@
+#!/usr/bin/env python
+# Copyright (c) 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Waterfall monitoring script.
+ This script checks all builders specified in the config file and sends
+ status email about any step failures in these builders. This also
+ reports a build as failure if the latest build on that builder was built
+ 2 days back. (Number of days can be configured in the config file)
+
+ This script can be run as cronjob on a linux machine once a day and
+ get email notification for any waterfall specified in the config file.
+
+ Sample cronjob entry below. This entry will run the script everyday at 9 AM.
+ Include this in the crontab file.
+ 0 9 * * * <Path to script> --config <Path to json file>
+"""
+
+import datetime
+import json
+import optparse
+import sys
+import time
+import traceback
+import urllib
+
+from datetime import timedelta
+from email.mime.text import MIMEText
+from subprocess import Popen, PIPE
+
+
+SUCCESS_SUBJECT = ('[CHROME TESTING]: Builder status %s: PASSED.')
+FAILURE_SUBJECT = ('[CHROME TESTING]: Builder status %s: FAILED %d out of %d')
+EXCEPTION_SUBJECT = ('Exception occurred running waterfall_builder_monitor.py '
+ 'script')
+
+
+def GetTimeDelta(date, days):
+ if isinstance(date, datetime.datetime):
+ return date + timedelta(days)
+
+
+def GetDateFromEpochFormat(epoch_time):
+ last_build_date = time.localtime(epoch_time)
+ last_build_date = datetime.datetime(int(last_build_date.tm_year),
+ int(last_build_date.tm_mon),
+ int(last_build_date.tm_mday),
+ int(last_build_date.tm_hour),
+ int(last_build_date.tm_min),
+ int(last_build_date.tm_sec))
+ return last_build_date
+
+
+def GetJSONData(json_url):
+ response = urllib.urlopen(json_url)
+ if response.getcode() == 200:
+ try:
+ data = json.loads(response.read())
+ except ValueError:
+ print 'ValueError for JSON URL: %s' % json_url
+ raise
+ else:
+ raise Exception('Error from URL: %s' % json_url)
+ response.close()
+ return data
+
+
+def SendEmailViaSendmailCommand(sender_email, recipient_emails,
+ subject, email_body):
+ msg = MIMEText(email_body)
+ msg["From"] = sender_email
+ msg["To"] = recipient_emails
+ msg["Subject"] = subject
+ pipe = Popen(["/usr/sbin/sendmail", "-t"], stdin=PIPE)
+ pipe.communicate(msg.as_string())
+
+
+def SendStatusEmailViaSendmailCommand(consolidated_results,
+ recipient_emails,
+ sender_email):
+ failure_count = 0
+ for result in consolidated_results:
+ if result['error'] != 'passed' and not result['build_too_old']:
+ failure_count += 1
+ today = str(datetime.date.today()).replace('-', '/')[5:]
+ if failure_count == 0:
+ subject = SUCCESS_SUBJECT % today
+ else:
+ subject = FAILURE_SUBJECT % (today,
+ failure_count,
+ len(consolidated_results))
+
+ email_body = ''
+ for result in consolidated_results:
+ if result['error'] != 'passed' or result['build_too_old']:
+ if result['build_date'] is not None:
+ email_body += result['platform'] + ': ' +\
+ result['build_link'] + ' ( Build too old: ' +\
+ result['build_date'] + ' ) ' +'\n\n'
+ else:
+ email_body += result['platform'] + ': ' +\
+ result['build_link'] + '\n\n'
+
+ SendEmailViaSendmailCommand(sender_email, recipient_emails,
+ subject, email_body)
+
+
+def SendExceptionEmailViaSendmailCommand(exception_message_lines,
+ recipient_emails,
+ sender_email):
+ subject = EXCEPTION_SUBJECT
+ email_body = ''
+ email_body = '\n'.join(exception_message_lines)
+
+ SendEmailViaSendmailCommand(sender_email, recipient_emails,
+ subject, email_body)
+
+
+class OfficialBuilderParser(object):
+ """This class implements basic utility functions on a specified builder."""
+ def __init__(self, builder_type, build_info):
+ self.platform = builder_type
+ self.builder_info = build_info
+ self.builder_url = build_info['builder_url']
+ self.build_json_url = build_info['json_url']
+ self.build = self._GetLatestBuildNumber()
+
+ def _GetLatestBuildNumber(self):
+ json_url = self.builder_info['builds_url']
+ data = GetJSONData(json_url)
+ # Get a sorted list of all the keys in the json data.
+ keys = sorted(data)
+ return self._GetLatestCompletedBuild(keys)
+
+ def _GetLatestCompletedBuild(self, keys):
+ reversed_list = keys[::-1]
+ for build in reversed_list:
+ data = self._GetJSONDataForBuild(build)
+ if data is not None:
+ if 'text' in data:
+ return build
+ return None
+
+ def _GetJSONDataForBuild(self, build):
+ if build is None:
+ return build
+ json_url = self.build_json_url % build
+ return GetJSONData(json_url)
+
+
+class GetBuilderStatus(OfficialBuilderParser):
+ def __init__(self, builder_type, build_info):
+ OfficialBuilderParser.__init__(self, builder_type, build_info)
+
+ def CheckForFailedSteps(self, days):
+ if self.build is None:
+ return {}
+ result = {'platform': self.platform,
+ 'build_number': self.build,
+ 'build_link': self.builder_url + self.build,
+ 'build_date': None,
+ 'build_too_old': False,
+ 'error': 'unknown'}
+ data = self._GetJSONDataForBuild(self.build)
+ if data is not None:
+ if 'text' in data:
+ if 'build' in data['text'] and 'successful' in data['text']:
+ result['error'] = 'passed'
+ else:
+ if 'failed' in data['text'] or\
+ 'exception' in data['text'] or\
+ 'interrupted' in data['text']:
+ result['error'] = 'failed'
+ if 'times' in data:
+ old_date = GetTimeDelta(datetime.datetime.now(), days)
+ last_build_date = GetDateFromEpochFormat(data['times'][0])
+ if last_build_date < old_date:
+ result['build_too_old'] = True
+ result['build_date'] = str(last_build_date).split(' ')[0]
+ else:
+ raise Exception('There was some problem getting JSON data '
+ 'from URL: %s' % result['build_link'])
+ return result
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('--config', type='str',
+ help='Absolute path to the config file.')
+
+ (options, _) = parser.parse_args()
+ if not options.config:
+ print 'Error: missing required parameter: --config'
+ parser.print_help()
+ return 1
+
+ try:
+ with open(options.config, 'r') as config_file:
+ try:
+ json_data = json.loads(config_file.read())
+ except ValueError:
+ print 'ValueError for loading JSON data from : %s' % options.config
+ raise ValueError
+
+ old_build_days = -2
+ if 'old_build_days' in json_data:
+ old_build_days = - json_data['old_build_days']
+ consolidated_results = []
+ for key in json_data['build_info'].keys():
+ builder_status = GetBuilderStatus(key, json_data['build_info'][key])
+ builder_result = builder_status.CheckForFailedSteps(old_build_days)
+ consolidated_results.append(builder_result)
+
+ SendStatusEmailViaSendmailCommand(consolidated_results,
+ json_data['recipient_emails'],
+ json_data['sender_email'])
+ return 0
+ except Exception:
+ formatted_lines = traceback.format_exc().splitlines()
+ SendExceptionEmailViaSendmailCommand(formatted_lines,
+ json_data['recipient_emails'],
+ json_data['sender_email'])
+ return 1
+
+if __name__ == '__main__':
+ sys.exit(main())
« no previous file with comments | « chrome/test/chromedriver/test/waterfall_builder_config_sample.json ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698