casa
$Rev:20696$
|
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("<", "<").replace(">", ">")) 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()