Home | History | Annotate | Download | only in monitor
      1 # Copyright 2011 Google Inc. All Rights Reserved.
      2 #
      3 
      4 __author__ = 'kbaclawski (at] google.com (Krystian Baclawski)'
      5 
      6 from collections import namedtuple
      7 import glob
      8 import gzip
      9 import os.path
     10 import pickle
     11 import time
     12 import xmlrpclib
     13 
     14 from django import forms
     15 from django.http import HttpResponseRedirect
     16 from django.shortcuts import render_to_response
     17 from django.template import Context
     18 from django.views import static
     19 
     20 Link = namedtuple('Link', 'href name')
     21 
     22 
     23 def GetServerConnection():
     24   return xmlrpclib.Server('http://localhost:8000')
     25 
     26 
     27 def MakeDefaultContext(*args):
     28   context = Context({'links': [
     29       Link('/job-group', 'Job Groups'), Link('/machine', 'Machines')
     30   ]})
     31 
     32   for arg in args:
     33     context.update(arg)
     34 
     35   return context
     36 
     37 
     38 class JobInfo(object):
     39 
     40   def __init__(self, job_id):
     41     self._job = pickle.loads(GetServerConnection().GetJob(job_id))
     42 
     43   def GetAttributes(self):
     44     job = self._job
     45 
     46     group = [Link('/job-group/%d' % job.group.id, job.group.label)]
     47 
     48     predecessors = [Link('/job/%d' % pred.id, pred.label)
     49                     for pred in job.predecessors]
     50 
     51     successors = [Link('/job/%d' % succ.id, succ.label)
     52                   for succ in job.successors]
     53 
     54     machines = [Link('/machine/%s' % mach.hostname, mach.hostname)
     55                 for mach in job.machines]
     56 
     57     logs = [Link('/job/%d/log' % job.id, 'Log')]
     58 
     59     commands = enumerate(job.PrettyFormatCommand().split('\n'), start=1)
     60 
     61     return {'text': [('Label', job.label), ('Directory', job.work_dir)],
     62             'link': [('Group', group), ('Predecessors', predecessors),
     63                      ('Successors', successors), ('Machines', machines),
     64                      ('Logs', logs)],
     65             'code': [('Command', commands)]}
     66 
     67   def GetTimeline(self):
     68     return [{'started': evlog.GetTimeStartedFormatted(),
     69              'state_from': evlog.event.from_,
     70              'state_to': evlog.event.to_,
     71              'elapsed': evlog.GetTimeElapsedRounded()}
     72             for evlog in self._job.timeline.GetTransitionEventHistory()]
     73 
     74   def GetLog(self):
     75     log_path = os.path.join(self._job.logs_dir,
     76                             '%s.gz' % self._job.log_filename_prefix)
     77 
     78     try:
     79       log = gzip.open(log_path, 'r')
     80     except IOError:
     81       content = []
     82     else:
     83       # There's a good chance that file is not closed yet, so EOF handling
     84       # function and CRC calculation will fail, thus we need to monkey patch the
     85       # _read_eof method.
     86       log._read_eof = lambda: None
     87 
     88       def SplitLine(line):
     89         prefix, msg = line.split(': ', 1)
     90         datetime, stream = prefix.rsplit(' ', 1)
     91 
     92         return datetime, stream, msg
     93 
     94       content = map(SplitLine, log.readlines())
     95     finally:
     96       log.close()
     97 
     98     return content
     99 
    100 
    101 class JobGroupInfo(object):
    102 
    103   def __init__(self, job_group_id):
    104     self._job_group = pickle.loads(GetServerConnection().GetJobGroup(
    105         job_group_id))
    106 
    107   def GetAttributes(self):
    108     group = self._job_group
    109 
    110     home_dir = [Link('/job-group/%d/files/' % group.id, group.home_dir)]
    111 
    112     return {'text': [('Label', group.label),
    113                      ('Time submitted', time.ctime(group.time_submitted)),
    114                      ('State', group.status),
    115                      ('Cleanup on completion', group.cleanup_on_completion),
    116                      ('Cleanup on failure', group.cleanup_on_failure)],
    117             'link': [('Directory', home_dir)]}
    118 
    119   def _GetJobStatus(self, job):
    120     status_map = {'SUCCEEDED': 'success', 'FAILED': 'failure'}
    121     return status_map.get(str(job.status), None)
    122 
    123   def GetJobList(self):
    124     return [{'id': job.id,
    125              'label': job.label,
    126              'state': job.status,
    127              'status': self._GetJobStatus(job),
    128              'elapsed': job.timeline.GetTotalTime()}
    129             for job in self._job_group.jobs]
    130 
    131   def GetHomeDirectory(self):
    132     return self._job_group.home_dir
    133 
    134   def GetReportList(self):
    135     job_dir_pattern = os.path.join(self._job_group.home_dir, 'job-*')
    136 
    137     filenames = []
    138 
    139     for job_dir in glob.glob(job_dir_pattern):
    140       filename = os.path.join(job_dir, 'report.html')
    141 
    142       if os.access(filename, os.F_OK):
    143         filenames.append(filename)
    144 
    145     reports = []
    146 
    147     for filename in sorted(filenames, key=lambda f: os.stat(f).st_ctime):
    148       try:
    149         with open(filename, 'r') as report:
    150           reports.append(report.read())
    151       except IOError:
    152         pass
    153 
    154     return reports
    155 
    156 
    157 class JobGroupListInfo(object):
    158 
    159   def __init__(self):
    160     self._all_job_groups = pickle.loads(GetServerConnection().GetAllJobGroups())
    161 
    162   def _GetJobGroupState(self, group):
    163     return str(group.status)
    164 
    165   def _GetJobGroupStatus(self, group):
    166     status_map = {'SUCCEEDED': 'success', 'FAILED': 'failure'}
    167     return status_map.get(self._GetJobGroupState(group), None)
    168 
    169   def GetList(self):
    170     return [{'id': group.id,
    171              'label': group.label,
    172              'submitted': time.ctime(group.time_submitted),
    173              'state': self._GetJobGroupState(group),
    174              'status': self._GetJobGroupStatus(group)}
    175             for group in self._all_job_groups]
    176 
    177   def GetLabelList(self):
    178     return sorted(set(group.label for group in self._all_job_groups))
    179 
    180 
    181 def JobPageHandler(request, job_id):
    182   job = JobInfo(int(job_id))
    183 
    184   ctx = MakeDefaultContext({
    185       'job_id': job_id,
    186       'attributes': job.GetAttributes(),
    187       'timeline': job.GetTimeline()
    188   })
    189 
    190   return render_to_response('job.html', ctx)
    191 
    192 
    193 def LogPageHandler(request, job_id):
    194   job = JobInfo(int(job_id))
    195 
    196   ctx = MakeDefaultContext({'job_id': job_id, 'log_lines': job.GetLog()})
    197 
    198   return render_to_response('job_log.html', ctx)
    199 
    200 
    201 def JobGroupPageHandler(request, job_group_id):
    202   group = JobGroupInfo(int(job_group_id))
    203 
    204   ctx = MakeDefaultContext({
    205       'group_id': job_group_id,
    206       'attributes': group.GetAttributes(),
    207       'job_list': group.GetJobList(),
    208       'reports': group.GetReportList()
    209   })
    210 
    211   return render_to_response('job_group.html', ctx)
    212 
    213 
    214 def JobGroupFilesPageHandler(request, job_group_id, path):
    215   group = JobGroupInfo(int(job_group_id))
    216 
    217   return static.serve(request,
    218                       path,
    219                       document_root=group.GetHomeDirectory(),
    220                       show_indexes=True)
    221 
    222 
    223 class FilterJobGroupsForm(forms.Form):
    224   label = forms.ChoiceField(label='Filter by label:', required=False)
    225 
    226 
    227 def JobGroupListPageHandler(request):
    228   groups = JobGroupListInfo()
    229   group_list = groups.GetList()
    230 
    231   field = FilterJobGroupsForm.base_fields['label']
    232   field.choices = [('*', '--- no filtering ---')]
    233   field.choices.extend([(label, label) for label in groups.GetLabelList()])
    234 
    235   if request.method == 'POST':
    236     form = FilterJobGroupsForm(request.POST)
    237 
    238     if form.is_valid():
    239       label = form.cleaned_data['label']
    240 
    241       if label != '*':
    242         group_list = [group for group in group_list if group['label'] == label]
    243   else:
    244     form = FilterJobGroupsForm({'initial': '*'})
    245 
    246   ctx = MakeDefaultContext({'filter': form, 'groups': group_list})
    247 
    248   return render_to_response('job_group_list.html', ctx)
    249 
    250 
    251 def MachineListPageHandler(request):
    252   machine_list = pickle.loads(GetServerConnection().GetMachineList())
    253 
    254   return render_to_response('machine_list.html',
    255                             MakeDefaultContext({'machines': machine_list}))
    256 
    257 
    258 def DefaultPageHandler(request):
    259   return HttpResponseRedirect('/job-group')
    260