Add BuildFarm object, remove compilers and host_age from BuildResultStore.
[build-farm.git] / import-and-analyse.py
1 #!/usr/bin/python
2 # Write sqlite entries for test reports in the build farm
3 # Copyright (C) 2007-2010 Jelmer Vernooij <jelmer@samba.org>
4 # Copyright (C) 2007-2010 Andrew Bartlett <abartlet@samba.org>
5 # Published under the GNU GPL
6
7 """Script to parse build farm log files from the data directory, import
8 them into the database, add links to the oldrevs/ directory and send
9 some mail chastising the possible culprits when the build fails, based
10 on recent commits.
11 """
12
13 from buildfarm import (
14     BuildFarm,
15     data,
16     hostdb,
17     )
18 import commands
19 from email.mime.text import MIMEText
20 import logging
21 import optparse
22 import os
23 import re
24 import smtplib
25
26 dry_run = True
27
28 parser = optparse.OptionParser("import-and-analyse [options]")
29 parser.add_option("--dry-run", help="Will cause the script to send output to stdout instead of to sendmail.", action="store_true")
30 parser.add_option("--verbose", help="Be verbose", action="count")
31
32 (opts, args) = parser.parse_args()
33
34 UNPACKED_DIR = "/home/ftp/pub/unpacked"
35
36 # we open readonly here as only apache(www-run) has write access
37 buildfarm = BuildFarm()
38 db = data.BuildResultStore(os.path.abspath(os.path.dirname(__file__)), True)
39 hostsdb = buildfarm.hostdb
40
41 compilers = buildfarm.compilers
42 hosts = hostsdb.hosts()
43 trees = db.trees
44
45 smtp = smtplib.SMTP()
46 smtp.connect()
47
48 class Log(object):
49
50     def __init__(self):
51         self.change_log = None
52         self.committers = set()
53         self.authors = set()
54         self.recipients = None
55
56
57 def get_log_git(tree, cur, old):
58     cmd = "cd %s/%s && git log --pretty=full %s..%s ./" % (UNPACKED_DIR, tree, old, cur)
59
60     log = Log()
61
62     log.change_log = commands.getoutput(cmd)
63     #print log.change_log
64
65     # get the list of possible culprits
66     log2 = log.change_log
67
68     for m in re.findall("[\n]*Author: [^<]*<([^>]+)>\nCommit: [^<]*<([^>]+)>\n(.*)$", log.change_log):
69         author = m.group(1)
70         committer = m.group(2)
71
72         # handle cherry-picks from svnmirror repo
73         author = author.replace("0c0555d6-39d7-0310-84fc-f1cc0bd64818", "samba.org")
74
75         # for now only send reports to samba.org addresses.
76         if not "@samba.org" in author:
77             author = None
78
79         if author:
80             log.authors.add(author)
81         if committer:
82             log.committers.add(committer)
83
84     # Add a URL to the diffs for each change
85     log.change_log = re.sub("([\n]*commit ([0-9a-f]+))", "\\1\nhttp:\/\/build.samba.org\/?function=diff;tree=%s;revision=\\2" % tree, log.change_log)
86
87     all = set()
88     all.update(log.authors)
89     all.update(log.committers)
90     log.recipients = all
91     return log
92
93
94 def get_log(tree, cur, old):
95     treedir = os.path.join(UNPACKED_DIR, tree)
96
97     if os.path.exists(os.path.join(treedir, ".git")):
98         return get_log_git(tree, cur, old)
99     else:
100         raise Exception("Unknown vcs for %s" % treedir)
101
102
103 def check_and_send_mails(tree, host, compiler, cur, old):
104     t = trees[tree]
105
106     (cur_rev, cur_rev_timestamp) = cur.revision_details()
107     cur_status = cur.status()
108
109     (old_rev, old_rev_timestamp) = old.revision_details()
110     old_status = old.status()
111
112     if dry_run:
113         print "rev=%s status=%s" % (cur_rev, cur_status)
114         print "old rev=%s status=%s" % (old_rev, old_status)
115
116     if not cur_status.regressed_since(old_status):
117         if dry_run:
118             print "the build didn't get worse since %r" % old_status
119         return
120
121     log = get_log(tree, cur, old)
122     if not log:
123         if dry_run:
124             print "no log"
125         return
126
127     recipients = ",".join(log.recipients.keys())
128
129     body = """
130 Broken build for tree %(tree)s on host %(host)s with compiler %(compiler)s
131
132 Tree %(tree)s is %(scm)s branch %(branch)s.
133
134 Build status for new revision %(cur_rev)s is %(cur_status)s
135 Build status for old revision %(old_rev)s was %(old_status)s
136
137 See http://build.samba.org/?function=View+Build;host=%(host)s;tree=%(tree)s;compiler=%(compiler)s
138
139 The build may have been broken by one of the following commits:
140
141 %(change_log)s
142     """ % {"tree": tree, "host": host, "compiler": compiler, "change_log": log.change_log, "scm": t.scm, "branch": t.branch}
143
144     msg = MIMEText(body)
145     msg["Subject"] = "BUILD of %s:%s BROKEN on %s with %s AT REVISION %s" % (tree, t.branch, host, compiler, cur_rev)
146     msg["From"] = "\"Build Farm\" <build@samba.org>"
147     msg["To"] = recipients
148     smtp.send(msg["From"], [msg["To"]], msg.as_string())
149
150
151 for host in hosts:
152     for tree in trees:
153         for compiler in compilers:
154             retry = 0
155             if opts.verbose >= 2:
156                 print "Looking for a log file for %s %s %s..." % (host, compiler, tree)
157
158             # By building the log file name this way, using only the list of
159             # hosts, trees and compilers as input, we ensure we
160             # control the inputs
161             try:
162                 build = db.get_build(host, tree, compiler)
163             except data.NoSuchBuildError:
164                 continue
165
166             if opts.verbose >= 2:
167                 print "Processing %s..." % build
168
169             db.upload_build(build)
170
171             (rev, commit_rev, rev_timestamp) = db.revision_details()
172
173             try:
174                 prev_rev = db.get_previous_revision(tree, host, compiler, rev)
175             except hostdb.NoSuchBuild:
176                 # Can't send a nastygram until there are 2 builds..
177                 continue
178             else:
179                 prev_build = db.get_build(tree, host, compiler, prev_rev)
180                 check_and_send_mails(tree, host, compiler, build.status(), prev_build.status())
181
182
183 smtp.quit()