2 # Simple database query script for the buildfarm
4 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2001-2005
5 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001
6 # Copyright (C) Vance Lankhaar <vance@samba.org> 2002-2005
7 # Copyright (C) Martin Pool <mbp@samba.org> 2001
8 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 def span(classname, contents):
32 return "<span class=\"%s\">%s</span>" % (classname, contents)
35 def check_dir_exists(kind, path):
36 if not os.path.isdir(path):
37 raise Exception("%s directory %s does not exist" % (kind, path))
41 def status_info_cmp(self, s1, s2):
49 if i >= len(a1) or i >= len(a2):
58 return s2["value"] - s1["value"]
61 class BuildfarmDatabase(object):
67 def __init__(self, basedir, readonly=False):
68 self.basedir = basedir
69 check_dir_exists("base", self.basedir)
70 self.readonly = readonly
72 self.webdir = os.path.join(basedir, "web")
73 check_dir_exists("web", self.webdir)
75 self.datadir = os.path.join(basedir, "data")
76 check_dir_exists("data", self.datadir)
78 self.cachedir = os.path.join(basedir, "cache")
79 check_dir_exists("cache", self.cachedir)
81 self.lcovdir = os.path.join(basedir, "lcov/data")
82 check_dir_exists("lcov", self.lcovdir)
84 self.compilers = util.load_list(os.path.join(self.webdir, "compilers.list"))
85 self.hosts = util.load_hash(os.path.join(self.webdir, "hosts.list"))
111 'repo': 'build-farm',
125 'repo': 'samba-docs',
140 'branch': 'v3-5-test',
147 'branch': 'v3-6-test',
169 'subdir': 'lib/replace/',
176 'subdir': 'lib/talloc/',
183 'subdir': 'lib/tdb/',
190 'subdir': 'lib/ldb/',
209 def cache_fname(self, tree, host, compiler, rev=None):
211 return os.path.join(self.cachedir, "build.%s.%s.%s-%s" % (tree,host,compiler,rev))
213 return os.path.join(self.cachedir, "build.%s.%s.%s" % (tree,host,compiler))
215 def build_fname(self, tree, host, compiler, rev=None):
216 """get the name of the build file"""
218 return os.path.join(self.datadir, "oldrevs/build.%s.%s.%s-%s" % (tree, host, compiler, rev))
219 return os.path.join(self.datadir, "upload/build.%s.%s.%s" % (tree, host, compiler))
222 # the mtime age is used to determine if builds are still happening
224 # the ctime age is used to determine when the last real build happened
226 ##############################################
227 def build_age_mtime(self, host, tree, compiler, rev):
228 """get the age of build from mtime"""
229 file = self.build_fname(tree, host, compiler, rev)
232 st = os.stat("%s.log" % file)
234 # File does not exist
237 return time.time() - st.st_mtime
239 def build_age_ctime(self, host, tree, compiler, rev):
240 """get the age of build from ctime"""
241 file = self.build_fname(tree, host, compiler, rev)
244 st = os.stat("%s.log" % file)
248 return time.time() - st.st_ctime
250 def build_revision_details(self, host, tree, compiler, rev=None):
251 """get the svn revision of build"""
252 file = self.build_fname(tree, host, compiler, rev)
253 cachef = self.cache_fname(tree, host, compiler, rev)
255 # don't fast-path for trees with git repository:
256 # we get the timestamp as rev and want the details
258 if tree not in self.trees:
260 if self.trees[tree]["scm"] != "git":
264 st1 = os.stat("%s.log" % file)
266 # File does not exist
267 return "NO SUCH FILE"
270 st2 = os.stat("%s.revision" % cachef)
272 # File does not exist
275 # the ctime/mtime asymmetry is needed so we don't get fooled by
276 # the mtime update from rsync
277 if st2 and st1.st_ctime <= st2.st_mtime:
278 return util.FileLoad("%s.revision" % cachef)
280 log = util.FileLoad("%s.log" % file)
282 m = re.search("BUILD COMMIT REVISION: (.*)", log)
286 m = re.search("BUILD REVISION: (.*)", log)
292 m = re.search("BUILD COMMIT TIME: (.*)", log)
294 ret += ":" + m.group(1)
296 if not self.readonly:
297 util.FileSave("%s.revision" % cachef, ret)
301 def build_revision(self, host, tree, compiler, rev):
302 r = self.build_revision_details(host, tree, compiler, rev)
303 return r.split(":")[0]
305 def build_revision_time(self, host, tree, compiler, rev):
306 r = self.build_revision_details(host, tree, compiler, rev)
307 return r.split(":", 1)[1]
309 def build_status_from_logs(self, log, err):
310 """get status of build"""
313 return span("status passed", "ok")
315 return span("status failed", st)
317 m = re.search("TEST STATUS:(.*)", log)
319 tstatus = span_status(m.group(1))
321 m = re.search("ACTION (PASSED|FAILED): test", log)
323 test_failures = len(re.findall("testsuite-(failure|error): ", log))
324 test_successes = len(re.findall("testsuite-success: ", log))
325 if test_successes > 0:
326 tstatus = span_status(test_failures)
328 tstatus = span_status(255)
330 tstatus = span("status unknown", "?")
332 m = re.search("INSTALL STATUS:(.*)", log)
334 istatus = span_status(m.group(1))
336 istatus = span("status unknown", "?")
338 m = re.search("BUILD STATUS:(.*)", log)
340 bstatus = span_status(m.group(1))
342 bstatus = span("status unknown", "?")
344 m = re.search("CONFIGURE STATUS:(.*)", log)
346 cstatus = span_status(m.group(1))
348 cstatus = span("status unknown", "?")
350 m = re.search("(PANIC|INTERNAL ERROR):.*", log)
352 sstatus = "/"+span("status panic", "PANIC")
356 if "No space left on device" in err or "No space left on device" in log:
357 dstatus = "/"+span("status failed", "disk full")
361 if "maximum runtime exceeded" in log:
362 tostatus = "/"+span("status failed", "timeout")
366 m = re.search("CC_CHECKER STATUS: (.*)", log)
367 if m and int(m.group(1)) > 0:
368 sstatus += "/".span("status checker", m.group(1))
370 return "%s/%s/%s/%s%s%s%s" % (
371 cstatus, bstatus, istatus, tstatus, sstatus, dstatus, tostatus)
373 def build_status(self, host, tree, compiler, rev):
374 """get status of build"""
375 file = self.build_fname(tree, host, compiler, rev)
376 cachefile = self.cache_fname(tree, host, compiler, rev)+".status"
378 st1 = os.stat("%s.log" % file)
381 return "Unknown Build"
384 st2 = os.stat(cachefile)
389 if st2 and st1.st_ctime <= st2.st_mtime:
390 return util.FileLoad(cachefile)
392 log = util.FileLoad("%s.log" % file)
394 err = util.FileLoad("%s.err" % file)
399 ret = self.build_status_from_logs(log, err)
401 if not self.readonly:
402 util.FileSave(cachefile, ret)
406 def build_status_info_from_string(self, rev_seq, rev, status_raw):
407 """find the build status as an perl object
409 the 'value' gets one point for passing each stage"""
410 status_split = status_raw.split("/")
415 for r in status_split:
429 status_str += "%d" % r
439 "string": status_str,
443 def build_status_info_from_html(self, rev_seq, rev, status_html):
444 """find the build status as an perl object
446 the 'value' gets one point for passing each stage
448 status_raw = util.strip_html(status_html)
449 return self.build_status_info_from_string(rev_seq, rev, status_raw)
451 def build_status_info(self, host, tree, compiler, rev_seq):
452 """find the build status as an perl object
454 the 'value' gets one point for passing each stage
456 rev = self.build_revision(host, tree, compiler, rev_seq)
457 status_html = self.build_status(host, tree, compiler, rev_seq)
458 return self.build_status_info_from_html(rev_seq, rev, status_html)
460 def lcov_status(self, tree):
461 """get status of build"""
462 cachefile = os.path.join(self.cachedir, "lcov.%s.%s.status" % (self.LCOVHOST, tree))
463 file = os.path.join(self.lcovdir, self.LCOVHOST, tree, "index.html")
467 # File does not exist
470 st2 = os.stat(cachefile)
472 # file does not exist
475 if st2 and st1.st_ctime <= st2.st_mtime:
476 return util.FileLoad(cachefile)
478 lcov_html = util.FileLoad(file)
479 m = re.search('\<td class="headerItem".*?\>Code\ \;covered\:\<\/td\>.*?\n.*?\<td class="headerValue".*?\>([0-9.]+) \%', lcov_html)
481 ret = "<a href=\"/lcov/data/%s/%s\">%s %%</a>" % (self.LCOVHOST, tree, m.group(1))
485 util.FileSave(cachefile, ret)
488 def err_count(self, host, tree, compiler, rev):
489 """get status of build"""
490 file = self.build_fname(tree, host, compiler, rev)
491 cachef = self.cache_fname(tree, host, compiler, rev)
494 st1 = os.stat("%s.err" % file)
496 # File does not exist
499 st2 = os.stat("%s.errcount" % cachef)
501 # File does not exist
504 if st2 and st1.st_ctime <= st2.st_mtime:
505 return util.FileLoad("%s.errcount" % cachef)
508 err = util.FileLoad("%s.err" % file)
510 # File does not exist
513 ret = util.count_lines(err)
515 if not self.readonly:
516 util.FileSave("%s.errcount" % cachef, str(ret))
520 def read_log(self, tree, host, compiler, rev):
521 """read full log file"""
522 return util.FileLoad(self.build_fname(tree, host, compiler, rev)+".log")
524 def read_err(self, tree, host, compiler, rev):
525 """read full err file"""
526 return util.FileLoad(self.build_fname(tree, host, compiler, rev)+".err")
528 def get_old_revs(self, tree, host, compiler):
529 """get a list of old builds and their status."""
530 directory = os.path.join(self.datadir, "oldrevs")
531 logfiles = [d for d in os.listdir(directory) if d.startswith("build.%s.%s.%s-" % (tree, host, compiler)) and d.endswith(".log")]
533 m = re.match(".*-([0-9A-Fa-f]+).log$", l)
536 stat = os.stat(os.path.join(directory, l))
537 # skip the current build
538 if stat.st_nlink == 2:
541 "STATUS": self.build_status(host, tree, compiler, rev),
543 "TIMESTAMP": stat.st_ctime
547 ret.sort(lambda a, b: cmp(a["TIMESTAMP"], b["TIMESTAMP"]))
551 def has_host(self, host):
552 return host in os.listdir(os.path.join(self.datadir, "upload"))