import repository from arizona
[raven.git] / tools / phonehome / report.py
1 import os, sys, time, getopt
2
3 #names
4 glo_want_good_columns = ["hostname", "status", "tinstall", "telapsed",
5                          "tinstallelap", "tbenchelap", "resptime",
6                          "1minload", "5minload",
7                          "busycpu", "memact", "swapused", "txrate", "rxrate",
8                          "swapin", "swapout", "diskin", "diskout"]
9
10 # graphs of comon variables that would be meaningless (text strings, constants,
11 # etc)
12 glo_comon_ignore_graph = ["name", "address", "location", "nodetype",
13                           "bootstate", "keyok", "kernver", "fcname",
14                           "date", "drift"]
15
16 # glo_good_results is a list of good results from phonehome. It contains one
17 # line per result, in the order that they are received.
18 # glo_colnames contains the names of the columns in glo_good results
19 # this is usually: (name, slice, hostname, tboot, tinstall, telapsed,
20 #                   retry, tlastinstall, treport, status, tinstallelap, tbenchelap)
21 glo_colnames = []
22 glo_good_results = []
23 glo_good_nodes = []
24
25 # glo_bad_nodes is a list of nodes that did not submit a phonehome result
26 glo_bad_nodes = []
27
28 # glo_comon_results contains one dictionary entry for each line in the comon
29 # database. The dictionary entry's value is the list of comon fields. The fields
30 # are named by the list glo_comon_colnames
31 glo_comon_results = {}
32 glo_comon_colnames = []
33
34 glo_no_initscript_start_data = False
35
36 def reset():
37    # reset the stats so we can read() again
38    global glo_good_results
39    global glo_colnames
40    global glo_good_nodes
41    global glo_bad_nodes
42
43    glo_good_results = []
44    glo_colnames = []
45    glo_good_nodes = []
46    glo_bad_nodes = []
47
48 def list_find(list, what):
49    for i,x in enumerate(list):
50        if x == what:
51            return i
52    return -1
53
54 def comon_link(hostname):
55    """ Generate a link for hostname to its comon detail page """
56    return "<a href=http://summer.cs.princeton.edu/status/tabulator.cgi?table=nodes/table_" + hostname + "&limit=50>" + hostname + "</a>"
57
58 def read_comon(name, slicename, timestamp = None):
59    """ Read the comon database. """
60    global glo_comon_results
61    global glo_comon_colnames
62
63    comon_name = "/repository/phonehome/comon_" + slicename + ".txt"
64
65    if timestamp:
66        comon_name = comon_name + "^" + timestamp
67
68    try:
69       comon_file = open(comon_name, "r")
70    except:
71       print "WARNING: failed to open comon file " + comon_name + "<br>"
72       return
73
74    glo_comon_colnames = comon_file.readline().rstrip("\n").split(",")
75    glo_comon_colnames = [r.strip() for r in glo_comon_colnames]
76
77    comon_lines = comon_file.readlines()
78    for line in comon_lines:
79       results = line.rstrip("\n").split(",")
80       results = [r.strip() for r in results]
81       name = results[0]
82
83       glo_comon_results[name] = results
84
85 def safeint(x):
86    try:
87        return int(x)
88    except:
89        return 0
90
91 def read(name, slicename, timestamp = None):
92    global glo_good_nodes
93    global glo_bad_nodes
94    global glo_good_results
95    global glo_colnames
96    global glo_no_initscript_start_data
97
98    nodelist_name = "/repository/phonehome/nodelist_" + slicename + ".txt"
99    phonehome_name = "/repository/phonehome/" + name + "_" + slicename + ".txt"
100
101    if timestamp:
102        nodelist_name = nodelist_name + "^" + timestamp
103        phonehome_name = phonehome_name + "^" + timestamp
104
105    try:
106       nodelist_file = open(nodelist_name, "r")
107       nodelist_lines = nodelist_file.readlines()
108       nodelist_file.close()
109    except:
110       print "WARNING: failed to open nodelist file " + nodelist_name + "<br>"
111       nodelist_lines = []
112
113    try:
114       phonehome_file = open(phonehome_name, "r")
115    except:
116       print "ERROR: failed to open phonehome file " + phonehome_name + "<br>"
117       print "(this may mean no results were logged today)<br>"
118       sys.exit(0)
119
120    phonehome_lines = phonehome_file.readlines()
121    phonehome_file.close()
122
123    for line in phonehome_lines:
124       line = line.rstrip("\n")
125       result = line.split("\t")
126       if len(result) > 5:
127          # if any columns are missing, pad them out
128          while len(result) < 13:
129              result.append(-1)
130
131          # get rid of any '' as they cause int() to fail
132          newresult=[]
133          for r in result:
134              if not r:
135                  r = "-1"
136              newresult.append(r)
137          result = newresult
138
139          tBoot = int(result[3])
140          tInstall = int(result[4])
141          tLastInstall = int(result[7])
142          tReport = int(result[8])
143          tInitScriptStart = int(result[10])
144          tInitScriptFinish = int(result[12])
145
146          # compute the elapsed time since last-install
147          if int(tLastInstall) <= 0:
148              result.append("-1")
149          else:
150              result.append(tInstall - tLastInstall)
151
152          # compute the elapsed time to run the benchmark
153          if tReport <= 0:
154              result.append("-1")
155          else:
156              result.append(tReport - tInstall)
157
158          # compute the elapsed time from initscript start to initscript finish
159          if (tInitScriptStart <= 0) or (tInitScriptFinish <= 0):
160             result.append("-1")
161          else:
162             result.append(tInitScriptFinish - tInitScriptStart)
163
164          # compute the elapsed time from initscript start to install of package
165          if (tInitScriptStart <= 0):
166             # for the older logs where we didn't record initscript start, let's
167             # use the time since boot.
168             glo_no_initscript_start_data = True
169             result.append("-1")
170             if (tInstall - tBoot) < 30000:
171                result.append(tInstall - tBoot) # telapsed_combined
172             else:
173                result.append(-1)
174          else:
175             result.append(tInstall - tInitScriptStart)
176             result.append(tInstall - tInitScriptStart) # telapsed_combined
177
178          if safeint(result[5]) > (60*60*24):
179              continue
180              # it's over 1 day old, so throw it out
181
182          glo_good_results.append(result)
183          glo_good_nodes.append(result[2])
184
185    glo_colnames = ["testname", "slicename", "hostname", "tboot", "tinstall", "telapsed", "retry",
186                    "tlastinstall", "treport", "status",
187                    "tinitscriptstart", "tinitscriptprerunstork", "tinitscriptfinish",
188                    "tinstallelap", "tbenchelap", "tinitscriptelap", "telapsedfrominitstart",
189                    "telapsed_combined"]
190
191    for line in nodelist_lines:
192       line = line.rstrip("\n")
193       if not (line in glo_good_nodes):
194          glo_bad_nodes.append(line)
195
196 def get_data(hostname, result, colname):
197    """ given a result entry and a column name, return the data value for that
198        column.
199    """
200    data = None
201
202    if not hostname:
203        hostname = result[2]
204
205    # see if it's one of the phonehome statistics
206    index = list_find(glo_colnames,colname)
207    if index >= 0:
208       if not result:
209          return "-nf-"
210       if index >= len(result):
211          return "-id-"
212       data = result[index]
213
214    # see if it's a comon column name
215    if not data:
216       comon_index = list_find(glo_comon_colnames, colname)
217       if comon_index >= 0:
218          line = glo_comon_results.get(hostname, None)
219          if not line:
220             return "-nf-"
221          if comon_index >= len(line):
222             return "-id-"
223          data = line[comon_index]
224
225    if not data:
226        data = "-nd-"
227
228    return data
229
230 def get_data_int(hostname, result, colname):
231    data = get_data(hostname, result, colname)
232    try:
233        data = int(data)
234    except:
235        data = 0
236    return data
237
238 def get_data_float(hostname, result, colname):
239    data = get_data(hostname, result, colname)
240    try:
241        data = float(data)
242    except:
243        data = 0
244    return data
245
246 def print_stat(value):
247    print "<td align=right>" + str(value) + " sec</td><td align=right>" + str(value/60) + " (min)</td>",
248
249 def compute_stats(colname):
250    global glo_good_results
251    global glo_colnames
252
253    if colname == "goodcount":
254       return (len(glo_good_results), len(glo_good_results), len(glo_good_results), len(glo_good_results))
255
256    colnum = list_find(glo_colnames, colname)
257    if colnum < 0:
258        print "unknown colname " + colname
259        return None
260
261    resptimes = []
262    sum = 0
263    count = 0
264    min_resptime = -1
265    max_resptime = -1
266
267    for result in glo_good_results:
268        resptime = int(result[colnum])
269        resptimes.append(resptime)
270        count = count + 1
271        sum = sum + resptime
272        if resptime < min_resptime or min_resptime < 0:
273            min_resptime = resptime
274        if resptime > max_resptime:
275            max_resptime = resptime
276
277    resptimes.sort()
278
279    if count == 0:
280       mean_resptime = 0
281       median_resptime = 0
282    else:
283       mean_resptime = int(sum / count)
284       median_resptime = resptimes[int(len(resptimes)/2)]
285
286    return (mean_resptime, median_resptime, min_resptime, max_resptime)
287
288 def print_stats_row(colname):
289    stats = compute_stats(colname)
290    if not stats:
291       return
292
293    (mean_resptime, median_resptime, min_resptime, max_resptime) = stats
294
295    print "<tr>",
296
297    print "<td>"+colname+"</td>",
298
299    print_stat(mean_resptime)
300    print_stat(median_resptime)
301    print_stat(min_resptime)
302    print_stat(max_resptime)
303
304    print "</tr>"
305
306 def print_first_last_times(colname):
307    global glo_good_results
308    global glo_colnames
309
310    colnum = list_find(glo_colnames, colname)
311    if colnum < 0:
312        print "unknown colname " + colname
313        return
314
315    tFirst = None
316    tLast = None
317    for result in glo_good_results:
318       t = int(result[4])
319       if (not tFirst) or (t < tFirst):
320           tFirst = t
321       if (not tLast) or (t > tLast):
322           tLast = t
323
324    print "<br>" + colname + " first=" + str(tFirst),
325    if tFirst:
326        print time.ctime(tFirst)
327
328    print ", last =" + str(tLast),
329    if tLast:
330        print time.ctime(tLast)
331
332 def report_stats():
333    global glo_good_results
334
335    print "<h2>Statistics:</h2>"
336
337    print_first_last_times("tboot")
338    print_first_last_times("tinstall")
339
340    print "<br>Reporting node count = " + str(len(glo_good_results))
341
342    print "<br><table border=1>"
343
344    print "<tr><td>field</td><td colspan=2 align=center>mean</td><td colspan=2 align=center>median</td><td colspan=2 align=center>min</td><td colspan=2 align=center>max</td></tr>"
345
346    print_stats_row("telapsed")
347    print_stats_row("telapsedfrominitstart")
348    print_stats_row("tinstallelap")
349    print_stats_row("tbenchelap")
350    print_stats_row("tinitscriptelap")
351
352    print "</table>"
353
354 #   print "<tr><td>Success count</td><td align=\"right\">" + str(count) + "</td><td></td></tr>"
355 #   print "<tr><td>Mean install time</td><td align=\"right\">" + str(mean_resptime) + \
356 #         " sec</td><td align=\"right\">(" + str(int(mean_resptime/60)) + " min)</td></tr>"
357 #   print "<tr><td>Median install time</td><td align=\"right\">" + str(median_resptime) + \
358 #         " sec</td><td align=\"right\">(" + str(int(median_resptime/60)) + " min)</td></tr>"
359 #   print "<tr><td>Min install time</td><td align=\"right\">" + str(min_resptime) + \
360 #         " sec</td><td align=\"right\">(" + str(int(min_resptime/60)) + " min)</td></tr>"
361 #   print "<tr><td>Max install time</td><td align=\"right\">" + str(max_resptime) + \
362 #         " sec</td><td align=\"right\">(" + str(int(max_resptime/60)) + " min)</td></tr>"
363 #   print "</table>"
364
365 def write_histo(colname):
366    global glo_good_results
367    global glo_good_colnames
368
369    colnum = list_find(glo_colnames, colname)
370    if colnum<0:
371        print "unknown column " + colname
372        return
373
374    max = 0
375    buckets = {}
376    sumbuckets = {}
377    for result in glo_good_results:
378       telapsed = int(int(result[colnum]) / 60)
379       if telapsed > max:
380          max = telapsed
381       buckets[telapsed] = buckets.get(telapsed, 0) + 1
382
383    if max > 1000000:
384        print "max is too big " + str(max) + " for colname " + colname
385        return
386
387    try:
388        f = open("/tmp/histogram.dat", "w")
389        for i in range(0, max+1):
390           value = buckets.get(i, 0)
391           if i==0:
392               sumbuckets[i] = value
393           else:
394               sumbuckets[i] = sumbuckets.get(i-1, 0) + value
395           if value > 0:
396               f.write(str(i) + " " + str(sumbuckets[i]) + "\n")
397        f.close()
398    except:
399        print "failed to write histogram data file"
400
401 glo_compare_colnum = 0
402 def result_compare(x, y):
403     global glo_compare_colnum
404     try:
405         return int(x[glo_compare_colnum]) - int(y[glo_compare_colnum])
406     except:
407         return 0
408
409 def write_histo2(colname):
410    global glo_good_results
411    global glo_good_colnames
412    global glo_compare_colnum
413
414    colnum = list_find(glo_colnames, colname)
415    if colnum<0:
416        print "unknown column " + colname
417        return
418
419    glo_compare_colnum = colnum;
420    sorted_results = glo_good_results[:]
421    sorted_results.sort(result_compare)
422
423    try:
424        f = open("/tmp/histogram.dat", "w")
425        count = 0
426        for result in sorted_results:
427            count = count + 1
428            f.write(str(float(float(result[colnum])/60)) + " " + str(count) + "\n")
429        f.close()
430    except:
431        print "failed to write histogram data file"
432
433 def report_histo(colname, pngname, title):
434    write_histo2(colname)
435
436    f = open("/tmp/histogram.gnuplot", "w")
437    f.write('set output "' + pngname + '"\n')
438    f.write('set term png\n')
439    f.write('set xlabel "' + colname + ' (minutes)"\n')
440    f.write('set ylabel "nodes"\n')
441    f.write('plot "/tmp/histogram.dat" with linespoints\n')
442    f.close()
443
444    os.system("gnuplot < /tmp/histogram.gnuplot")
445
446    print '<h2>' + title + '</h2>'
447    print '<br><img src="' + os.path.basename(pngname) + '"><br>'
448
449 def write_scatter(col1, col2):
450    global glo_good_results
451    global glo_good_colnames
452
453    try:
454        f = open("/tmp/scatter.dat", "w")
455        for result in glo_good_results:
456            dat1 = get_data_float(None, result, col1)
457            dat2 = get_data_float(None, result, col2)
458
459            f.write(str(dat1) + " " + str(dat2) + "\n")
460        f.close()
461    except:
462        print "failed to write scatter data file"
463
464 def report_scatter(col1, col2, pngname, title):
465    write_scatter(col1, col2)
466
467    f = open("/tmp/scatter.gnuplot", "w")
468    f.write('set output "' + pngname + '"\n')
469    f.write('set term png\n')
470    f.write('set xlabel "' + col1 + ' (seconds)"\n')
471    f.write('set ylabel "' + col2 + '"\n')
472    f.write('plot "/tmp/scatter.dat"\n')
473    f.close()
474
475    os.system("gnuplot < /tmp/scatter.gnuplot")
476
477    print '<h2>' + title + '</h2>'
478    print '<br><img src="' + os.path.basename(pngname) + '"><br>'
479
480 def print_column(hostname, result, colname):
481    global glo_colnames, glo_comon_results, glo_comon_colnames
482
483    data = None
484
485    if colname == "namehost":
486       print comon_link(hostname),
487       return
488
489    if colname.startswith("ctime_"):
490       data = get_data(hostname, result, colname[6:])
491       data = time.ctime(float(data))
492    else:
493       data = get_data(hostname, result, colname)
494
495    print data,
496
497
498 def print_results(hostname, result, columns):
499    print "<tr>",
500    for colname in columns:
501        print "<td>",
502        print_column(hostname, result, colname)
503        print "</td>",
504    print "</tr>"
505
506
507 def report_good():
508    global glo_good_results
509
510    print "<h2>Reporting node results:</h2>"
511
512    print "<table>"
513
514    # a header row, to name our columns
515    print "<tr>",
516    for colname in glo_want_good_columns:
517        print "<td>" + colname + "</td>",
518    print "</tr>"
519
520    for result in glo_good_results:
521        print_results(result[2], result, glo_want_good_columns)
522
523    print "</table>"
524
525 def report_bad():
526    if glo_bad_nodes:
527       print "<h2>Non-reporting nodes:</h2>"
528
529       print "<table>"
530       for result in glo_bad_nodes:
531           print "<tr><td>" + comon_link(result) + "</td></tr>"
532       print "</table>"
533
534 def main():
535    global glo_comon_colnames
536    global glo_comon_ignore_graph
537    global glo_no_initscript_start_data
538    global glo_want_good_columns
539
540    name = "smbphonehome_tar"
541    slicename = "arizona_stork_install"
542    timestamp = None
543    report_timings = True
544
545    # option processing
546    (options, args) = getopt.getopt(sys.argv[1:], '', ["notimings"])
547    for opt in options:
548        optname = opt[0]
549
550        if optname == "--notimings":
551            report_timings = False
552
553    if len(args)>=1:
554        slicename = args[0]
555
556    if len(args)>=2:
557        name = args[1]
558
559    if len(args)>=3:
560        timestamp = args[2]
561
562    # some of the names are a  little misleading, so fix them up
563    if name == "smbphonehome_tar":
564        title = "install"
565    elif name == "cpubench":
566        title = "sieve"
567    else:
568        title = name
569
570    print "<h1>" + str(title) + " report for " + slicename + "</h1>"
571
572    # a suffix for use in the name of the png file
573    if timestamp:
574       timestamp_suffix = "_" + timestamp
575    else:
576       timestamp_suffix = ""
577
578    if report_timings:
579       print "<ul><li>telapsed = time from boot to start of .postinstall script</li>"
580       print "<li>telapinstall = time from previous package install to start of .postinstall script for this package</li>"
581       print "<li>telapbench = time to run the .postinstall script (including execution of any benchmarks)</li>"
582       print "</ul>"
583    else:
584       glo_want_good_columns = ["slicename", "hostname", "status"]
585
586    read(name, slicename, timestamp)
587    read_comon(name, slicename, timestamp)
588    report_stats()
589
590    if report_timings:
591       report_histo("telapsed", "/repository/phonehome/" + name + "_" + slicename + timestamp_suffix + ".png",
592                    "Install time since boot:")
593       report_histo("telapsedfrominitstart", "/repository/phonehome/" + name + "_" + slicename + timestamp_suffix + "_elapstartinit.png",
594                    "Install time since start of initscript:")
595       report_histo("tinstallelap", "/repository/phonehome/" + name + "_" + slicename + timestamp_suffix + "_installelap.png",
596                    "Install time since last package")
597       report_histo("tbenchelap", "/repository/phonehome/" + name + "_" + slicename + timestamp_suffix + "_benchelap.png",
598                    "Benchmark run time")
599
600       if name == "smbphonehome_tar":
601           for colname in glo_comon_colnames:
602              if colname in glo_comon_ignore_graph:
603                  continue
604
605              if glo_no_initscript_start_data:
606                  phonehomeColName = "telapsed"
607                  startName = "boot"
608              else:
609                  phonehomeColName = "telapsedfrominitstart"
610                  startName = "initscript start"
611
612              report_scatter(phonehomeColName, colname,
613                             "/repository/phonehome/" + name + "_" + slicename + timestamp_suffix + "_elap_" + colname + ".png",
614                             "Install time since " + startName+ " vs " + colname + " [comon]")
615       else:
616           for colname in glo_comon_colnames:
617              if colname in glo_comon_ignore_graph:
618                  continue
619              report_scatter("tbenchelap", colname,
620                             "/repository/phonehome/" + name + "_" + slicename + timestamp_suffix + "_benchelap_" + colname + ".png",
621                             "Benchmark time vs " + colname + " [comon]")
622
623    report_good()
624    report_bad()
625
626 if __name__ == "__main__":
627    main()