woo_mailer.py
author Geoff Brown <gbrown@mozilla.com>
Mon, 04 Jun 2018 07:47:03 -0600
changeset 353 4dc7cd6e0bf2f999fff0e30f1a2bb9e397e641f7
parent 316 71c02a1dd9d5ee852f684ef8b22c38eea7e88fae
permissions -rwxr-xr-x
Bug 1445301 - Update locations file for sclements; r=me

#!/usr/bin/env python

# This Source Code is subject to the terms of the Mozilla Public License
# version 2.0 (the "License"). You can obtain a copy of the License at
# http://mozilla.org/MPL/2.0/.

import ConfigParser
import datetime
import textwrap

import tempita

from sendemail import sendemail
from woo_client import TopBugs

DEFAULT_CONF_FILE = 'woo_cron.conf'
BUGZILLA_BUG_URL_PREFIX = 'https://bugzilla.mozilla.org/show_bug.cgi?id='
BUCKET_FORMAT = '    %d failures (%.0f%%) in %d %s bugs (%.0f - %.0f failures/week)\n'

email_tmpl = '''The War on Orange Progress Report
Week of {{startday}} to {{endday}}

Trunk orange factor: {{stats['orangefactorstr']}} ({{stats['orangecount']}} oranges in {{stats['testruncount']}} pushes)
Last week's trunk orange factor: {{previous_stats['orangefactorstr']}} ({{previous_stats['orangecount']}} oranges in {{previous_stats['testruncount']}} pushes)

This week's top ten oranges in trunk:


{{for s in summaries}}
{{s}}


{{endfor}}

{{distribution_summary}}

For a graphical, interactive version, see <{{exturl}}/?display=TopBugs&startday={{startday}}&endday={{endday}}&tree=trunk>

Visit the home of the War on Orange at <{{exturl}}/>

Brought to you by the Mozilla Engineering Productivity Team.

<https://wiki.mozilla.org/EngineeringProductivity>
IRC channel #ateam
'''


def datestr(date):
    return date.strftime('%Y-%m-%d')


def main():
    import errno
    import sys
    from optparse import OptionParser

    parser = OptionParser()
    parser.add_option('-c', '--config', action='store', type='string',
                      dest='config_file', default=DEFAULT_CONF_FILE, help='specify config file')
    parser.add_option('-t', '--test', action='store_true', dest='test_mode',
                      help='test mode, check options and print email to screen but do not send')
    (options, args) = parser.parse_args()

    cfg = ConfigParser.ConfigParser()
    cfg.read(options.config_file)

    try:
        local_server_url = cfg.get('woo', 'local_server_url')
        external_server_url = cfg.get('woo', 'external_server_url')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        sys.stderr.write('"local_server_url" and "external_server_url" options not found in\n'
                         '"woo" section in file "%s".\n' % options.config_file)
        sys.exit(errno.EINVAL)

    try:
        from_address = cfg.get('report', 'from')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        sys.stderr.write('No "from" option defined in "report" section of file "%s".\n' % options.config_file)
        sys.exit(errno.EINVAL)

    try:
        mail_dest = [x.strip() for x in cfg.get('email', 'dest').split(',')]
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        mail_dest = []

    try:
        admin_email = [x.strip() for x in cfg.get('email', 'admin').split(',')]
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        admin_email = []

    try:
        mail_username = cfg.get('email', 'username')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        mail_username = None

    try:
        mail_password = cfg.get('email', 'password')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        mail_password = None

    try:
        mail_server = cfg.get('email', 'server')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        mail_server = 'mail.mozilla.com'

    try:
        mail_port = cfg.getint('email', 'port')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        mail_port = 465

    try:
        mail_ssl = cfg.getboolean('email', 'ssl')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        mail_ssl = True

    try:
        nntp_server = cfg.get('nntp', 'server')
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        nntp_server = 'news.mozilla.org'

    try:
        newsgroups = [x.strip() for x in cfg.get('nntp', 'newsgroups').split(',')]
    except:
        newsgroups = []

    if not mail_dest and not newsgroups:
        sys.stderr.write('No destination email addresses nor newsgroups specified!\n')
        sys.stderr.write('Please put a comma-separated list of destination emails in the "dest"\n'
                         'option of the "email" section and/or a comma-separated list of\n'
                         'destination newsgroups in the "newsgroups" option of the "nntp" section\n'
                         'in file "%s".\n' % options.config_file)
        sys.exit(errno.EINVAL)

    today = datetime.date.today()
    # count up to yesterday, the last day for which we have complete stats
    endday = datestr(today - datetime.timedelta(days=1))
    startday = datestr(today - datetime.timedelta(days=7))
    tb = TopBugs(local_server_url, startday, endday)

    previous_endday = datestr(today - datetime.timedelta(days=8))
    previous_startday = datestr(today - datetime.timedelta(days=14))
    ptb = TopBugs(local_server_url, previous_startday, previous_endday)

    print 'Getting top bugs...'
    top = tb.top_bugs()[:10]

    print 'Getting bug details...'
    bugs = tb.bug_details(map(lambda x: x[0], top))

    stats = tb.stats()
    previous_stats = ptb.stats()

    bug_stats = tb.stats_by_bug()

    wrapper = textwrap.TextWrapper()
    wrapper.initial_indent = '    '
    wrapper.subsequent_indent = '    '

    # Build summaries here instead of in the template so we can sanely wrap
    # lines.
    summaries = []
    count = 0
    for t in top:
        count += 1
        summaries.append('%2d. %s oranges: bug %s (%s)\n    <%s%s>\n%s' %
                         (count, t[1], t[0], bugs[t[0]]['status'],
                          BUGZILLA_BUG_URL_PREFIX, t[0],
                          '\n'.join(wrapper.wrap(bugs[t[0]]['summary']))))
    # Build distribution summary
    buckets = [
        {'tag': 'high-frequency', 'low': 50, 'high': float('inf'), 'bugs': 0, 'failures': 0},
        {'tag': 'mid-frequency',  'low': 10, 'high': 50,           'bugs': 0, 'failures': 0},
        {'tag': 'low-frequency',  'low': 0,  'high': 10,           'bugs': 0, 'failures': 0},
    ]
    failure_total = 0
    bug_total = 0
    for bug_id, counts in bug_stats.iteritems():
        failures = counts['total']
        bug_total += 1
        failure_total += failures
        for bucket in buckets:
            if failures >= bucket['low'] and failures < bucket['high']:
                bucket['bugs'] += 1
                bucket['failures'] += failures
    distribution_summary = '%d failures were reported in %d bugs:\n' % (
        failure_total, bug_total)
    for bucket in buckets:
        distribution_summary += BUCKET_FORMAT % (
            bucket['failures'],
            100*bucket['failures']/failure_total,
            bucket['bugs'],
            bucket['tag'],
            bucket['low'],
            bucket['high']-1)

    tmpl = tempita.Template(email_tmpl)
    text = tmpl.substitute({'startday': startday, 'endday': endday,
                            'summaries': summaries,
                            'exturl': external_server_url, 'stats': stats,
                            'previous_stats': previous_stats,
                            'distribution_summary': distribution_summary})
    if stats['orangefactor'] is None or previous_stats['orangefactor'] is None:
        change = ':S'
    elif stats['orangefactor'] < previous_stats['orangefactor']:
        change = ':)'
    elif stats['orangefactor'] == previous_stats['orangefactor']:
        change = ':|'
    else:
        change = ':('

    subject = 'War on Orange, %s to %s: OF %s %s' % (startday, endday,
                                                     stats['orangefactorstr'],
                                                     change)

    if stats['orangecount'] < 2 and not options.test_mode:
        error_msg = 'This week\'s orange count is less than 2! Something must have broken :-('
        if admin_email:
            print 'Sending email to admin only...'
            sendemail(from_addr=from_address, to_addrs=admin_email,
                      subject=error_msg, username=mail_username,
                      password=mail_password, text_data=text,
                      server=mail_server, port=mail_port, use_ssl=mail_ssl)
        sys.exit(error_msg)

    if mail_dest:
        if options.test_mode:
            print 'Email:'
            print ''
            print 'From: %s' % from_address
            print 'To: %s' % ', '.join(mail_dest)
            print 'Subject: %s' % subject
            print
            print text
        else:
            print 'Sending email...'
            sendemail(from_addr=from_address, to_addrs=mail_dest,
                      subject=subject, username=mail_username,
                      password=mail_password, text_data=text,
                      server=mail_server, port=mail_port, use_ssl=mail_ssl)

    if newsgroups:
        msg = '''From: %s
Newsgroups: %s
Subject: %s

%s
''' % (from_address, ','.join(newsgroups), subject, text)
        if options.test_mode:
            print 'Newsgroup post:'
            print ''
            print msg
        else:
            print 'Posting to newsgroup(s)...'
            import nntplib
            import StringIO
            s = nntplib.NNTP(nntp_server)
            s.post(StringIO.StringIO(msg))
    print 'Done!'

if __name__ == '__main__':
    main()