casa  $Rev:20696$
 All Classes Namespaces Files Functions Variables
report.py
Go to the documentation of this file.
00001 """Create HTML report of regression test results
00002 
00003 usage: python report.py testresult_dir report_dir revision generate_plot
00004 
00005 testresult_dir: database top directory
00006 report_dir: output directory
00007 revision: 'latest' for latest test-branch only, or 'all'
00008 generate_plot: boolean, generates png plots iff true
00009 
00010 expected format of logfiles:
00011 [name]  =  [value] # [comment]
00012 
00013 """
00014 
00015 from taskinit import casalog
00016 import shutil
00017 import os
00018 import sys
00019 import re
00020 import sets
00021 import commands
00022 import pylab as pl
00023 import datetime
00024 import string
00025 
00026 # profile generation:
00027 import time
00028 from tw_utils import *
00029 from matplotlib.font_manager import  FontProperties
00030 
00031 colormania = False   # more or less colors in the generated HTML
00032 colormania = True    #
00033 
00034 SOURCE_DIR = os.environ["CASAPATH"].split()[0]
00035 known_releases = ["CASA Version 2.3.0 (build #6654)",
00036                   "CASA Version 2.3.1 (build #6826)",
00037                   "CASA Version 2.4.0 (build #8115)",
00038                   "CASA Version 3.0.0 (r9861)", # for Mac...
00039                   "CASA Version 3.0.0 (r9888)", # for Linux...
00040                   "CASA Version 3.0.1 (r11099)",
00041                   "CASA Version 3.0.2 (r11761)",
00042                   "CASA Version 3.1.0 (r13568)",
00043                   "CASA Version 3.2.0 (r15111)"]
00044 
00045 
00046 exclude_host = ['el4tst','el4tst64b',
00047                 'ub8tst','ub8tst64b',
00048                 'fc8tst','fc8tst64b', 'fc8tst.cv.nrao.edu','onager','ballista' ]
00049 exclude_test = {}
00050 exclude_test['pointing_regression'] = ["CASA Version 2.4.0 (build #8115)"]
00051 same_version_per_host = False # if False, the latest run for each test is reported
00052 
00053 def cmp_exec(a, b):
00054     if a[1] == 'exec':
00055         return -1
00056     elif b[1] == 'exec':
00057         return 1
00058     else:
00059         return 0
00060 
00061 # Usual integer / string comparison
00062 def cmp_std(a, b):
00063     if a < b:
00064         return -1
00065     elif a > b:
00066         return 1
00067     else:
00068         return 0
00069 
00070 # Compare version numbers
00071 def cmp_version(a, b):
00072     if a.find('build') >= 0 and b.find('build') < 0:
00073         return -1
00074     elif a.find('build') < 0 and b.find('build') >= 0:
00075         return 1
00076     elif a.find('build') >= 0 and b.find('build') >= 0:
00077         return cmp_std(a, b)
00078     else:
00079         # Neither a nor b contain 'build'
00080         n = len("CASA Version 3.0.1 (r")
00081         if a[:n] != b[:n]:
00082             return cmp_std(a[:n], b[:n])
00083         else:
00084             # Compare XYZ numerically in
00085             # "CASA Version 3.0.1 (rXYZ)"
00086             a_int = int(a[n:len(a)-len(")")])
00087             b_int = int(b[n:len(b)-len(")")])
00088             return cmp_std(a_int, b_int)
00089         
00090 
00091 def shorten(s, maxlength=20):
00092     if len(s) > maxlength:
00093         # HTML tooltip
00094         return "<span title='"+s+"'>" + \
00095                s[0:maxlength-2] + ".." + \
00096                "</span>"
00097     else:
00098         return s
00099 
00100 def architecture(s):
00101     try:
00102         arch = re.compile('([^\s]+ [^\s]+-bit)').search(s).groups()[0]
00103     except:
00104         arch = "???"
00105     return arch
00106 
00107 def distribution(s):
00108 
00109     try:
00110         rev,name = re.compile(' ([^\s]+) \(([^\)]+)\)').search(s).groups()
00111     except:
00112         rev,name = "???", "???"
00113         
00114     if s.find('Linux') >= 0:
00115         base = 'Linux'
00116         if s.find('Fedora Core') >= 0:
00117             distro = 'FC'
00118         elif s.find('Fedora') >= 0:
00119             distro = 'FC'
00120         elif s.find('Red Hat Enterprise Linux') >= 0:
00121             distro = 'RHEL'
00122         elif s.find('openSUSE') >= 0:
00123             distro = 'SuSE'
00124         elif s.find('Ubuntu') >= 0:
00125             distro = 'Ubuntu'
00126         else:
00127             distro = '???'
00128         return base, distro, rev, name    
00129         # e.g.  Linux, RHEL, 5.2, Tikanga
00130     elif s.find('Darwin') >= 0:
00131         base = 'Darwin'
00132         distro = 'OS X'
00133 
00134         return base, distro, rev, name
00135     else:
00136         return "???", "???", "???", "???"
00137 
00138 
00139 # Returns true if the given hostname is testing
00140 # the non-active (stable, test etc..) branch
00141 def is_stable_branch(host):
00142     return (host.find('tst') >= 0 or \
00143             host.find('ma01') >= 0 or \
00144             host.find('sneffels') >= 0)
00145 
00146 def selected_revisions(data):
00147   
00148     all = sets.Set()
00149     for log in data:
00150         all.add(log['CASA'])
00151 
00152     all_list = []
00153     for c in all:
00154         all_list.append(c)
00155     all_list.sort(reverse=True, cmp=cmp_version)
00156 
00157     # The following code selects which versions to use,
00158     # with lower density as we go back in time.
00159     # For example with b(base) = 5 and d(density) = 4
00160     # the following versions are selected
00161     #
00162     # 0,1,2,3,4,       (density=1    in range < 5)
00163     # 8,12,16,20,24,   (density=1/4  in range < 25)
00164     # 32,48,64,80,96   (density=1/16 in range < 125)
00165     # etc.
00166     #
00167     # and also 5,6,7,8,9, ... 4b-1
00168     #
00169     b = 5
00170     d = 4
00171     selected = sets.Set()
00172     for c in range(len(all_list)):
00173         interval = b
00174         density = 1
00175         use_it = False
00176         for j in range(20):
00177             # loops until b^20 (i.e. long enough)
00178             if (c < interval and (c % density) == 0) or \
00179                    (c < 4*b) :
00180 
00181                 use_it = True
00182                 break
00183             interval *= b;
00184             density  *= d;
00185 
00186         # Use also
00187         #   - the oldest log of all times
00188         #   - known releases
00189         if all_list[c] == "CASA Version 2.0 Rev 5654" or \
00190            all_list[c] in known_releases:
00191             use_it = True
00192 
00193         if use_it:
00194             selected.add(all_list[c])
00195             print "Use ", all_list[c]
00196         else:
00197             print "Drop", all_list[c]
00198 
00199     # And always use, too, the latest version on the test branch
00200     stable_versions = []
00201     for log in data:
00202         host = log['host']
00203         if is_stable_branch(host):
00204             stable_versions.append(log['CASA'])
00205 
00206     if len(stable_versions) > 0:
00207         stable_versions.sort(reverse=True, cmp=cmp_version)
00208         selected.add(stable_versions[0])
00209         print "Use latest on test branch: ", stable_versions[0]
00210     else:
00211         print "No tests on test branch"
00212 
00213     return selected
00214     
00215 
00216 class report:
00217     def __init__(self, reg_dir, report_dir, \
00218                  revision='all', \
00219                  gen_plot=True,   # generate plots?
00220                  skull=''         # path to crash image
00221                  ):
00222 
00223         casalog.showconsole(onconsole=True)
00224 
00225         pl.ioff()  #turn of plots
00226 
00227         result_dir = reg_dir + '/Result/'
00228         
00229         data = self.read_log(result_dir, revision)
00230 
00231         if len(data) == 0:
00232             raise Exception, "No matching test log files found in %s, cannot determine CASA version" % result_dir
00233 
00234         if revision == 'all':
00235             # Select only some revisions (in order to reduce length of historical report)
00236             casas_set = selected_revisions(data)
00237 
00238             # Filter log files
00239             filtered_data = []
00240             for log in data:
00241                 if log['CASA'] in casas_set:
00242                     filtered_data.append(log)
00243             data = filtered_data
00244 
00245         #
00246         # collect some general data
00247         #
00248         hosts_set         = sets.Set()
00249         tests_set         = sets.Set()
00250         self.tests_v      = {}          # subtests per version
00251         self.subtests     = {}
00252         casas_set         = sets.Set()
00253         for log in data:
00254             # Use only the hostname as key
00255             # (the 'platform' string format might change
00256             #  with time)
00257             # hosts.add((log['host'], log['platform']))
00258 
00259             tests_set.add   (log['testid'])
00260             hosts_set.add   (log['host'])
00261             casas_set.add   (log['CASA'])
00262 
00263             test = log['testid']
00264             if not self.subtests.has_key(test):
00265                 self.subtests[test] = sets.Set()
00266 
00267             if not self.tests_v.has_key(log['CASA']):
00268                 self.tests_v[log['CASA']] = {}
00269 
00270             if not self.tests_v[log['CASA']].has_key(test):
00271                 self.tests_v[log['CASA']][test] = sets.Set()
00272 
00273             if log['type'] == 'exec':
00274                 self.subtests[test].add(('', log['type']))
00275                 self.tests_v[log['CASA']][test].add(('', log['type']))
00276             else:
00277                 self.subtests[test].add((log['image'],log['type']))
00278                 self.tests_v[log['CASA']][test].add((log['image'],log['type']))
00279 
00280         #
00281         # Identify also hosts from entries in ./Log but no ./Result logfile exist
00282         if False:
00283             metalogs = os.listdir(reg_dir + '/Log/')
00284             for filename in metalogs:
00285                 m = re.compile('-(casa-dev-15|[^-]*)\.log$').search(filename)
00286                 if m != None:
00287                     h = m.groups()[0]
00288                     if not h in exclude_host:
00289                         hosts_set.add(h)
00290                 else:
00291                     raise Exception, "Illegal filename: %s" % filename
00292 
00293         # sort tests alphabetically and
00294         # casa revisions chronologically
00295         # (for display purposes only)
00296         self.tests = []
00297         for t in tests_set:
00298             self.tests.append(t)
00299         self.tests.sort()
00300 
00301         print "tests =", self.tests
00302         
00303         self.casas = []
00304         for c in casas_set:
00305             self.casas.append(c)
00306         self.casas.sort(reverse=True, cmp=cmp_version)
00307 
00308 
00309         # Get test short description (from latest log)
00310         self.test_description = {}
00311         test_date = {}
00312         for log in data:
00313             test = log['testid']
00314             # The following if statement translates to
00315             # "if the logfile contains the test description
00316             # and (we didn't know it yet or it's newer than
00317             # the one we know)"
00318             if log.has_key('description') and \
00319                (not self.test_description.has_key(test) or \
00320                 test_date[test] < log['date']):
00321                 
00322                 self.test_description[test] = log['description']
00323                 test_date[test] = log['date']    
00324 
00325         # Get OS string + latest CASA revision per host
00326         self.platform = {}
00327         self.casa_revision = {}
00328         platform_date = {}
00329         # the platform string layout has changed,
00330         # use the newest one
00331         for log in data:
00332             host = log['host']
00333             if not self.platform.has_key(host) or \
00334                    platform_date[host] < log['date']:
00335                 self.platform[host] = log['platform']
00336                 platform_date[host] = log['date']
00337 
00338             v = log['CASA']
00339             if not self.casa_revision.has_key(host) or \
00340                    cmp_version(v, self.casa_revision[host]) > 0:
00341                 self.casa_revision[host] = v
00342 
00343         latest_on_stable = self.casas[0] # global latest
00344 
00345         if True:
00346             # this part is now obsolete but not harmful...
00347             
00348             # Don't use the very latest revision (it's usually under construction)
00349             # (or set to latest if only 1 revision exists)
00350             self.global_latest = max(self.casa_revision.values())
00351             print "Latest revision =", self.global_latest
00352             latest2 = {}
00353             for log in data:
00354                 host = log['host']
00355                 v = log['CASA']
00356                 if cmp_version(v, self.global_latest) < 0 and \
00357                        (not latest2.has_key(host) or \
00358                         cmp_version(v, latest2[host])) > 0:
00359                     latest2[host] = v
00360                     
00361             for host in self.casa_revision.keys():
00362                 if not latest2.has_key(host):
00363                     latest2[host] = self.casa_revision[host]
00364             
00365 
00366             #print "Latest", self.casa_revision
00367             #print "2Latest", latest2
00368             
00369             self.casa_revision = latest2 
00370 
00371         # we need to loop through the hosts in the
00372         # same order for each HTML table row, so
00373         # build a list of unique hosts
00374         self.hosts_rel=[]  
00375         self.hosts_devel=[]  
00376         for host in hosts_set:
00377             if is_stable_branch(host):
00378                 self.hosts_rel.append(host)
00379             else:
00380                 self.hosts_devel.append(host)
00381 
00382         self.hosts_rel.sort()
00383         self.hosts_devel.sort()
00384         print "hosts =", self.hosts_rel, self.hosts_devel
00385 
00386         self.hosts = self.hosts_devel[:]
00387         self.hosts.extend(self.hosts_rel)
00388 
00389         #
00390         # Done collecting. Generate reports
00391         #
00392         pagename  = report_dir+'/test-report.html'
00393 
00394         if not os.path.isdir(report_dir):
00395             os.mkdir(report_dir)
00396 
00397         if skull != '':
00398             shutil.copyfile(skull,
00399                             report_dir + '/skullnbones.jpg')
00400 
00401         
00402         
00403         fd = open(pagename, "w")
00404         fd.write('<html></head>')
00405 
00406         print "Get archive size..."
00407         archive_size = commands.getoutput("du -hs " + result_dir + " | awk '{print $1}'")
00408         fd.write('<title>CASA regression tests</title>\n')
00409         fd.write('<body>\n')
00410         if revision == 'all':
00411             fd.write('[ All versions ]  <a href="CASA_latest/test-report.html">[ Latest test release ]</a><p>')
00412         else:
00413             fd.write('<a href="../test-report.html">[ All versions ]</a>  [ Latest test release ]<p>')
00414 #        fd.write('  <a href="../CASA_230/test-report.html">[ CASA 2.3.0 ]</a>')
00415 
00416 #        fd.write('<p>')
00417         fd.write('Generated on <i>'+time.strftime('%a %Y %b %d %H:%M:%S %Z')+'</i>')
00418 
00419         fd.write('<br>Summary of <i>'+str(len(data))+'</i> tests, archive size is <i>'+ \
00420                  archive_size + '</i>')
00421         fd.write('<dl><dt>Jump to<dd><a href="#revision_platform">Revision vs. platform</a> (short)')
00422         fd.write('<dd><a href="#test_platform">Test vs. platform</a> (short)')
00423         fd.write('<dd><a href="#revision_platform_full">Revision vs. platform</a> (full)')
00424         fd.write('<dd><a href="#test_platform_full">Test vs. platform</a> (full)')
00425         fd.write('</dl>')
00426         fd.write('<center>')
00427 
00428         if revision != 'all':
00429             a = latest_on_stable.find('(r')
00430             if a >=0:
00431                 a += len('(r')
00432                 b = latest_on_stable.find(')', a)
00433                 revision = latest_on_stable[a:b]
00434                 if len(revision) < 1:
00435                     raise Exception, "Could not parse revision number '%s'" \
00436                           % (latest_on_stable)
00437             else:
00438                 a = latest_on_stable.find('build #')
00439                 if a >=0:
00440                     a += len('build #')
00441                     b = latest_on_stable.find(')', a)
00442                     revision = latest_on_stable[a:b]
00443                 else:
00444                     raise Exception, "Could not parse revision number '%s'" \
00445                           % (latest_on_stable)
00446 
00447             fd.write('<br><h2>Revision '+revision+' only, test branch only</h2>')
00448             fd.write('<hr>')
00449 
00450             # Filter out all other versions
00451 
00452             self.casas = [latest_on_stable]
00453             
00454             data2 = []
00455             for d in data:
00456                 if d['CASA'] == latest_on_stable:
00457                     data2.append(d)
00458             data = data2
00459                 
00460 
00461         # Simple tables
00462         fd.write('<br><a name="revision_platform"></a>')
00463 
00464         extended = False
00465 
00466         self.generate_host_vs_revision('Revision vs. platform',
00467                                        reg_dir, report_dir,
00468                                        self.tests_v,
00469                                        False, extended, data, fd)
00470 
00471         self.dump_legend(fd)
00472 
00473         fd.write('<br><a name="test_platform"></a>')
00474 
00475         self.generate_host_vs_test(reg_dir, report_dir, revision,
00476                                    False, extended, data, fd)
00477 
00478         fd.write('<hr>')
00479         
00480         # Detailed tables
00481         fd.write('<br><a name="revision_platform_full"></a>')
00482         extended = True
00483         self.generate_host_vs_revision('Revision vs. platform',
00484                                        reg_dir, report_dir,
00485                                        self.tests_v,
00486                                        gen_plot, extended, data, fd)
00487 
00488         fd.write('<hr>')
00489 
00490         fd.write('<br><a name="test_platform_full"></a>')
00491         self.generate_host_vs_test(reg_dir, report_dir, revision,
00492                                    gen_plot, extended, data, fd)
00493 
00494         fd.write('</center></body>\n')
00495         fd.write('</html>\n')
00496         fd.close()
00497         print "Wrote", pagename
00498         
00499         os.system('touch '+report_dir+'/success')
00500         # because casapy always returns success, even if
00501         # executing this program failed
00502 
00503     def generate_host_vs_revision(self, heading, reg_dir, report_dir,
00504                                   tests,             # dictionary of tests->subtests, which
00505                                                      # should be considered
00506                                   gen_plot, extended, data, fd):
00507 
00508         result_dir = reg_dir + '/Result'
00509 
00510         status   = {}     # pass/fail/undetermined per (host, revision, test)
00511         run_date = {}     # latest run             per (host, revision, test)
00512         l        = {}     # logfilename            per (host, revision, test)
00513         summary  = {}     # no of pass/fail/undet. per (host, revision)
00514         for host in self.hosts:
00515             status[host] = {}
00516             run_date[host] = {}
00517             l[host] = {}
00518             summary[host] = {}
00519             for casa in self.casas:
00520                 status[host][casa] = {}
00521                 run_date[host][casa] = {}
00522                 l[host][casa] = {}
00523                 summary[host][casa] = {}
00524                 summary[host][casa]['pass'] = 0
00525                 summary[host][casa]['fail'] = 0
00526                 summary[host][casa]['undetermined'] = 0
00527                 for test in tests[casa].keys():
00528                     status  [host][casa][test] = {}
00529                     run_date[host][casa][test] = {}
00530                     l       [host][casa][test] = {}
00531 
00532         for log in data:
00533             host = log['host']
00534             casa = log['CASA']
00535             test = log['testid']
00536             if test in tests[casa].keys():
00537               if log['type'] == 'exec':
00538                   subtest = ('', log['type'])
00539               else:
00540                   subtest = ((log['image'],log['type']))
00541 
00542               if subtest in tests[casa][test]:
00543 
00544                 # If we have a 2nd entry for this (host,casa,test)
00545                 # then one of them is obsolete, warn about that.
00546                 if run_date[host][casa][test].has_key(subtest):
00547                     if run_date[host][casa][test][subtest] < log['date']:
00548                         print "%s deprecated by %s" %\
00549                               (l[host][casa][test][subtest], log['logfile'])
00550                     else:
00551                         print "%s deprecated by %s" %\
00552                               (log['logfile'], l[host][casa][test][subtest])
00553 
00554                 if not run_date[host][casa][test].has_key(subtest) or \
00555                        run_date[host][casa][test][subtest] < log['date']:
00556 
00557                     run_date[host][casa][test][subtest] = log['date']
00558                     l[host][casa][test][subtest] = log['logfile']
00559 
00560                     status[host][casa][test][subtest] = log['status']
00561 
00562         for host in self.hosts:
00563             for casa in self.casas:
00564                 # Count number of pass/fail/undet.
00565                 for test in tests[casa].keys():
00566                     for subtest in tests[casa][test]:
00567                         if status[host][casa][test].has_key(subtest):
00568                             s = status[host][casa][test][subtest]
00569                             
00570                             summary[host][casa][s] += 1 # increments number
00571                                                         # of s=pass/fail/undet. by 1
00572                             
00573                             #print host, casa, test, subtest
00574                         else:
00575                             summary[host][casa]['undetermined'] += 1
00576 
00577 
00578         fd.write('<h2><I><a name="rev_plat_det">'+heading+'</a></I></h2><br>')
00579         if extended:
00580             fd.write('Number of passed / failed / not-run tests.<br>')
00581             fd.write('If a test was run more than once, the results of the <i>latest run</i> counts.')
00582             fd.write('<br>')
00583             fd.write('<TABLE border=1 cellpadding=0 cellspacing=0 summary="Quick view over tests.">\n')
00584         else:
00585             fd.write('<TABLE border=1 cellpadding=0 cellspacing=0 summary="Quick view over tests.">\n')
00586 
00587             
00588         fd.write('<TR><TD></TD>')
00589         if len(self.hosts_devel) > 0:
00590             fd.write('<td align=center colspan='+str(len(self.hosts_devel))+'>active</td>')
00591         if len(self.hosts_rel) > 0:
00592             fd.write('<td align=center colspan='+str(len(self.hosts_rel))+'>test</td>')
00593         fd.write('</tr>')
00594         
00595         fd.write('<TR><TD></TD>')
00596         for host in self.hosts:
00597             if self.platform.has_key(host):
00598                 fd.write('<td align=center title="'+self.platform[host]+'">')
00599             else:
00600                 fd.write('<td align=center>')
00601             self.dump_host(fd, host, extended)
00602             fd.write('</td>')
00603 
00604         fd.write('<TD align=center><b>Revision<br>summary</b></TD>')
00605         fd.write('</TR>\n')    
00606 
00607         for casa in self.casas:
00608             print "Generate summary for", casa
00609             fd.write('<TR>')
00610             if casa in known_releases:
00611                 fd.write('<TD><nobr><b>' + casa + '</b></nobr></TD>')
00612             else:
00613                 fd.write('<TD><nobr>' + casa + '</nobr></TD>')
00614             tot_pass = 0
00615             tot_fail = 0
00616             tot_unde = 0
00617             tot_total = 0
00618             for host in self.hosts:
00619                 passes       = summary[host][casa]['pass']
00620                 failures     = summary[host][casa]['fail']
00621                 undetermined = summary[host][casa]['undetermined']
00622                 total = passes+failures+undetermined
00623                 self.dump_td_start(fd, passes, failures, undetermined, " align=center")
00624                 
00625                 if extended:
00626                     fd.write('%s / %s / %s (total %s)' %
00627                              (passes, failures, undetermined, total))
00628                 self.dump_td_end(fd)
00629 
00630                 tot_pass += passes
00631                 tot_fail += failures
00632                 tot_unde += undetermined
00633                 tot_total += total
00634 
00635             # summary for revision
00636             self.dump_td_start(fd, tot_pass, tot_fail, tot_unde, " align=center")
00637             if extended:
00638                 fd.write('%s / %s / %s (total %s)' %
00639                          (tot_pass, tot_fail, tot_unde, tot_total))
00640             self.dump_td_end(fd)
00641 
00642             fd.write('</TR>\n')
00643 
00644         fd.write('</TABLE>\n')
00645 
00646 
00647     def generate_host_vs_test(self, reg_dir, report_dir, revision,
00648                               gen_plot, extended, data, fd):
00649         fd.write('<h2><I><a name="test_plat_det">Test vs. platform</a></I></h2><br>')
00650         if extended:
00651             if revision != 'all':
00652                 if not same_version_per_host:
00653                     fd.write('For each combination of (test, host) the results of the <i>latest run</i> ')
00654                     fd.write('is reported. Note: The revision number might vary within the same column/row')
00655                 else:
00656                     fd.write('For each host the results of the <i>latest available CASA revision</i>, ')
00657                     fd.write('excluding '+self.global_latest+' which is under construction, is reported. ')                
00658                     fd.write('<br>')
00659                     fd.write('If a test was run more than once with a given revision, the results of the <i>latest run</i> is reported. ')
00660             fd.write('<br>')
00661             fd.write('<dl><dt>')
00662             fd.write('<dd>Logfiles contain the Python session\'s output to stdout/stderr, messages sent to casalogger, and the contents of any *.log files created.')
00663             fd.write('<dd>On execution error the last error message from the log file is shown.')
00664             fd.write('<dd>Follow the history links to get an overview over how a test outcome correlates with platform and CASA version.')
00665             
00666             fd.write('<dd><small>session log: Messages from the full casapy session, including messages from the framework, and excluding logfiles produced by the test itself. ')
00667             fd.write('Used to track problems with the casapy installation, X connection etc.</small>')
00668             fd.write('</dt></dl>')
00669             fd.write('<TABLE border=1 cellpadding=0 cellspacing=0 summary="Quick view over tests.">\n')
00670         else:
00671             fd.write('<TABLE border=1 cellpadding=0 cellspacing=0>\n')
00672 
00673         summary_subtest = {} # count the number of pass/fail per test and per host
00674         summary_host = {}
00675         fd.write('<TR><TD colspan=2 rowspan=2></TD>')
00676 
00677         if len(self.hosts_devel) > 0:
00678             fd.write('<td align=center colspan='+str(len(self.hosts_devel))+'>active</td>')
00679         if len(self.hosts_rel) > 0:
00680             fd.write('<td align=center colspan='+str(len(self.hosts_rel))+'>test</td>')
00681         fd.write('</tr>')
00682         
00683         for host in self.hosts:
00684             summary_host[host] = {}
00685             summary_host[host]['pass'] = 0
00686             summary_host[host]['fail'] = 0
00687             summary_host[host]['undet'] = 0
00688             summary_host[host]['time'] = 0.0
00689 
00690             if self.platform.has_key(host):
00691                 fd.write('<td align=center title="'+self.platform[host]+'">')
00692             else:
00693                 fd.write('<td align=center>')
00694             self.dump_host(fd, host, extended)
00695 
00696             if extended:
00697                 if not os.path.isdir(reg_dir + '/Log'):
00698                     raise Exception, "Missing directory: " + reg_dir + '/Log'
00699                 latest_metalog = commands.getoutput('/bin/ls -1tr ' + reg_dir + '/Log/ | grep -E "\-'+host+'.log$" | tail -1')
00700                 if len(latest_metalog) > len('.log'):
00701                     shutil.copyfile(reg_dir + '/Log/' + latest_metalog, \
00702                                     report_dir + '/' + latest_metalog)
00703                     fd.write(' <small><a href="'+latest_metalog+'">latest run</a></small>')
00704 
00705                 if same_version_per_host:
00706                     if self.casa_revision.has_key(host):
00707                         fd.write('<br><i>' + self.casa_revision[host]+'</i>')
00708                     else:
00709                         fd.write('<br><i>???</i>')
00710 
00711             fd.write('</td>')
00712             
00713         fd.write('<TD><b>Test branch summary</b></TD>')
00714         fd.write('</TR>\n')    
00715 
00716         subtests_list = {}
00717 
00718         for test in self.tests:      # Loop through set of all tests,
00719                                      # not just test for any particular version
00720 
00721             # loop through the subtests so that the 'exec' subtest
00722             # is always processed first
00723             subtests_list[test] = [s for s in self.subtests[test]]
00724             subtests_list[test].sort(cmp=cmp_exec)
00725             
00726             print "Subtests for", test, "=", subtests_list[test]
00727 
00728             summary_filename = report_dir+"/summary_"+test+".html"
00729             fd.write('<TR>')
00730             fd.write('<TD rowspan='+str(1+len(self.subtests[test]))+' align=center><big><b>')
00731             if extended:
00732                 fd.write('<a name="'+test+'"></a>')
00733                 fd.write('<a href="summary_'+test+'.html">'+shorten(test)+'</a>')
00734 
00735                 f = open(summary_filename, "w")
00736                 
00737                 f.write('<html><head></head><body><center>\n')
00738 
00739                 print "Creating %s..." % (summary_filename)
00740 
00741                 # Make a summary of only the current test
00742                 # as function of CASA version
00743                 tests_to_consider = {}
00744                 for casa in self.casas:
00745                     tests_to_consider[casa] = {test: self.subtests[test]}
00746                 
00747                 self.generate_host_vs_revision(test+' all subtests',
00748                                                reg_dir, report_dir,
00749                                                tests_to_consider,
00750                                                False, False,
00751                                                data, f)
00752                 self.dump_legend(f)
00753                 f.write('</center></body></html>')
00754                 f.close()               
00755             else:
00756                 fd.write('<a href="#'+test+'">'+shorten(test, 10)+'</a>')
00757                 
00758             fd.write('</big></b>')
00759             if extended and self.test_description.has_key(test):
00760                 fd.write('<br><br>' + self.test_description[test].replace("<", "&lt;").replace(">", "&gt"))
00761             fd.write('</TD>')
00762             fd.write('</TR>\n')
00763 
00764             for subtest in subtests_list[test]:
00765                 summary_subtest['pass'] = 0
00766                 summary_subtest['fail'] = 0
00767                 summary_subtest['undet'] = 0
00768                 fd.write('<TR>')
00769                 fd.write('<td align=left>')
00770 
00771                 if extended:
00772                     fd.write('<dl><dt>')
00773                 if subtest[1] == 'exec':
00774                     fd.write('Execution')
00775                 else:
00776                     txt = {}
00777                     txt['ms'] = 'Visibilities'
00778                     txt['simple'] = 'Statistics'
00779                     txt['cube']   = 'Cubefit'
00780                     txt['pol1']   = 'PolImage1'
00781                     txt['pol2']   = 'PolImage2'
00782                     txt['pol4']   = 'PolImage4'
00783                     if extended:
00784                         fd.write(shorten(subtest[0]))
00785                         fd.write('<dt>'+txt[subtest[1]])
00786                     else:
00787                         fd.write(shorten(subtest[0], 10))
00788                         # don't write test type
00789                 if extended:
00790                     if subtest[1] == 'exec':
00791                         fd.write('<dd>History:')
00792                     label = {}
00793                     label['exec'] = 'Status'
00794                     if subtest[1] == 'exec':
00795                         label['time'] = 'Time'
00796                         label['mvirtual'] = 'Virt. memory'
00797                         label['mresident'] = 'Res. memory'
00798                         label['filedesc'] = 'File desc.'
00799 
00800                     for i in ['exec', 'time', 'mvirtual', 'mresident', 'filedesc']:
00801                         if label.has_key(i):
00802                             fd.write('<dd>')
00803                             if subtest[1] == 'exec':
00804                                 basename = 'history-'+test+'-'+i
00805                                 fd.write('<a href="'+basename+'.html">'+label[i]+'</a>')
00806                             else:
00807                                 basename = ('history-'+test+'-'+subtest[0]+'-'+subtest[1]).replace('/', '-').replace('--', '-')
00808                                 fd.write('<a href="'+basename+'.html">History</a>')
00809 
00810                             twin_plots = open(report_dir+'/'+basename+'.html', 'w')
00811                             twin_plots.write('<html><head><title>'+basename+'</title></head><body>')
00812                             twin_plots.write('<img src="'+basename+'-0.png"><br>')
00813                             twin_plots.write('<img src="'+basename+'-1.png">')
00814                             twin_plots.write('</body></html>')
00815                             twin_plots.close()
00816                     fd.write('</dl>')
00817                 fd.write('</td>')
00818                 
00819                 for host in self.hosts:
00820                     self.dump_entry(test, subtest, host,
00821                                     summary_subtest,
00822                                     summary_host,
00823                                     data, fd, reg_dir, report_dir,
00824                                     extended, revision)
00825 
00826                 self.dump_td_start(fd, \
00827                                    summary_subtest['pass'], \
00828                                    summary_subtest['fail'], \
00829                                    summary_subtest['undet'], \
00830                                    ' align=center title="'+test+'"')
00831                 if extended:
00832                     fd.write('%s / %s / %s' % \
00833                              (summary_subtest['pass'], \
00834                               summary_subtest['fail'], \
00835                               summary_subtest['undet']))
00836                 self.dump_td_end(fd)
00837                 fd.write('</TR>\n')
00838             # end for each subtest
00839             if gen_plot:
00840                 self.history_plot(report_dir+'/history-'+test, test, data)
00841         # end for each test
00842 
00843         fd.write('<TR><TD COLSPAN=2><b>Platform summary</b></TD>')
00844         total={}
00845         total['pass'] = 0
00846         total['fail'] = 0
00847         total['undet'] = 0
00848         for host in self.hosts:
00849             self.dump_td_start(fd, \
00850                                summary_host[host]['pass'], \
00851                                summary_host[host]['fail'], \
00852                                summary_host[host]['undet'], \
00853                                ' align=center title="'+host+'"')
00854             if extended:
00855                 fd.write('%s / %s / %s' % (summary_host[host]['pass'], summary_host[host]['fail'], summary_host[host]['undet']))
00856                 fd.write('<br>Total time: %.1f h' % (summary_host[host]['time']/3600.0))
00857             self.dump_td_end(fd)
00858             total['pass']  += summary_host[host]['pass']
00859             total['fail']  += summary_host[host]['fail']
00860             total['undet'] += summary_host[host]['undet']
00861 
00862         self.dump_td_start(fd, \
00863                            total['pass'], \
00864                            total['fail'], \
00865                            total['undet'], \
00866                            " align=center")
00867 
00868         if extended:
00869             fd.write('%s / %s / %s' % (total['pass'], total['fail'], total['undet']))
00870         self.dump_td_end(fd)
00871         fd.write('</TR>\n')
00872 
00873 
00874 
00875         
00876         fd.write('</TABLE>\n')
00877 
00878     def dump_legend(self, fd):
00879         fd.write('Legend: <br>Releases in <b>bold</b> <table border="1" cellpadding=0 cellspacing=0><tr>')
00880         self.dump_td_start(fd, 1, 0, 0, " align=center")
00881         fd.write('All tests passed')
00882         self.dump_td_end(fd)
00883         fd.write('</tr><tr>')
00884         
00885         self.dump_td_start(fd, 1, 0, 1, " align=center")
00886         fd.write('Some test(s) not run<br>No failures')
00887         self.dump_td_end(fd)
00888         fd.write('</tr><tr>')
00889         self.dump_td_start(fd, 1, 1, 0, " align=center")
00890         fd.write('Some test(s) passed<br>Some test(s) failed')
00891         self.dump_td_end(fd)
00892         fd.write('</tr><tr>')
00893         self.dump_td_start(fd, 0, 1, 0, " align=center")
00894         fd.write('All tests failed')
00895         self.dump_td_end(fd)        
00896         fd.write('</tr></table>')
00897         
00898     def dump_entry(self, test, subtest, host, \
00899                    summary_subtest, summary_host, \
00900                    data, fd, reg_dir, report_dir, \
00901                    extended, revision):
00902 
00903         result_dir = reg_dir + '/Result'
00904 
00905         # Find the latest run; CASA version has 1st priority, date has 2nd priority
00906         log = None
00907         latest_run_version = ''
00908         latest_run_date = ''
00909         for l in data:
00910             if l['host'] == host and \
00911                (same_version_per_host == False or l['CASA'] == self.casa_revision[host]) and \
00912                l['testid'] == test and \
00913                l['type'] == subtest[1] and \
00914                (subtest[0] == '' or l['image'] == subtest[0]):
00915 
00916                 if latest_run_version == '' or \
00917                    (cmp_version(l['CASA'], latest_run_version) > 0 or \
00918                     cmp_version(l['CASA'], latest_run_version) == 0 and \
00919                     l['date'] > latest_run_date):
00920                     
00921                     latest_run_version = l['CASA']
00922                     latest_run_date    = l['date']
00923                     log = l
00924 
00925         if host in self.hosts_devel:
00926             branch = "active"
00927         else:
00928             branch = "test"
00929 
00930         coords = ' title="' + branch + ': ' + test + ' \\ '+host+'"'
00931         
00932         if log != None:
00933             #print log
00934             summary_host[host][log['status']] += 1
00935             if is_stable_branch(host):
00936                 summary_subtest[log['status']] += 1
00937 
00938             if log['type'] != 'exec':
00939                 coords = ' title="' + branch + ': ' + log['image']+'-'+log['type'] + ' \\ '+host+'"'
00940             self.dump_td_start(fd,
00941                                1,
00942                                log['status'] == 'fail',
00943                                0, coords)
00944                  
00945             if extended:
00946                 #link to resultlog and runlog files
00947                 if log['type'] == 'exec':
00948                     fd.write('<a href="'+log['logfile']+'">')
00949                 else:
00950                     # delegate to subpage with more details
00951                     basename = (test+'-'+host+'-'+subtest[0]+'-'+subtest[1]).replace('/', '-').replace('--', '-')
00952                     fd.write('<a href="'+basename+'.html">')
00953                     f = open(report_dir+'/'+basename+'.html', 'w')
00954                     f.write('<html><head><title>'+basename+'</title></head><body>')
00955                     f.write('<a href="'+log['logfile']+'">log</a>')
00956                     
00957                 # and copy the log file and runlog to the target dir
00958                 from_dir = os.path.dirname('%s/%s' % (result_dir, log['logfile']))
00959                 to_dir   = os.path.dirname('%s/%s' % (report_dir, log['logfile']))
00960 
00961                 if not os.path.isdir(to_dir):
00962                     os.makedirs(to_dir)
00963                 shutil.copyfile('%s/%s' % (result_dir, log['logfile']), \
00964                                 '%s/%s' % (report_dir, log['logfile']))
00965 
00966                 fd.write('%s' % log['status'])
00967                 fd.write('</a>')
00968                 if log.has_key('reason'):
00969                     fd.write(': '+log['reason'])
00970                 if log.has_key('runlog'):
00971                     if os.path.isfile(from_dir+'/'+log['runlog']):
00972                         shutil.copyfile(from_dir+'/'+log['runlog'],
00973                                         to_dir+'/'+log['runlog'])
00974                     else:
00975                         print >> sys.stderr, \
00976                               "Error: %s: Missing file: %s" % \
00977                               (log['logfile'], log['runlog'])
00978                         commands.getstatusoutput('touch '+ to_dir+'/'+log['runlog'])
00979                         #fixme!!! touch
00980 
00981                     fd.write('<br>')
00982                     fd.write('<a href='+os.path.dirname(log['logfile'])+'/'+log['runlog']+'>log ')
00983                     fd.write('(')
00984                     # don't repeat CASA revision: fd.write('rev. ' % log['CASA'].split('#')[1].split(')')[0])
00985                     d = log['date'].split('_')
00986                     fd.write('%s-%s-%s %s:%s)</a>' % \
00987                              (d[0], d[1], d[2], d[3], d[4]))
00988 
00989                 fd.write('<BR>')
00990                 if log['type'] == 'exec':
00991                     
00992                     summary_host[host]['time'] += float(log['time'])
00993                     
00994                     if log['status'] == 'pass':
00995                         fd.write('Time of run: %.2f s'% float(log['time']))
00996                         if log.has_key('resource'):
00997 
00998                             profile_html = 'profile-'+test+'-'+host+'.html'
00999                             profile_png  = 'profile-'+test+'-'+host+'.png'
01000                             profile_cpu_png  = 'profile_cpu-'+test+'-'+host+'.png'
01001 
01002                             t, mvirtual, mresident, nfiledesc, \
01003                                cpu_us, cpu_sy, cpu_id, cpu_wa = \
01004                                self.parse_resources(log)
01005 
01006                             if (len(mvirtual) > 0):
01007                                 max_virtual  = "%.0f MB" % (max(mvirtual))
01008                                 max_resident = "%.0f MB" % (max(mresident))
01009                                 max_filedesc = "%d" % max(nfiledesc)
01010                             else:
01011                                 max_virtual  = "N/A"
01012                                 max_resident = "N/A"
01013                                 max_filedesc = "N/A"
01014                                 
01015                                                                 
01016                             fd.write('<br><a href='+profile_html+'>')
01017                             fd.write('Memory(max): ' + max_virtual + ' virt. / ' + max_resident + ' res.')
01018                             fd.write('</a>')
01019 
01020                             fd.write('<br><a href='+profile_html+'>')
01021                             fd.write('File desc.(max): ' + max_filedesc)
01022                             fd.write('</a>')
01023 
01024                             fd.write('<br><a href='+profile_html+'>')
01025                             
01026                             if (len(cpu_us) > 0):
01027                                 avg_cpu_us = "%.0f" % (sum(cpu_us)*1.0/len(cpu_us))
01028                                 avg_cpu_sy = "%.0f" % (sum(cpu_sy)*1.0/len(cpu_us))
01029                                 avg_cpu_id = "%.0f" % (sum(cpu_id)*1.0/len(cpu_us))
01030                                 avg_cpu_wa = "%.0f" % (sum(cpu_wa)*1.0/len(cpu_us))
01031                                 fd.write("CPU(avg): %s%%us %s%%sy %s%%wa %s%%id" % \
01032                                          (avg_cpu_us, \
01033                                           avg_cpu_sy, \
01034                                           avg_cpu_wa, \
01035                                           avg_cpu_id))
01036                             else:
01037                                 fd.write('CPU(avg): N/A')
01038                             fd.write('</a>')
01039 
01040                             self.create_profile_html(t, mvirtual, mresident, nfiledesc, \
01041                                                      cpu_us, cpu_sy, cpu_id, cpu_wa, \
01042                                                      report_dir,
01043                                                      profile_png, profile_html, \
01044                                                      profile_cpu_png, \
01045                                                      test, host)
01046                     elif log['status'] == 'fail':
01047                         # filter depends on error message format
01048                         sed_filter = 's/STDERR [^\:]*:[^\:]*: //g'
01049                         last_error_message = commands.getoutput( \
01050                             "egrep '^STDERR' "+to_dir+'/'+log['runlog']+" | tail -1 | sed '"+sed_filter+"'")
01051 
01052                         nexttolast_error_message = commands.getoutput( \
01053                             "egrep '^STDERR' "+to_dir+'/'+log['runlog']+" | tail -2 | head -1 | sed '"+sed_filter+"'")
01054                         fd.write('<small>')
01055                         fd.write(shorten(nexttolast_error_message, 40) + '<br>')
01056                         fd.write(shorten(last_error_message, 40))
01057                         fd.write('</small>')
01058                     # endif status == ...
01059 
01060                 # endif type == 'exec'
01061                 else:
01062                     if log['type'] == 'simple':
01063                         if log.has_key('image_min'):
01064                             # if image was not produced, these statistics were not calculated
01065                             
01066                             f.write('<pre>')
01067                             f.write('Image    min: %g\nmax: %g\nrms: %g \n' %
01068                                     (float(log['image_min']),
01069                                      float(log['image_max']),
01070                                      float(log['image_rms'])))
01071                             f.write('Template min: %g\nmax: %g\nrms: %g \n' %
01072                                     (float(log['ref_min']),
01073                                      float(log['ref_max']),
01074                                      float(log['ref_rms'])))
01075                         f.write('</pre>')
01076                     elif log['type'] == 'cube':
01077                         if log.has_key('image_x'):
01078                             f.write('<pre>')
01079                             f.write('On image    \n  optimized coord:\n   [%g,%g]\n  FWHM: %.6f\n\nfit #1\n  optimized coord:\n   [%g,%g]\n  FWHM: %g \n\n' %
01080                                     (float(string.replace(string.replace(log['image_x'], '[', ''), ']', '')),
01081                                      float(string.replace(string.replace(log['image_y'], '[', ''), ']', '')),
01082                                      float(string.replace(string.replace(log['image_fwhm'], '[', ''), ']', '')),
01083                                      float(string.replace(string.replace(log['image_fit1_x'], '[', ''), ']', '')),
01084                                      float(string.replace(string.replace(log['image_fit1_y'], '[', ''), ']', '')),
01085                                      float(string.replace(string.replace(log['image_fit1_fwhm'], '[', ''), ']', ''))))
01086                             f.write('On Template \n  optimized coord:\n   [%g,%g]\n  FWHM: %.6f\n\nfit #1\n  optimized coord:\n   [%g,%g]\n  FWHM: %g \n'%
01087                                     (float(string.replace(string.replace(log['ref_x'], '[', ''), ']', '')),
01088                                      float(string.replace(string.replace(log['ref_y'], '[', ''), ']', '')),
01089                                      float(string.replace(string.replace(log['ref_fwhm'], '[', ''), ']', '')),
01090                                      float(string.replace(string.replace(log['ref_fit1_x'], '[', ''), ']', '')),
01091                                      float(string.replace(string.replace(log['ref_fit1_y'], '[', ''), ']', '')),
01092                                      float(string.replace(string.replace(log['ref_fit1_fwhm'], '[', ''), ']', ''))))
01093                             f.write('</pre>')
01094                     elif log['type'] == 'pol1' or \
01095                          log['type'] == 'pol2' or \
01096                          log['type'] == 'pol4':
01097                         for pol in ['I', 'Q', 'U', 'V']:
01098                             if log.has_key('image_'+pol+'_ra'):
01099                                 f.write('<pre>')
01100                                 for i in [0, 1]:
01101                                     f.write('Pol %s: %s:\nra %s  dec %s \nbmaj %s bmin %s \nbpa %s flux %s' %
01102                                              (pol, \
01103                                               ['Component found', 'Template'][i],
01104                                               log[['image_', 'ref_'][i]+pol+'_ra'],
01105                                               log[['image_', 'ref_'][i]+pol+'_dec'],
01106                                               log[['image_', 'ref_'][i]+pol+'_bmax'],
01107                                               log[['image_', 'ref_'][i]+pol+'_bmin'],
01108                                               log[['image_', 'ref_'][i]+pol+'_bpa'],
01109                                               log[['image_', 'ref_'][i]+pol+'_flux']))
01110                                     f.write('<br>')
01111                                 f.write('</pre>')
01112                                 f.write('<br>')
01113                             if log.has_key('image_'+pol+'_min'):
01114                                 f.write('<pre>')                        
01115                                 f.write('Image '+pol+'\n    min: %g\n    max: %g\n    rms: %g\n' %
01116                                          (float(log['image_'+pol+'_min']),
01117                                           float(log['image_'+pol+'_max']),
01118                                           float(log['image_'+pol+'_rms'])))
01119                                 f.write('Template '+pol+'\n    min: %g\n    max: %g\n    rms: %g\n' %
01120                                          (float(log['ref_'+pol+'_min']),
01121                                           float(log['ref_'+pol+'_max']),
01122                                           float(log['ref_'+pol+'_rms'])))
01123                                 f.write('</pre>')
01124                                 f.write('<br>')
01125                     elif log['type'] == 'ms':
01126                         f.write('<pre>')
01127                         f.write('MS       min: %g\nmax: %g\nrms: %g \n' %
01128                                 (float(log['ms_amp_min']),
01129                                  float(log['ms_amp_max']),
01130                                  float(log['ms_amp_rms'])))
01131                         f.write('Template min: %g\nmax: %g\nrms: %g \n' %
01132                                 (float(log['ref_amp_min']),
01133                                  float(log['ref_amp_max']),
01134                                  float(log['ref_amp_rms'])))
01135                         f.write('</pre>')
01136                     else:
01137                         raise Exception, 'Unknown test type '+log['type']
01138                     
01139                     self.link_to_images(log, from_dir, to_dir, f)
01140                     f.write('</body></html>')
01141                     f.close()
01142                 #endif type != 'exec'
01143             # endif extended
01144         # endif log != None
01145         else:
01146 
01147             # If there's no log[] entry produced, and if the session log
01148             # doesn't say "casapy returned 0", then the session must have crashed
01149             #
01150             # The version of the session log matches if either
01151             # - no version string is found, or
01152             # - we're reporting all revision, or
01153             # - the revision number can be grep'ed from the session log
01154             #
01155             
01156             framework_log = reg_dir + '/Log/run-' + test + '-' + host + '.log'
01157             if subtest[1] == 'exec' and \
01158                    os.path.isfile(framework_log) and \
01159                    os.system('grep >/dev/null "casapy returned 0" ' + framework_log) != 0 and \
01160                    (os.system('grep CASA.version ' + framework_log) != 0 or revision == 'all' or
01161                     os.system('grep -w r' + revision + ' ' + framework_log) == 0):
01162                         self.dump_td_start(fd,
01163                                            0,
01164                                            1,
01165                                            0,
01166                                            'align=center ' + coords)
01167                         if extended:
01168                             fd.write('CRASHED')
01169                             if os.system('tail -20 ' + framework_log + ' | grep TIMEOUT >/dev/null') == 0:
01170                                 fd.write('<br>TIMEOUT')
01171                                 
01172                             elif os.system('tail -10 ' + framework_log + ' | grep "casapy returned" >/dev/null') == 0:
01173                                 error_message = commands.getoutput('tail -10 ' + framework_log + ' | grep -B1 "casapy returned" | head -1')
01174                                 fd.write('<br><img src="skullnbones.jpg"><br>' + shorten(error_message, 40))
01175                             else:
01176                                 fd.write('<br>??? unknown reason ???')
01177 
01178             else:
01179                 fd.write('<td align=center '+coords+'>')
01180                 if extended and subtest[1] == 'exec':
01181                     fd.write('Test not run yet')
01182             if extended and subtest[1] != 'exec':
01183                 fd.write('Test not done')
01184             summary_host[host]['undet'] += 1
01185             if is_stable_branch(host):
01186                 summary_subtest['undet'] += 1
01187 
01188         if extended and log != None:
01189             if not same_version_per_host:
01190                 fd.write('<br>'+log['CASA'])
01191             if log.has_key('data_version'):
01192                 fd.write('<br>Data version: '+log['data_version'])
01193 
01194             if log['type'] == 'exec':
01195                 from_dir = os.path.dirname('%s/%s' % (result_dir, log['logfile']))
01196                 to_dir   = os.path.dirname('%s/%s' % (report_dir, log['logfile']))
01197 
01198                 prof_file = from_dir + "/cProfile.profile"
01199                 plot_file = "python_profile-"+test+"-"+host+".png"
01200                 if os.path.isfile(prof_file):
01201                     print "Creating python callgraph..."
01202                     # This might fail with a "... marshal blah, blah ..." error
01203                     # if there's a mismatch between this python and
01204                     # CASA's python which created the binary cProfile.profile
01205                     lib = "lib64" if os.uname()[4] == 'x86_64' else "lib"
01206                     gprof2dot = "/tmp/gprof2dot.py"
01207                     if not os.path.isfile(gprof2dot):
01208                         gprof2dot = "/export/data/casa-regressions/bin/gprof2dot.py"
01209                     os.system("/usr/" + lib + "/casapy/bin/python " + gprof2dot + " -f pstats " +\
01210                               prof_file + " | dot -Tpng -o " +\
01211                               report_dir + '/' + plot_file)
01212                     fd.write('<br><a href="'+plot_file+'">Python profile</a>')
01213 
01214 
01215                 cpp_dot  = from_dir + '/cpp_profile.dot'
01216                 cpp_txt  = from_dir + '/cpp_profile.txt'
01217                 cpp_src  = from_dir + '/cpp_profile.cc'
01218                 cpp_png  = from_dir + '/cpp_profile.png'
01219                 cpp_html = to_dir + '/cpp_profile.html'
01220                 if os.path.isfile(cpp_dot):
01221                     print "Creating C++ profile ", cpp_dot
01222                     #cmd = "cat " + cpp_dot + " | dot -Tpng -o " + to_dir + '/cpp_profile.png'
01223                     #print cmd
01224                     #os.system(cmd)
01225 
01226                     if os.path.isfile(cpp_txt):
01227                         shutil.copyfile(from_dir+'/cpp_profile.txt',
01228                                         to_dir  +'/cpp_profile.txt')
01229                     if os.path.isfile(cpp_src):
01230                         shutil.copyfile(from_dir+'/cpp_profile.cc',
01231                                         to_dir  +'/cpp_profile.cc')
01232                     if os.path.isfile(cpp_png):
01233                         shutil.copyfile(from_dir+'/cpp_profile.png',
01234                                         to_dir  +'/cpp_profile.png')
01235                     elif os.path.isfile(cpp_dot):
01236                         # If the dot tool wasn't on the test machine
01237                         # create the .png now
01238                         os.system("cat " + cpp_dot + " | dot -Tpng -o " + \
01239                                   to_dir + '/cpp_profile.png')
01240 
01241                     fd.write('<br><a href="'+os.path.dirname(log['logfile'])+'/cpp_profile.html">C++ profile</a>')
01242                     f = open(cpp_html, 'w')
01243                     f.write('<html><head><title>'+cpp_html+'</title></head><body>')
01244                     f.write('<table cellpadding="10">')
01245                     f.write('<tr><td rowspan="2">'+test+' timing profile</td><td>as <a href="cpp_profile.png">callgraph</a><br></td></tr><tr><td>as <a href="cpp_profile.txt">text</a></td></tr>')
01246                     f.write('<tr><td colspan="2">'+test+' <a href="cpp_profile.cc">annotated source</a></td></tr>')
01247                     f.write('</table>')
01248                     f.write('<p>Note: The timing profile includes CPU time only. Time spent waiting for I/O is not included. Check <a href="../profile-'+test+'-'+host+'.html">here</a> to see if this test is CPU bound or I/O bound.')
01249                     f.write('<p>Note: The timing profile covers the casapy process including any threads. Time which was spent in casapy subprocesses (such as asdm2MS) is not included.')
01250                     f.write('<p>Note: The graphical and textual profiles contain the same information but displayed in different ways. However, functions (nodes) accounting for less than 1 percent of the total execution time, as well as function calls (edges) accounting for less than 0.1 percent of the overall execution time, are excluded from the graphical representation. For that reason, the percentages may not add up exactly.')
01251                     f.write('<p>For further information see <a href="http://oprofile.sourceforge.net">oprofile</a> ')
01252                     f.write('and <a href="http://code.google.com/p/jrfonseca/wiki/Gprof2Dot">Gprof2Dot</a>.')
01253                     f.write('</body></html>')
01254                     f.close()
01255             
01256         if extended and subtest[1] == 'exec':          
01257             framework_log = 'run-'     + test + '-' + host + '.log'
01258 
01259             if os.path.isfile(reg_dir + '/Log/' + framework_log):
01260                 shutil.copyfile(reg_dir + '/Log/' + framework_log, \
01261                                 report_dir + '/' + framework_log)
01262                 fd.write('<br><small><a href="'+framework_log+'">session log</a></small>')
01263                 
01264         self.dump_td_end(fd)            
01265 
01266     def link_to_images(self, log, from_dir, to_dir, fd):
01267         # Add link to images for image tests
01268         i = 1
01269         while log.has_key('imagefile_'+str(i)):
01270             fn = log['imagefile_'+str(i)]
01271             print "Copy image:", fn
01272             if os.path.isfile(from_dir+'/'+fn):
01273                 shutil.copyfile(from_dir+'/'+fn,
01274                                 to_dir  +'/'+fn)
01275             fd.write('<br>Image: '+fn+'<img src="'+os.path.dirname(log['logfile'])+'/'+fn+'">')
01276             i = i + 1
01277 
01278     def dump_td_start(self, fd, passed, failed, undetermined, extra=""):
01279         # The overall status is undetermined iff there
01280         # are only passed + still undetermined tests
01281         if failed > 0:
01282             if passed == 0:
01283                 color = 'ff0000'
01284             else:
01285                 color = 'ff6058'
01286         elif undetermined > 0:
01287             if passed > 0:
01288                 color = 'ffff80'
01289             else:
01290                 color = 'ffffff'
01291         else:
01292             color = '60ff60'
01293 
01294         if colormania:
01295             fd.write('<TD BGCOLOR='+color)
01296         else:
01297             fd.write('<TD><font COLOR='+color)
01298         fd.write(' %s>' % extra)
01299 
01300     def dump_td_end(self, fd):
01301         if colormania:
01302             fd.write('</TD>')
01303         else:
01304             fd.write('</font></TD>')
01305 
01306     def parse_resources(self, log):
01307         # expected format:
01308         # version 1: t0,mv0,mr0,nf0;t1,mv1,mr1,nf1;...
01309         # version 2: t0,mv0,mr0,nf0,cpuus0,cpusy0,cpuid0,cpuwa0;...
01310         samples = log['resource'].split(';')
01311         t = []
01312         mvirtual = []
01313         mresident = []
01314         nfiledesc = []
01315         cpu_us = []
01316         cpu_sy = []
01317         cpu_id = []
01318         cpu_wa = []
01319         
01320         for s in samples:
01321             if (s != ''):
01322                 ss = s.split(',')
01323                 t.append(float(ss[0]))
01324                 nfiledesc.append(int(ss[3]))
01325                 if log['version'] == '1':
01326                     mvirtual.append(float(ss[1])/1024.0/1024)
01327                     mresident.append(float(ss[2])/1024.0/1024)
01328                 else:
01329                     mvirtual.append(float(ss[1]))
01330                     mresident.append(float(ss[2]))
01331                     cpu_us.append(float(ss[4]))
01332                     cpu_sy.append(float(ss[5]))
01333                     cpu_id.append(float(ss[6]))
01334                     cpu_wa.append(float(ss[7]))
01335                     
01336         return t, mvirtual, mresident, nfiledesc, \
01337                cpu_us, cpu_sy, cpu_id, cpu_wa
01338 
01339     def read_log(self, result_dir, revision):
01340         #
01341         #  Convert logfiles in a list of dictionaries
01342         #
01343 
01344         # Read from a single (monolithic) file of concatenated logfiles?
01345         monofile = result_dir + "/../all-result.txt"
01346         mono = os.path.exists(monofile)
01347         
01348         if mono:
01349             fd = open(monofile)
01350             line = fd.readline().rstrip()
01351             print "Parsing monolithic logfile, %s..." % (monofile)
01352 
01353         else:
01354             # ... or recursively find all log files
01355             # This is very expensive in I/O when there are
01356             # ~10,000 to ~100,000 small log files
01357             find_cmd = 'find '+result_dir+' -name result\*.txt'
01358             findout = commands.getoutput(find_cmd)
01359             alllogs = ['']
01360             if(findout != ''):
01361                 alllogs=findout.split('\n')
01362 
01363             log_index = 0
01364             print "Parsing %s logfiles..." % str(len(alllogs))
01365 
01366             #print alllogs
01367 
01368         data = []
01369         i = 0
01370         while True:
01371             if mono:
01372                 if line:
01373                     logfile = line
01374                 else:
01375                     break
01376             else:
01377                 if log_index < len(alllogs):
01378                     logfile = alllogs[log_index]
01379                     log_index += 1
01380                     fd = open(logfile, "r")
01381                 else:
01382                     break
01383 
01384             i += 1
01385             if (i % 100) == 0:
01386                 sys.stdout.write('.')
01387                 sys.stdout.flush()
01388             #print "match:", logfile
01389 
01390             data_file = {}
01391             lineno = 0
01392             line = fd.readline().rstrip() ; lineno += 1
01393             data_file['logfile'] = logfile.split(result_dir)[1].lstrip('/')
01394             while line and (len(line) == 0 or line[0] != "/"):
01395                 ###
01396                 ### workaround the crap that is inserted because the subversion client is too old...
01397                 ###
01398                 line = line.replace("'svn: This client is too old to work with working copy '.'.  You need", "'$Rev: 4128 $'                           # Data repository version")
01399                 if line == "to get a newer Subversion client, or to downgrade this working copy." or \
01400                        line == "See http://subversion.tigris.org/faq.html#working-copy-format-change" or \
01401                        line == "for details.' # Data repository version" :
01402                     line = "cruft001             = 330204                                   # cruft from subversion"
01403 
01404                 ###
01405                 ### workarounds for casapyinfo not returning
01406                 ### non-zero on error
01407                 ###
01408                 line = re.sub(" rcasapyinfo.*", "' # changed by report", line)
01409                 if re.compile("^ ").search(line):
01410                     line = fd.readline().rstrip()
01411                     lineno += 1
01412                     continue
01413                            
01414 
01415                 try:
01416                     k, v, c = re.compile(r"""
01417                     ^(\w+)              # key
01418                     \s*                 # whitespace
01419                     =                   # =
01420                     \s*                 # whitespace
01421                     ('.*'|[^#']+)       # value
01422                     \s*                 # whitespace
01423                     \#(.*)$             # comment
01424                     """, re.VERBOSE).search(line).groups()
01425                 except:
01426                     raise Exception("%s:%d: Cannot parse '%s'" % \
01427                                     (logfile, lineno, line))
01428                 k = k.strip()
01429                 v = v.strip(" '")
01430                 c = c.strip()
01431                 #print k, "=", v, "=", c
01432                 #data_file[k] = (v, c)  # include comments
01433                 data_file[k] = v
01434                 
01435                 # next line
01436                 line = fd.readline().rstrip() ; lineno += 1
01437 
01438             if not mono:
01439                 fd.close()
01440 
01441             # Test for mandatory entries' existence
01442             is_valid = True
01443             for key in ['host', 'testid', 'CASA', 'type', 'status']:
01444                 if not data_file.has_key(key):
01445                     is_valid = False
01446                     print >> sys.stderr, \
01447                           "Warning: %s: Missing key: %s" % (logfile, key)
01448 
01449             # Mandatory entries for passed tests
01450             for req in [{'simple': ['image_min', 'image_max', 'image_rms', \
01451                                     'ref_min'  , 'ref_max'  , 'ref_rms'] },
01452                         {'cube': ['image_x', 'image_y', 'image_fwhm', \
01453                                   'image_fit1_x', 'image_fit1_y', 'image_fit1_fwhm', \
01454                                   'ref_x', 'ref_y', 'ref_fwhm', \
01455                                   'ref_fit1_x', 'ref_fit1_y', 'ref_fit1_fwhm',] }]:
01456                 for type in req:
01457                     for key in req[type]:
01458                         if data_file.has_key('type') and \
01459                                data_file.has_key('status') and \
01460                                data_file['type'] == type and \
01461                                data_file['status'] == 'pass' and \
01462                                not data_file.has_key(key):
01463                             is_valid = False
01464                             print >> sys.stderr, \
01465                                   "Warning: %s: type=%s ; missing keys %s" % \
01466                                   (logfile, type, key)
01467 
01468             # Filter out test results where component
01469             # could not be found in reference image
01470             if is_valid and data_file['type'] in ['pol1','pol2','pol4']:
01471                 found_ref = False
01472                 for k in data_file.keys():
01473                     if len(k) > 4 and k[:4] == 'ref_':
01474                         found_ref = True
01475                         
01476                 if not found_ref:
01477                     is_valid = False
01478 
01479 
01480             if is_valid and data_file['host'] in exclude_host:
01481                 is_valid = False
01482 
01483             if is_valid and \
01484                    exclude_test.has_key(data_file['testid']) and \
01485                    data_file['CASA'] in exclude_test[data_file['testid']]:
01486                 #print "Excluding", data_file['testid'], "in version", data_file['CASA']
01487                 is_valid = False
01488 
01489             if is_valid:
01490                 if revision == 'all' or \
01491                        is_stable_branch(data_file['host']):
01492 
01493                     data.append(data_file)
01494                     
01495         # end for each logfile
01496         
01497         return data
01498 
01499     def dump_host(self, fd, host, extended):
01500         if self.platform.has_key(host):
01501             base, dist, rev, name = distribution(self.platform[host])
01502         else:
01503             base, dist, rev, name = ("???", "???", "?", "???")
01504         fd.write('<b>')
01505         if extended:
01506             fd.write(base + '<br>' + dist + ' ' + rev + '</b>')
01507             fd.write('<br>' + name + '<br>')
01508             if self.platform.has_key(host):
01509                 fd.write(architecture(self.platform[host]))
01510             else:
01511                 fd.write("???");
01512             fd.write('</b><br>' + host)
01513         else:
01514             fd.write(dist + '<br>' + rev + '</b><br>')
01515             if self.platform.has_key(host):
01516                 arch = architecture(self.platform[host])
01517                 fd.write(re.sub('.* ', '', arch).replace('-',''))
01518             else:
01519                 fd.write("???");
01520 
01521         
01522     def history_plot(self, png_filename_prefix, test, data):
01523         plotdata = {}      # the type is
01524                            # dictionary of (exec, time, mvirtual, mresident, filedesc,
01525                            #                [image]-[subtest])
01526                            #    dictionary of hosts:
01527                            #       list of
01528                            #          tuple (date, exectime)
01529         plotdata['exec'] = {}
01530         plotdata['time'] = {}
01531         plotdata['mresident'] = {}
01532         plotdata['mvirtual'] = {}
01533         plotdata['filedesc']  = {}
01534 
01535         label={}
01536         label['time'] = 'Execution time (s)'
01537         label['mresident'] = 'Peak resident memory (MB)'
01538         label['mvirtual'] = 'Peak virtual memory (MB)'
01539         label['filedesc'] = 'Max no. of open file descriptors'
01540 
01541         hosts = sets.Set()
01542         for log in data:
01543             if log['testid'] == test:
01544 
01545                 if log['type'] != 'exec':
01546                     t = log['image'].replace('/', '-')+'-'+log['type']
01547                     if not plotdata.has_key(t):
01548                         plotdata[t] = {}
01549                         for h in hosts:
01550                             plotdata[t][h] = []
01551                 else:
01552                     t = log['type']
01553 
01554                 if not hosts.__contains__(log['host']):
01555                     hosts.add(log['host'])
01556                     for key in plotdata.keys():
01557                         if not plotdata[key].has_key(log['host']):
01558                             plotdata[key][log['host']] = []
01559 
01560                 #print t
01561 
01562                 # Read the number after '(r' or after 'build #' or after 'Rev ' (older versions)
01563                 a = log['CASA'].find('(r')
01564                 if a >=0:
01565                     a += len('(r')
01566                     b = log['CASA'].find(')', a)
01567                     revision = log['CASA'][a:b]
01568                 else:
01569                   a = log['CASA'].find('build #')
01570                   if a >=0:
01571                       a += len('build #')
01572                       b = log['CASA'].find(')', a)
01573                       revision = log['CASA'][a:b]
01574                   else:
01575                     a = log['CASA'].find('Rev ')
01576                     if a >=0:
01577                         a += len('Rev ')
01578                         revision = log['CASA'][a:]
01579                     else:
01580                         raise Exception, "%s: Could not parse revision number '%s'" \
01581                               % (log['logfile'],log['CASA'])
01582                     
01583                 # regression status (execution and image tests)
01584                 plotdata[t][log['host']].append(
01585                     (log['date'],revision, [0,1][log['status']=='pass']) )
01586 
01587                 # track the following only for successful runs
01588                 if log['status'] == 'pass' and \
01589                        log['type'] == 'exec':
01590 
01591                     plotdata['time'][log['host']].append(
01592                         (log['date'],revision, log['time']) )
01593 
01594                     if log.has_key('resource'):
01595                         t, mvirtual, mresident, nfiledesc, \
01596                            dummy, dummy, dummy, dummy = \
01597                            self.parse_resources(log)
01598                         # discards CPU usage
01599                         if (len(mresident) > 0):
01600                             plotdata['mresident'][log['host']].append(
01601                                 (log['date'],revision, max(mresident)) )
01602                         if (len(mvirtual) > 0):
01603                             plotdata['mvirtual'][log['host']].append(
01604                                 (log['date'],revision, max(mvirtual)) )
01605                         if (len(nfiledesc) > 0):
01606                             plotdata['filedesc'][log['host']].append(
01607                                 (log['date'],revision, max(nfiledesc)) )
01608         plot_symbols = ['o',
01609                         '^',
01610                         'v',
01611                         '<',
01612                         '>',
01613                         's',
01614                         '+',  #unsupported: '*',
01615                         'x',
01616                         'D',
01617                         'd',
01618                         '1',
01619                         '2',
01620                         '3',
01621                         '4',
01622                         'h',
01623                         'H',
01624                         'p',
01625                         '|']
01626         for key in plotdata.keys():
01627 
01628             # plot both * as fct of date and * as fct of revision
01629             for time_data in [0, 1]:
01630                 pl.close()
01631                 pl.clf()
01632                 fig = pl.figure(figsize=(14,6))
01633 
01634                 ax = fig.add_subplot(111)
01635                 legend = []
01636                 total_runs = 0  # for this test on any host
01637                 hostno = 0
01638                 for host in hosts:
01639                   if host != 'ub8tst':  # has excessive exec.times which messes
01640                                         # up the scale of the plots!
01641                     #sort by dates
01642                     plotdata[key][host].sort(cmp=lambda a,b:cmp(a[0], b[0]))
01643                     x = []
01644                     y = []
01645                     for date,rev,t in plotdata[key][host]:
01646                         if time_data == 0:
01647                             x.append(rev)
01648                         else:
01649                             x.append(datetime.datetime.strptime(date, '%Y_%m_%d_%H_%M'))
01650                         y.append(t)
01651                     if time_data == 0:
01652                         ax.plot(x, y, plot_symbols[hostno % len(plot_symbols)]+'-.')
01653                     else:
01654                         x = pl.date2num(x)
01655                         ax.plot_date(x, y, plot_symbols[hostno % len(plot_symbols)]+'-.',
01656                                      xdate=True, ydate=False)
01657                         
01658                     total_runs += len(x)
01659                     legend.append(host)
01660                     hostno += 1
01661 
01662                 if time_data == 0:
01663                     pass
01664                 else:
01665                     fig.autofmt_xdate()
01666                     ax.xaxis.set_major_formatter(pl.DateFormatter("%d-%b-%Y"))
01667 
01668                 if label.has_key(key):
01669                     # time or memory or filedesc plot
01670                     pl.ylabel(label[key])
01671                     if time_data == 0:
01672                         pl.title(test + '\n(' + str(total_runs) + [' successful', ''][key=='exec']+ ' runs)')
01673                 else:
01674                     # pass/fail tests
01675                     if time_data == 0:
01676                         pl.title(test + ' ' + key + '\n(' + str(total_runs) + [' successful', ''][key=='exec']+ ' runs)')
01677 
01678                 v = ax.axis()
01679 
01680                 if not label.has_key(key):
01681                     ax.set_yticklabels([])
01682                     pl.text(v[0], 1, 'pass -', fontsize=20, \
01683                             horizontalalignment='right', \
01684                             verticalalignment='center')
01685                     pl.text(v[0], 0, 'fail -', fontsize=20, \
01686                             horizontalalignment='right', \
01687                             verticalalignment='center', name='sans-serif')
01688                     ax.axis([v[0], v[1], -0.5, 1.5])
01689                 else:
01690                     ax.axis([v[0], v[1], 0, v[3]])
01691 
01692                 ax.legend(legend, loc='center left', shadow=True)
01693                 if time_data == 0:
01694                     pl.xlabel('SVN revision')
01695                 else:
01696                     pl.xlabel('Date')
01697                 
01698                 fn = png_filename_prefix+'-'+key+'-'+str(time_data)+'.png'
01699                 print "Saving %s..." % fn,
01700                 sys.stdout.flush()
01701                 fig.savefig(fn)
01702                 print "done"
01703                 sys.stdout.flush()
01704 
01705     def create_profile_html(self, t, y11, y22, numfile, \
01706                             cpu_us, cpu_sy, cpu_id, cpu_wa, \
01707                             report_dir, \
01708                             png_filename, html_filename, \
01709                             png_cpu_filename, \
01710                             testname, host):
01711         # CPU profile plot
01712         pl.clf()
01713         if len(t) == len(cpu_us):  # memory and/or CPU data may not be available
01714             pl.plot(t,cpu_us,lw=1)
01715             pl.plot(t,cpu_id,lw=1)
01716             pl.plot(t,cpu_wa,lw=1)
01717             pl.plot(t,cpu_sy,lw=1)
01718 
01719         if len(t) > 0:
01720             pl.axis([1.1*min(t)-0.1*max(t), 1.1*max(t)-0.1*min(t), -5, 105])
01721         else:
01722             pl.axis([0, 1, -5, 105])
01723             
01724         pl.xlabel('time (sec)')
01725         pl.ylabel('CPU usage (percent)')
01726         font=FontProperties(size='small')
01727         pl.legend(('user', 'idle', 'iowait', 'system'),
01728                   loc=[0.7,0.85], prop=font)
01729 
01730         pl.title('Machine CPU usage for '+testname+' on '+host)
01731         pl.savefig(report_dir + '/' + png_cpu_filename);
01732 
01733 
01734 
01735         # Memory profile plot
01736         pl.clf()
01737         pl.plot(t,y11,lw=2)
01738         pl.plot(t,y22,lw=2)
01739         if len(y11) > 0 and len(y22) > 0:
01740             if max(y11)>=max(y22):
01741                 pl.axis([0.9*min(t),1.1*max(t),0,1.1*max(y11)])
01742             else:
01743                 pl.axis([0.9*min(t),1.1*max(t),0,1.1*max(y22)])
01744         else:
01745             pl.axis([0, 1, 0, 1])
01746         pl.xlabel('time (sec)')
01747         pl.ylabel('memory footprint') #note virtual vs. resident
01748         font=FontProperties(size='small')
01749         pl.legend(('virtual','resident'),loc=[0.7,0.85], prop=font)
01750         ax2 = pl.twinx()
01751         pl.ylabel('No of open File Descriptors')
01752         ax2.yaxis.tick_right()
01753         pl.plot(t,numfile, 'r-.',lw=2)
01754         pl.legend(['No. of Open FDs'],loc=[0.7,0.8], prop=font)
01755         pl.title('memory usage of casapy for '+testname+' on '+host)
01756 
01757         pl.savefig(report_dir + '/' + png_filename);
01758 
01759 
01760         # Generate HTML
01761         ht=htmlPub(report_dir + '/' + html_filename, 'Profile of '+testname)
01762 
01763         body1=['<pre>CPU profile of run of %s at %s </pre>'%(testname,time.strftime('%Y/%m/%d/%H:%M:%S'))]
01764         body2=['']
01765         ht.doBlk(body1, body2, png_cpu_filename, 'CPU/Memory profiles')
01766 
01767         body1=['<pre>Memory profile of run of %s at %s </pre>'%(testname,time.strftime('%Y/%m/%d/%H:%M:%S'))]
01768         body2=['']
01769         ht.doBlk(body1, body2, png_filename, ' ')
01770         
01771         ht.doFooter()