blob: bec6390034debe1bc78696e67d6b153ac0291cc0 [file] [log] [blame]
# Copyright 2015 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.
"""Defines common functionality used for generating e-mail to sheriffs."""
import logging
import re
import urllib
from google.appengine.api import urlfetch
from dashboard import utils
from dashboard.models import bug_label_patterns
_SINGLE_EMAIL_SUBJECT = (
'%(percent_changed)s %(change_type)s in %(test_name)s '
'on %(bot)s at %(start)d:%(end)d')
_EMAIL_HTML_TABLE = """
A <b>%(percent_changed)s</b> %(change_type)s.<br>
<table cellpadding='4'>
<tr><td>Master:</td><td><b>%(master)s</b></td>
<tr><td>Bot:</td><td><b>%(bot)s</b></td>
<tr><td>Test:</td><td><b>%(test_name)s</b></td>
<tr><td>Revision Range:</td><td><b>%(start)d - %(end)d</b></td>
</table><p>
<a href="%(graph_url)s">View the graph</a>
and <a href='%(bug_url)s'>file a bug</a>.<br>
<b>+++++++++++++++++++++++++++++++</b><br>
"""
_SUMMARY_EMAIL_TEXT_BODY = """
A %(percent_changed)s %(change_type)s:
Master: %(master)s
Bot: %(bot)s
Test: %(test_name)s
Revision Range:%(start)d - %(end)d
View the graph: %(graph_url)s
File a bug: %(bug_url)s
+++++++++++++++++++++++++++++++
"""
_BUG_REPORT_COMMENT = (
'Performance dashboard identified a %(percent_changed)s %(change_type)s '
'in %(test_name)s on %(bot)s at revision range %(start)d:%(end)d. '
'Graph: %(graph_url)s')
_BUG_REPORT_LINK_URL = (
'https://code.google.com/p/chromium/issues/entry?summary=%s&comment=%s&'
'labels=Type-Bug-Regression,Pri-2,Performance-Sheriff,%s')
_ALL_ALERTS_LINK = (
'<a href="https://chromeperf.appspot.com/alerts?sheriff=%s">'
'All alerts</a><br>')
_PERF_TRY_EMAIL_SUBJECT = (
'Perf Try %(status)s on %(bot)s at %(start)s:%(end)s.')
_PERF_TRY_EMAIL_HTML_BODY = """
Perf Try Job %(status)s
<br><br>
A Perf Try Job was submitted on %(bot)s at
<a href="%(perf_url)s">%(perf_url)s</a>.<br>
<table cellpadding='4'>
<tr><td>Bot:</td><td><b>%(bot)s</b></td>
<tr><td>Test:</td><td><b>%(command)s</b></td>
<tr><td>Revision Range:</td><td><b>%(start)s - %(end)s</b></td>
<tr><td>HTML Results:</td><td><b>%(html_results)s</b></td>
%(profiler_results)s
</table><p>
"""
_PERF_TRY_EMAIL_TEXT_BODY = """
Perf Try Job %(status)s
Bot: %(bot)s
Test: %(command)s
Revision Range:%(start)s - %(end)s
HTML Results: %(html_results)s
%(profiler_results)s
+++++++++++++++++++++++++++++++
"""
_PERF_PROFILER_HTML_ROW = '<tr><td>%(title)s:</td><td><b>%(link)s</b></td>\n'
_PERF_PROFILER_TEXT_ROW = '%(title)s: %(link)s\n'
_BISECT_FYI_EMAIL_SUBJECT = (
'Bisect FYI Try Job Failed on %(bot)s for %(test_name)s.')
_BISECT_FYI_EMAIL_HTML_BODY = """
Bisect FYI Try Job Failed
<br><br>
A Bisect FYI Try Job for %(test_name)s was submitted on %(bot)s at
<a href="%(job_url)s">%(job_url)s</a>.<br>
<table cellpadding='4'>
<tr><td>Bot:</td><td><b>%(bot)s</b></td>
<tr><td>Test Case:</td><td><b>%(test_name)s</b></td>
<tr><td>Bisect Config:</td><td><b>%(config)s</b></td>
<tr><td>Error Details:</td><td><b>%(errors)s</b></td>
<tr><td>Bisect Results:</td><td><b>%(results)s</b></td>
</table>
"""
_BISECT_FYI_EMAIL_TEXT_BODY = """
Bisect FYI Try Job Failed
Bot: %(bot)s
Test Case: %(test_name)s
Bisect Config: %(config)s
Error Details: %(errors)s
Bisect Results:
%(results)s
+++++++++++++++++++++++++++++++
"""
def GetPerfTryJobEmail(perf_results):
"""Gets the contents of the email to send once a perf try job completes."""
if perf_results['status'] == 'Completed':
profiler_html_links = ''
profiler_text_links = ''
for title, link in perf_results['profiler_results']:
profiler_html_links += _PERF_PROFILER_HTML_ROW % {'title': title,
'link': link}
profiler_text_links += _PERF_PROFILER_TEXT_ROW % {'title': title,
'link': link}
subject_dict = {
'status': 'Success', 'bot': perf_results['bisect_bot'],
'start': perf_results['config']['good_revision'],
'end': perf_results['config']['bad_revision']
}
html_dict = {
'status': 'SUCCESS',
'bot': perf_results['bisect_bot'],
'perf_url': perf_results['buildbot_log_url'],
'command': perf_results['config']['command'],
'start': perf_results['config']['good_revision'],
'end': perf_results['config']['bad_revision'],
'html_results': perf_results['html_results'],
'profiler_results': profiler_html_links
}
text_dict = html_dict.copy()
text_dict['profiler_results'] = profiler_text_links
elif perf_results['status'] == 'Failure':
config = perf_results.get('config')
if not config:
config = {
'good_revision': '?',
'bad_revision': '?',
'command': '?',
}
subject_dict = {
'status': 'Failure', 'bot': perf_results['bisect_bot'],
'start': config['good_revision'],
'end': config['bad_revision']
}
html_dict = {
'status': 'FAILURE',
'bot': perf_results['bisect_bot'],
'perf_url': perf_results['buildbot_log_url'],
'command': config['command'],
'start': config['good_revision'],
'end': config['bad_revision'],
'html_results': '', 'profiler_results': ''
}
text_dict = html_dict
else:
return None
html = _PERF_TRY_EMAIL_HTML_BODY % html_dict
text = _PERF_TRY_EMAIL_TEXT_BODY % text_dict
subject = _PERF_TRY_EMAIL_SUBJECT % subject_dict
return {'subject': subject, 'html': html, 'body': text}
def GetSheriffEmails(sheriff):
"""Gets all of the email addresses to send mail to for a Sheriff.
This includes both the general email address of the sheriff rotation,
which is often a mailing list, and the email address of the particular
sheriff on duty, if applicable.
Args:
sheriff: A Sheriff entity.
Returns:
A comma-separated list of email addresses; this will be an empty string
if there are no email addresses.
"""
receivers = [sheriff.email] if sheriff.email else []
sheriff_on_duty = _GetSheriffOnDutyEmail(sheriff)
if sheriff_on_duty:
receivers.append(sheriff_on_duty)
return ','.join(receivers)
def _GetSheriffOnDutyEmail(sheriff):
"""Gets the email address of the sheriff on duty for a sheriff rotation.
Args:
sheriff: A Sheriff entity.
Returns:
A comma-separated list of email addresses, or None.
"""
if not sheriff.url:
return None
response = urlfetch.fetch(sheriff.url)
if response.status_code != 200:
logging.error('Response %d from %s for %s.', response.status_code,
sheriff.url, sheriff.key.string_id())
return None
match = re.match(r'document\.write\(\'(.*)\'\)', response.content)
if not match:
logging.error('Could not parse response from sheriff URL %s: %s',
sheriff.url, response.content)
return None
addresses = match.groups()[0].split(', ')
return ','.join('%s@google.com' % a for a in addresses)
def GetReportPageLink(test_path, rev=None, add_protocol_and_host=True):
"""Gets a URL for viewing a single graph."""
path_parts = test_path.split('/')
if len(path_parts) < 4:
logging.error('Could not make link, invalid test path: %s', test_path)
master, bot = path_parts[0], path_parts[1]
test_name = '/'.join(path_parts[2:])
trace_name = path_parts[-1]
trace_names = ','.join([trace_name, trace_name + '_ref', 'ref'])
if add_protocol_and_host:
link_template = 'https://chromeperf.appspot.com/report?%s'
else:
link_template = '/report?%s'
uri = link_template % urllib.urlencode([
('masters', master),
('bots', bot),
('tests', test_name),
('checked', trace_names),
])
if rev:
uri += '&rev=%s' % rev
return uri
def GetGroupReportPageLink(alert):
"""Gets a URL for viewing a graph for an alert and possibly related alerts."""
# Entities only have a key if they have already been put().
if alert and alert.key:
link_template = 'https://chromeperf.appspot.com/group_report?keys=%s'
return link_template % alert.key.urlsafe()
# If we can't make the above link, fall back to the /report page.
test_path = utils.TestPath(alert.test)
return GetReportPageLink(test_path, rev=alert.end_revision)
def GetAlertInfo(alert, test):
"""Gets the alert info formatted for the given alert and test.
Args:
alert: An Anomaly entity.
test: The Test entity for the given alert.
Returns:
A dictionary of string keys to values. Keys are 'email_subject',
'email_text', 'email_html', 'dashboard_link', 'alerts_link', 'bug_link'.
"""
percent_changed = alert.GetDisplayPercentChanged()
change_type = 'improvement' if alert.is_improvement else 'regression'
test_name = '/'.join(test.test_path.split('/')[2:])
sheriff_name = alert.sheriff.string_id()
master = test.master_name
bot = test.bot_name
graph_url = GetGroupReportPageLink(alert)
# Parameters to interpolate into strings below.
interpolation_parameters = {
'percent_changed': percent_changed,
'change_type': change_type,
'master': master,
'bot': bot,
'test_name': test_name,
'sheriff_name': sheriff_name,
'start': alert.start_revision,
'end': alert.end_revision,
'graph_url': graph_url,
}
bug_comment = _BUG_REPORT_COMMENT % interpolation_parameters
bug_summary = ('%(percent_changed)s %(change_type)s in %(test_name)s '
'on %(bot)s at %(start)d:%(end)d') % interpolation_parameters
labels = bug_label_patterns.GetBugLabelsForTest(test)
bug_url = _BUG_REPORT_LINK_URL % (
urllib.quote(bug_summary), urllib.quote(bug_comment), ','.join(labels))
interpolation_parameters['bug_url'] = bug_url
results = {
'email_subject': _SINGLE_EMAIL_SUBJECT % interpolation_parameters,
'email_text': _SUMMARY_EMAIL_TEXT_BODY % interpolation_parameters,
'email_html': _EMAIL_HTML_TABLE % interpolation_parameters,
'dashboard_link': graph_url,
'alerts_link': _ALL_ALERTS_LINK % urllib.quote(sheriff_name),
'bug_link': bug_url,
}
return results
def GetBisectFYITryJobEmail(job, test_results):
"""Gets the contents of the email to send once a bisect FYI job completes."""
if test_results['status'] != 'Completed':
subject_dict = {
'bot': test_results['bisect_bot'],
'test_name': job.job_name
}
html_dict = {
'bot': test_results['bisect_bot'],
'job_url': test_results['buildbot_log_url'],
'test_name': job.job_name,
'config': job.config if job.config else 'Undefined',
'errors': test_results.get('errors'),
'results': test_results.get('results'),
}
text_dict = html_dict
else:
return None
html = _BISECT_FYI_EMAIL_HTML_BODY % html_dict
text = _BISECT_FYI_EMAIL_TEXT_BODY % text_dict
subject = _BISECT_FYI_EMAIL_SUBJECT % subject_dict
return {'subject': subject, 'html': html, 'body': text}