Beispiel #1
0
    def renderWLCGViewPage(self, wlcg_data, request, start_date, end_date, t_query_start):

        t_query = time.time() - t_query_start

        days = dateform.dayDelta(start_date, end_date)

        # massage data
        #print "L1", len(wlcg_data)
        t_dataprocess_start = time.time()
        wlcg_records = dataprocess.rowsToDicts(wlcg_data)
        wlcg_records = dataprocess.addMissingScaleValues(wlcg_records)
        wlcg_records = dataprocess.collapseFields(wlcg_records, self.collapse)
        if self.tier_based:
            wlcg_records = dataprocess.tierMergeSplit(wlcg_records, self.tier_mapping, self.tier_shares, self.default_tier)
            if self.split != dataprocess.TIER:
                wlcg_records = dataprocess.collapseFields(wlcg_records, ( dataprocess.HOST, ) )
        # information on ops vo does not add any value
        wlcg_records = [ rec for rec in wlcg_records if rec[dataprocess.VO_NAME] != 'ops' ]
        wlcg_records = dataprocess.addEffiencyProperty(wlcg_records)
        wlcg_records = dataprocess.addEquivalentProperties(wlcg_records, days)

        sk = lambda key : dataprocess.sortKey(key, field_order=self.columns)
        if self.split is None:
            wlcg_records = sorted(wlcg_records, key=sk)
        else:
            split_records = dataprocess.splitRecords(wlcg_records, dataprocess.TIER)
            for split_attr, records in split_records.items():
                split_records[split_attr] = sorted(records, key=sk)

        #print "L2", len(wlcg_records)
        t_dataprocess = time.time() - t_dataprocess_start

        if self.split is None:
            table_content = self.createTable(wlcg_records, self.columns)
        else:
            table_content = ''
            for split_attr, records in split_records.items():
                table = self.createTable(records, self.columns)
                table_content += html.createParagraph(split_attr) + table + html.SECTION_BREAK

        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option   = request.args.get('enddate', [''])[0]

        title = 'WLCG %s view' % self.path
        selector_form = dateform.createMonthSelectorForm(self.path, start_date_option, end_date_option)
        range_text = html.createParagraph('Date range: %s - %s (%s days)' % (start_date, end_date, days))

        request.write( html.HTML_VIEWBASE_HEADER % {'title': title} )
        request.write( html.createTitle(title) )
        request.write( html.createParagraph(selector_form) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(range_text) )
        request.write( table_content )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph('Query time: %s' % round(t_query, 2)) )
        request.write( html.createParagraph('Data process time: %s' % round(t_dataprocess, 2)) )
        request.write( html.HTML_VIEWBASE_FOOTER )

        request.finish()
        return server.NOT_DONE_YET
Beispiel #2
0
    def renderWLCGViewPage(self, wlcg_data, request, start_date, end_date, t_query_start):

        t_query = time.time() - t_query_start

        days = dateform.dayDelta(start_date, end_date)

        # massage data
        #print "L1", len(wlcg_data)
        t_dataprocess_start = time.time()
        wlcg_records = dataprocess.rowsToDicts(wlcg_data)
        wlcg_records = dataprocess.addMissingScaleValues(wlcg_records)
        wlcg_records = dataprocess.collapseFields(wlcg_records, self.collapse)
        if self.tier_based:
            wlcg_records = dataprocess.tierMergeSplit(wlcg_records, self.tier_mapping, self.tier_shares, self.default_tier)
            if self.split != dataprocess.TIER:
                wlcg_records = dataprocess.collapseFields(wlcg_records, ( dataprocess.HOST, ) )
        # information on ops vo does not add any value
        wlcg_records = [ rec for rec in wlcg_records if rec[dataprocess.VO_NAME] != 'ops' ]
        wlcg_records = dataprocess.addEffiencyProperty(wlcg_records)
        wlcg_records = dataprocess.addEquivalentProperties(wlcg_records, days)

        sk = lambda key : dataprocess.sortKey(key, field_order=self.columns)
        if self.split is None:
            wlcg_records = sorted(wlcg_records, key=sk)
        else:
            split_records = dataprocess.splitRecords(wlcg_records, dataprocess.TIER)
            for split_attr, records in split_records.items():
                split_records[split_attr] = sorted(records, key=sk)

        #print "L2", len(wlcg_records)
        t_dataprocess = time.time() - t_dataprocess_start

        if self.split is None:
            table_content = self.createTable(wlcg_records, self.columns)
        else:
            table_content = ''
            for split_attr, records in split_records.items():
                table = self.createTable(records, self.columns)
                table_content += html.createParagraph(split_attr) + table + html.SECTION_BREAK

        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option   = request.args.get('enddate', [''])[0]

        title = 'WLCG %s view' % self.path
        selector_form = dateform.createMonthSelectorForm(self.path, start_date_option, end_date_option)
        range_text = html.createParagraph('Date range: %s - %s (%s days)' % (start_date, end_date, days))

        request.write( html.HTML_VIEWBASE_HEADER % {'title': title} )
        request.write( html.createTitle(title) )
        request.write( html.createParagraph(selector_form) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(range_text) )
        request.write( table_content )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph('Query time: %s' % round(t_query, 2)) )
        request.write( html.createParagraph('Data process time: %s' % round(t_dataprocess, 2)) )
        request.write( html.HTML_VIEWBASE_FOOTER )

        request.finish()
        return server.NOT_DONE_YET
Beispiel #3
0
    def renderWLCGViewPage(self, wlcg_data, request, start_date, end_date, t_query_start):

        t_query = time.time() - t_query_start

        days = dateform.dayDelta(start_date, end_date)

        t_dataprocess_start = time.time()

        wlcg_records = wlcg.rowsToDicts(wlcg_data, self.columns)
        wlcg_records = _changeUnits(wlcg_records)

        sk = lambda key : _sortKey(key, field_order=self.columns)
        if self.split is None:
            wlcg_records = self.sort(wlcg_records, key=sk)
        else:
            split_records = _splitRecords(wlcg_records, self.split)
            for split_attr, records in split_records.items():
                split_records[split_attr] = self.sort(records, key=sk)

        t_dataprocess = time.time() - t_dataprocess_start

        if self.split is None:
            table_content = self.createTable(wlcg_records, self.columns)
        else:
            table_content = ''
            columns = [ c for c in self.columns if c != self.split and c not in self.invisible_columns ]
            for split_attr, records in split_records.items():
                table = self.createTable(records, columns)
                table_content += html.createParagraph(split_attr) + table + html.SECTION_BREAK

        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option   = request.args.get('enddate', [''])[0]

        title = 'WLCG %s view' % self.path
        selector_form = dateform.createMonthSelectorForm(self.path, start_date_option, end_date_option)
        range_text = html.createParagraph('Date range: %s - %s (%s days)' % (start_date, end_date, days))

        request.write( html.HTML_VIEWBASE_HEADER % {'title': title} )
        request.write( html.createTitle(title) )
        request.write( html.createParagraph(selector_form) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(range_text) )
        request.write( table_content )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph('Query time: %s' % round(t_query, 2)) )
        request.write( html.createParagraph('Data process time: %s' % round(t_dataprocess, 2)) )
        request.write( html.HTML_VIEWBASE_FOOTER )

        request.finish()
        return server.NOT_DONE_YET
Beispiel #4
0
    def render_GET(self, request):

        subject = resourceutil.getSubject(request)

        # authz check
        ctx = [ (rights.CTX_VIEWGROUP, 'wlcg') ]
        if not self.authorizer.isAllowed(subject, rights.ACTION_VIEW, ctx):
            return self.renderAuthzErrorPage(request, 'WLCG view', subject)

        # access allowed
        request.write(html.HTML_VIEWBASE_HEADER % {'title': 'WLCG Views'})
        request.write( html.createTitle('WLCG Views') )
        for view_url, ( description, _ ) in self.subview.items():
            request.write('<div><a href=wlcg/%s>%s</a></div>\n' % (view_url, description))
            request.write( html.P )
        request.write(html.HTML_VIEWBASE_FOOTER)
        request.finish()
        return server.NOT_DONE_YET
Beispiel #5
0
    def render_GET(self, request):

        subject = resourceutil.getSubject(request)

        # authz check
        ctx = [ (rights.CTX_VIEWGROUP, 'wlcg') ]
        if not self.authorizer.isAllowed(subject, rights.ACTION_VIEW, ctx):
            return self.renderAuthzErrorPage(request, 'WLCG view', subject)

        # access allowed
        request.write(html.HTML_VIEWBASE_HEADER % {'title': 'WLCG Views'})
        request.write( html.createTitle('WLCG Views') )
        for view_url, ( description, _ ) in self.subview.items():
            request.write('<div><a href=wlcg/%s>%s</a></div>\n' % (view_url, description))
            request.write( html.P )
        request.write(html.HTML_VIEWBASE_FOOTER)
        request.finish()
        return server.NOT_DONE_YET
Beispiel #6
0
    def renderWLCGViewPage(self, db_rows, request, date, media, t_query_start):

        t_query = time.time() - t_query_start
        t_dataprocess_start = time.time()

        records = self.buildRecords(db_rows)

        # remove infomration from certain groups, add they not add any value
        records = [ rec for rec in records if rec['group'] not in ('dteam', 'behrmann') ]
        if media == 'disk':
            records = [ rec for rec in records if rec['media'] == 'disk' ]
        elif media == 'tape':
            records = [ rec for rec in records if rec['media'] == 'tape' ]
        elif media == 'all':
            pass
        else:
            return self.renderErrorPage('Invalid media selection', request)

        # get top level domains and sites per country
        hosts = set( [ rec['site'] for rec in records ] )
        tld_groups = {}
        country_sites = {}
        for host in hosts:
            tld = host.split('.')[-1].upper()
            tld_groups.setdefault(tld, []).append(host)

        # get groups
        groups = set( [ rec['group'] for rec in records ] )

        TOTAL = 'Total'
        TIER_TOTAL = self.default_tier.split('-')[0].upper()

        # calculate totals per site / group
        site_group_totals = {}
        for rec in records:
            site, group, rcu = rec['site'], rec['group'], rec['rcu']
            key = (site, group)
            site_group_totals[key] = site_group_totals.get(key, 0) + rcu

        # calculate total per site
        site_totals = {}
        for rec in records:
            site, rcu = rec['site'], rec['rcu']
            site_totals[site] = site_totals.get(site,0) + rcu

        # calculate total per group / country
        group_country_totals = {}
        for rec in records:
            country = rec['site'].split('.')[-1].upper() + '-TOTAL'
            key = (country, rec['group'])
            group_country_totals[key] = group_country_totals.get(key, 0) + rec['rcu']

        # calculate total per country
        country_totals = {}
        for rec in records:
            country = rec['site'].split('.')[-1].upper() + '-TOTAL'
            country_totals[country] = country_totals.get(country,0) + rec['rcu']

        # calculate total per group
        group_totals = {}
        for rec in records:
            group_totals[rec['group']] = group_totals.get(rec['group'],0) + rec['rcu']

        # calculate total
        total = sum( [ rec['rcu'] for rec in records ] )

        totals = []

        # put all calculated records together and add equivalents
        for (site, group), rcu in site_group_totals.items():
            totals.append( { 'site': site, 'group': group, 'rcu': rcu } )
        for site, rcu in site_totals.items():
            totals.append( { 'site': site, 'group': TOTAL, 'rcu': rcu } )
        for (country, group), rcu in group_country_totals.items():
            totals.append( { 'site': country, 'group': group, 'rcu': rcu } )
        for country, rcu in country_totals.items():
            totals.append( { 'site': country, 'group': TOTAL, 'rcu': rcu } )
        for group, rcu in group_totals.items():
            totals.append( { 'site': TIER_TOTAL, 'group': group, 'rcu': rcu } )
        totals.append( { 'site': TIER_TOTAL, 'group': TOTAL, 'rcu': total } )

        # create table
        columns = sorted(groups)
        columns.append(TOTAL)

        row_names = []
        for tld in sorted(tld_groups):
            row_names += sorted(tld_groups[tld])
            row_names.append(tld + '-TOTAL')
        row_names.append(TIER_TOTAL)

        elements = []
        for row in row_names:
            for col in columns:
                for rec in totals:
                    if rec['site'] == row and rec['group'] == col:
                        value = rec['rcu']
                        # hurrah for formatting
                        if row == TIER_TOTAL and col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, double_underlined=True)
                        elif (row.endswith('-TOTAL') and col == TOTAL) or row == TIER_TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, underlined=True)
                        elif row.endswith('-TOTAL') or row == TIER_TOTAL or col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True)
                        elements.append( ((col,row), value))
                        break
                else:
                    elements.append( ((col,row), '') )

        matrix = dict(elements)
        t_dataprocess = time.time() - t_dataprocess_start
        table_content = htmltable.createHTMLTable(matrix, columns, row_names, column_labels=COLUMN_NAMES)

        title = 'WLCG storage view'
        media_options = [ ( 'all', 'Disk and Tape') , ( 'disk', 'Disk only' ), ( 'tape', 'Tape only' ) ]

        media_buttons = html.createRadioButtons('media', media_options, checked_value=media)
        month_options = dateform.generateMonthFormOptions()
        selector = html.createSelector('Month', 'date', month_options, date)
        selector_form = html.createSelectorForm(self.path, [ selector ], media_buttons )

        date_text = html.createParagraph('Date: %s' % (date))

        request.write( html.HTML_VIEWBASE_HEADER % {'title': title} )
        request.write( html.createTitle(title) )
        request.write( html.createParagraph(selector_form) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(date_text) )
        request.write( table_content )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph('Query time: %s' % round(t_query, 2)) )
        request.write( html.createParagraph('Data process time: %s' % round(t_dataprocess, 2)) )
        request.write( html.HTML_VIEWBASE_FOOTER )

        request.finish()
        return server.NOT_DONE_YET
Beispiel #7
0
    def renderWLCGViewPage(self, wlcg_data, request, start_date, end_date, unit, t_query_start):

        t_query = time.time() - t_query_start
        days = dateform.dayDelta(start_date, end_date)
        t_dataprocess_start = time.time()

        wlcg_records = dataprocess.rowsToDicts(wlcg_data)
        # information on ops and dteam vo does not add any value
        wlcg_records = [ rec for rec in wlcg_records if rec[dataprocess.VO_NAME] not in ('dteam', 'ops') ]

        # massage data
        wlcg_records = dataprocess.addMissingScaleValues(wlcg_records)
        wlcg_records = dataprocess.collapseFields(wlcg_records, self.collapse)
        wlcg_records = dataprocess.tierMergeSplit(wlcg_records, self.tier_mapping, self.tier_shares, self.default_tier)
        # role must be collapsed after split in order for the tier split to function
        wlcg_records = dataprocess.collapseFields(wlcg_records, [ dataprocess.VO_ROLE ] )

        sort_key = lambda key : dataprocess.sortKey(key, field_order=[ dataprocess.HOST] )
        split_records = dataprocess.splitRecords(wlcg_records, dataprocess.TIER)
        for split_attr, records in split_records.items():
            split_records[split_attr] = sorted(records, key=sort_key)

        t_dataprocess = time.time() - t_dataprocess_start

        # get tld groups
        hosts = set( [ rec[dataprocess.HOST] for rec in wlcg_records ] )
        tld_groups = {}
        for host in hosts:
            tld = host.split('.')[-1].upper()
            tld_groups.setdefault(tld, []).append(host)

        # create composite to-tier names
        vo_tiers = set()
        for rec in wlcg_records:
            tl = 't1' if 'T1' in rec[dataprocess.TIER] else 't2'
            vt = tl + '-' + rec[dataprocess.VO_NAME]
            rec[dataprocess.VO_NAME] = vt
            del rec[dataprocess.TIER] # same as collapsing afterwards
            vo_tiers.add(vt)

        TOTAL = 'Total'
        TIER_TOTAL = self.default_tier.split('-')[0].upper()

        # calculate total per site
        site_totals = dataprocess.collapseFields(wlcg_records, ( dataprocess.VO_NAME, ) )
        for r in site_totals:
            r[dataprocess.VO_NAME] = TOTAL

        # calculate total per country-tier
        country_tier_totals = [ r.copy() for r in wlcg_records ]
        for rec in country_tier_totals:
            rec[dataprocess.HOST] = rec[dataprocess.HOST].split('.')[-1].upper() + '-TOTAL'
            rec[dataprocess.USER] = 'FAKE'
        country_tier_totals = dataprocess.collapseFields(country_tier_totals, ( dataprocess.USER, ) )

        # calculate total per country
        country_totals = dataprocess.collapseFields(country_tier_totals, ( dataprocess.VO_NAME, ) )
        for rec in country_totals:
            rec[dataprocess.VO_NAME] = TOTAL

        # calculate total per tier-vo
        tier_vo_totals = dataprocess.collapseFields(wlcg_records, ( dataprocess.HOST, ) )
        for r in tier_vo_totals:
            r[dataprocess.HOST] = TIER_TOTAL

        # calculate total
        total = dataprocess.collapseFields(wlcg_records, ( dataprocess.HOST, dataprocess.VO_NAME ) )
        assert len(total) in (0,1), 'Records did not collapse into a single record when calculating grand total'
        if len(total) == 0:
            total = [ { dataprocess.CPU_TIME : 0, dataprocess.WALL_TIME : 0, dataprocess.KSI2K_CPU_TIME : 0, dataprocess.KSI2K_WALL_TIME : 0 } ]
        total_record = total[0]
        total_record[dataprocess.HOST] = TIER_TOTAL
        total_record[dataprocess.VO_NAME] = TOTAL

        # put all calculated records together and add equivalents
        wlcg_records += site_totals
        wlcg_records += country_tier_totals
        wlcg_records += country_totals
        wlcg_records += tier_vo_totals
        wlcg_records += [ total_record ]
        wlcg_records = dataprocess.addEquivalentProperties(wlcg_records, days)

        # create table
        columns = sorted(vo_tiers)
        columns.append(TOTAL)

        row_names = []
        for tld in sorted(tld_groups):
            row_names += tld_groups[tld]
            row_names.append(tld + '-TOTAL')
        row_names.append(TIER_TOTAL)

        unit_extractor = WLCG_UNIT_MAPPING.get(unit, WLCG_UNIT_MAPPING_DEFAULT)

        elements = []
        for row in row_names:
            for col in columns:
                for rec in wlcg_records:
                    if rec[dataprocess.HOST] == row and rec[dataprocess.VO_NAME] == col:
                        value = _formatValue( unit_extractor(rec) )
                        # hurrah for formatting
                        if row == TIER_TOTAL and col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, double_underlined=True)
                        elif (row.endswith('-TOTAL') and col == TOTAL) or row == TIER_TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, underlined=True)
                        elif row.endswith('-TOTAL') or row == TIER_TOTAL or col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True)
                        elements.append( ((col,row), value))
                        break
                else:
                    elements.append( ((col,row), '') )

        matrix = dict(elements)
        table_content = htmltable.createHTMLTable(matrix, columns, row_names, column_labels=COLUMN_NAMES)

        # render page
        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option   = request.args.get('enddate', [''])[0]

        title = 'WLCG oversight view'
        unit_options = [ ( 'ksi2k-ne', 'KSI2K Node Equivalents (default)') , ( 'hs06-ne', 'HS06 Node Equivalents' ),
                         ( 'ksi2k-wallhours', 'KSI2K Walltime Hours' ) , ( 'hs06-wallhours', 'HS06 Walltime hours') ]
        unit_buttons = html.createRadioButtons('unit', unit_options, checked_value=unit)
        selector_form = dateform.createMonthSelectorForm(self.path, start_date_option, end_date_option, unit_buttons)

        quarters = dateform.generateFormQuarters()
        quarter_links = []
        for q in quarters:
            year, quart = dateform.parseQuarter(q)
            sd, ed = dateform.quarterStartEndDates(year, quart)
            quarter_links.append(html.createLink('%s?startdate=%s&enddate=%s' % (self.path, sd, ed), q ) )
        range_text = html.createParagraph('Date range: %s - %s (%s days)' % (start_date, end_date, days))

        request.write( html.HTML_VIEWBASE_HEADER % {'title': title} )
        request.write( html.createTitle(title) )
        request.write( html.createParagraph('Quarters: \n    ' + ('    ' + html.NBSP).join(quarter_links) ) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(selector_form) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(range_text) )
        request.write( table_content )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph('Query time: %s' % round(t_query, 2)) )
        request.write( html.createParagraph('Data process time: %s' % round(t_dataprocess, 2)) )
        request.write( html.HTML_VIEWBASE_FOOTER )

        request.finish()
        return server.NOT_DONE_YET
Beispiel #8
0
    def renderMachineView(self, results, request):

        machine_manifest = results[0][1][0]
        jobs_per_day     = results[1][1]
        top_projects     = results[2][1]
        top_users        = results[3][1]

        first_record_registration   = machine_manifest[0]
        last_record_registration    = machine_manifest[1]
        first_job_start             = machine_manifest[2]
        last_job_termination        = machine_manifest[3]
        distinct_users              = machine_manifest[4]
        distinct_projects           = machine_manifest[5]
        n_jobs                      = machine_manifest[6]

        e_batches = [ e[0] for e in jobs_per_day ] # dates
        e_groups  = [ 'jobs' ]
        e_matrix  = dict( [ ((e[0], e_groups[0]), e[1]) for e in jobs_per_day ] )
        executed_table = htmltable.createHTMLTable(e_matrix, e_batches, e_groups, base_indent=4)

        stats_batches = [ 'Walltime days', 'Efficiency', 'Number of jobs' ]

        p_groups = [ e[0] for e in top_projects ] # user list
        p_dict_elements = []
        p_dict_elements += [ ((stats_batches[0], p[0]), p[1]) for p in top_projects ]
        p_dict_elements += [ ((stats_batches[1], p[0]), p[2]) for p in top_projects ]
        p_dict_elements += [ ((stats_batches[2], p[0]), p[3]) for p in top_projects ]
        p_matrix = dict(p_dict_elements)
        project_table = htmltable.createHTMLTable(p_matrix, stats_batches, p_groups, base_indent=4)

        u_groups = [ e[0] for e in top_users ] # user list
        u_dict_elements = []
        u_dict_elements += [ ((stats_batches[0], u[0]), u[1]) for u in top_users ]
        u_dict_elements += [ ((stats_batches[1], u[0]), u[2]) for u in top_users ]
        u_dict_elements += [ ((stats_batches[2], u[0]), u[3]) for u in top_users ]
        u_matrix = dict(u_dict_elements)
        user_table = htmltable.createHTMLTable(u_matrix, stats_batches, u_groups, base_indent=4)

        title = 'Machine view for %s' % self.machine_name

        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option   = request.args.get('enddate', [''])[0]

        # generate year-month options one year back
        month_options = ['']
        gmt = time.gmtime()
        for i in range(gmt.tm_mon-12, gmt.tm_mon+1):
            if i <= 0:
                month_options.append('%i-%02d' % (gmt.tm_year - 1, 12 + i) )
            elif i > 0:
                month_options.append('%i-%02d' % (gmt.tm_year, i) )

        sel1 = html.createSelector('Start month', 'startdate', month_options, start_date_option)
        sel2 = html.createSelector('End month', 'enddate', month_options, end_date_option)
        selector_form = html.createSelectorForm(self.machine_name, [sel1, sel2] )

        if start_date_option or end_date_option:
            range_text = 'selected date range'
        else:
            range_text = 'current month'

        # create page

        request.write(html.HTML_VIEWBASE_HEADER % {'title': title})
        request.write( html.createTitle(title) )

        request.write('\n' + selector_form + '\n')

        if not (start_date_option or end_date_option):
        # skip manifest / inserts if date range is selected
            request.write( html.createSectionTitle('Manifest') )
            request.write( html.createParagraph('First record registration: %s' % first_record_registration) )
            request.write( html.createParagraph('Last record registration: %s' % last_record_registration) )
            request.write( html.createParagraph('First job start: %s' % first_job_start) )
            request.write( html.createParagraph('Last job termination: %s' % last_job_termination) )
            request.write( html.createParagraph('Distinct users: %s' % distinct_users) )
            request.write( html.createParagraph('Distinct projects: %s' % distinct_projects) )
            request.write( html.createParagraph('Number of jobs: %s' % n_jobs) )
            request.write( html.SECTION_BREAK )

            request.write( html.createSectionTitle('Executed jobs in the last ten days') )
            request.write( executed_table )
            request.write( html.SECTION_BREAK)

        request.write( html.createSectionTitle('Top 10 projects for the %s' % range_text) )
        request.write( project_table )
        request.write( html.SECTION_BREAK )

        request.write( html.createSectionTitle('Top 20 users for the %s' % range_text) )
        request.write( user_table )
        request.write( html.P + '\n' )

        request.write(html.HTML_VIEWBASE_FOOTER)

        request.finish()
        return server.NOT_DONE_YET
Beispiel #9
0
    def renderWLCGViewPage(self, db_rows, request, date, media, t_query_start):

        t_query = time.time() - t_query_start
        t_dataprocess_start = time.time()

        records = self.buildRecords(db_rows)

        # remove infomration from certain groups, add they not add any value
        records = [ rec for rec in records if rec['group'] not in ('dteam', 'behrmann') ]
        if media == 'disk':
            records = [ rec for rec in records if rec['media'] == 'disk' ]
        elif media == 'tape':
            records = [ rec for rec in records if rec['media'] == 'tape' ]
        elif media == 'all':
            pass
        else:
            return self.renderErrorPage('Invalid media selection', request)

        # get top level domains and sites per country
        hosts = set( [ rec['site'] for rec in records ] )
        tld_groups = {}
        country_sites = {}
        for host in hosts:
            tld = host.split('.')[-1].upper()
            tld_groups.setdefault(tld, []).append(host)

        # get groups
        groups = set( [ rec['group'] for rec in records ] )

        TOTAL = 'Total'
        TIER_TOTAL = self.default_tier.split('-')[0].upper()

        # calculate totals per site / group
        site_group_totals = {}
        for rec in records:
            site, group, rcu = rec['site'], rec['group'], rec['rcu']
            key = (site, group)
            site_group_totals[key] = site_group_totals.get(key, 0) + rcu

        # calculate total per site
        site_totals = {}
        for rec in records:
            site, rcu = rec['site'], rec['rcu']
            site_totals[site] = site_totals.get(site,0) + rcu

        # calculate total per group / country
        group_country_totals = {}
        for rec in records:
            country = rec['site'].split('.')[-1].upper() + '-TOTAL'
            key = (country, rec['group'])
            group_country_totals[key] = group_country_totals.get(key, 0) + rec['rcu']

        # calculate total per country
        country_totals = {}
        for rec in records:
            country = rec['site'].split('.')[-1].upper() + '-TOTAL'
            country_totals[country] = country_totals.get(country,0) + rec['rcu']

        # calculate total per group
        group_totals = {}
        for rec in records:
            group_totals[rec['group']] = group_totals.get(rec['group'],0) + rec['rcu']

        # calculate total
        total = sum( [ rec['rcu'] for rec in records ] )

        totals = []

        # put all calculated records together and add equivalents
        for (site, group), rcu in site_group_totals.items():
            totals.append( { 'site': site, 'group': group, 'rcu': rcu } )
        for site, rcu in site_totals.items():
            totals.append( { 'site': site, 'group': TOTAL, 'rcu': rcu } )
        for (country, group), rcu in group_country_totals.items():
            totals.append( { 'site': country, 'group': group, 'rcu': rcu } )
        for country, rcu in country_totals.items():
            totals.append( { 'site': country, 'group': TOTAL, 'rcu': rcu } )
        for group, rcu in group_totals.items():
            totals.append( { 'site': TIER_TOTAL, 'group': group, 'rcu': rcu } )
        totals.append( { 'site': TIER_TOTAL, 'group': TOTAL, 'rcu': total } )

        # create table
        columns = sorted(groups)
        columns.append(TOTAL)

        row_names = []
        for tld in sorted(tld_groups):
            row_names += sorted(tld_groups[tld])
            row_names.append(tld + '-TOTAL')
        row_names.append(TIER_TOTAL)

        elements = []
        for row in row_names:
            for col in columns:
                for rec in totals:
                    if rec['site'] == row and rec['group'] == col:
                        value = rec['rcu']
                        # hurrah for formatting
                        if row == TIER_TOTAL and col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, double_underlined=True)
                        elif (row.endswith('-TOTAL') and col == TOTAL) or row == TIER_TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, underlined=True)
                        elif row.endswith('-TOTAL') or row == TIER_TOTAL or col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True)
                        elements.append( ((col,row), value))
                        break
                else:
                    elements.append( ((col,row), '') )

        matrix = dict(elements)
        t_dataprocess = time.time() - t_dataprocess_start
        table_content = htmltable.createHTMLTable(matrix, columns, row_names, column_labels=COLUMN_NAMES)

        title = 'WLCG storage view'
        media_options = [ ( 'all', 'Disk and Tape') , ( 'disk', 'Disk only' ), ( 'tape', 'Tape only' ) ]

        media_buttons = html.createRadioButtons('media', media_options, checked_value=media)
        month_options = dateform.generateMonthFormOptions()
        selector = html.createSelector('Month', 'date', month_options, date)
        selector_form = html.createSelectorForm(self.path, [ selector ], media_buttons )

        date_text = html.createParagraph('Date: %s' % (date))

        request.write( html.HTML_VIEWBASE_HEADER % {'title': title} )
        request.write( html.createTitle(title) )
        request.write( html.createParagraph(selector_form) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(date_text) )
        request.write( table_content )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph('Query time: %s' % round(t_query, 2)) )
        request.write( html.createParagraph('Data process time: %s' % round(t_dataprocess, 2)) )
        request.write( html.HTML_VIEWBASE_FOOTER )

        request.finish()
        return server.NOT_DONE_YET
Beispiel #10
0
    def renderWLCGViewPage(self, wlcg_data, request, start_date, end_date, unit, t_query_start):

        t_query = time.time() - t_query_start
        days = dateform.dayDelta(start_date, end_date)
        t_dataprocess_start = time.time()

        wlcg_records = dataprocess.rowsToDicts(wlcg_data)
        # information on ops and dteam vo does not add any value
        wlcg_records = [ rec for rec in wlcg_records if rec[dataprocess.VO_NAME] not in ('dteam', 'ops') ]

        # massage data
        wlcg_records = dataprocess.addMissingScaleValues(wlcg_records)
        wlcg_records = dataprocess.collapseFields(wlcg_records, self.collapse)
        wlcg_records = dataprocess.tierMergeSplit(wlcg_records, self.tier_mapping, self.tier_shares, self.default_tier)
        # role must be collapsed after split in order for the tier split to function
        wlcg_records = dataprocess.collapseFields(wlcg_records, [ dataprocess.VO_ROLE ] )

        sort_key = lambda key : dataprocess.sortKey(key, field_order=[ dataprocess.HOST] )
        split_records = dataprocess.splitRecords(wlcg_records, dataprocess.TIER)
        for split_attr, records in split_records.items():
            split_records[split_attr] = sorted(records, key=sort_key)

        t_dataprocess = time.time() - t_dataprocess_start

        # get tld groups
        hosts = set( [ rec[dataprocess.HOST] for rec in wlcg_records ] )
        tld_groups = {}
        for host in hosts:
            tld = host.split('.')[-1].upper()
            tld_groups.setdefault(tld, []).append(host)

        # create composite to-tier names
        vo_tiers = set()
        for rec in wlcg_records:
            tl = 't1' if 'T1' in rec[dataprocess.TIER] else 't2'
            vt = tl + '-' + rec[dataprocess.VO_NAME]
            rec[dataprocess.VO_NAME] = vt
            del rec[dataprocess.TIER] # same as collapsing afterwards
            vo_tiers.add(vt)

        TOTAL = 'Total'
        TIER_TOTAL = self.default_tier.split('-')[0].upper()

        # calculate total per site
        site_totals = dataprocess.collapseFields(wlcg_records, ( dataprocess.VO_NAME, ) )
        for r in site_totals:
            r[dataprocess.VO_NAME] = TOTAL

        # calculate total per country-tier
        country_tier_totals = [ r.copy() for r in wlcg_records ]
        for rec in country_tier_totals:
            rec[dataprocess.HOST] = rec[dataprocess.HOST].split('.')[-1].upper() + '-TOTAL'
            rec[dataprocess.USER] = 'FAKE'
        country_tier_totals = dataprocess.collapseFields(country_tier_totals, ( dataprocess.USER, ) )

        # calculate total per country
        country_totals = dataprocess.collapseFields(country_tier_totals, ( dataprocess.VO_NAME, ) )
        for rec in country_totals:
            rec[dataprocess.VO_NAME] = TOTAL

        # calculate total per tier-vo
        tier_vo_totals = dataprocess.collapseFields(wlcg_records, ( dataprocess.HOST, ) )
        for r in tier_vo_totals:
            r[dataprocess.HOST] = TIER_TOTAL

        # calculate total
        total = dataprocess.collapseFields(wlcg_records, ( dataprocess.HOST, dataprocess.VO_NAME ) )
        assert len(total) in (0,1), 'Records did not collapse into a single record when calculating grand total'
        if len(total) == 0:
            total = [ { dataprocess.CPU_TIME : 0, dataprocess.WALL_TIME : 0, dataprocess.KSI2K_CPU_TIME : 0, dataprocess.KSI2K_WALL_TIME : 0 } ]
        total_record = total[0]
        total_record[dataprocess.HOST] = TIER_TOTAL
        total_record[dataprocess.VO_NAME] = TOTAL

        # put all calculated records together and add equivalents
        wlcg_records += site_totals
        wlcg_records += country_tier_totals
        wlcg_records += country_totals
        wlcg_records += tier_vo_totals
        wlcg_records += [ total_record ]
        wlcg_records = dataprocess.addEquivalentProperties(wlcg_records, days)

        # create table
        columns = sorted(vo_tiers)
        columns.append(TOTAL)

        row_names = []
        for tld in sorted(tld_groups):
            row_names += tld_groups[tld]
            row_names.append(tld + '-TOTAL')
        row_names.append(TIER_TOTAL)

        unit_extractor = WLCG_UNIT_MAPPING.get(unit, WLCG_UNIT_MAPPING_DEFAULT)

        elements = []
        for row in row_names:
            for col in columns:
                for rec in wlcg_records:
                    if rec[dataprocess.HOST] == row and rec[dataprocess.VO_NAME] == col:
                        value = _formatValue( unit_extractor(rec) )
                        # hurrah for formatting
                        if row == TIER_TOTAL and col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, double_underlined=True)
                        elif (row.endswith('-TOTAL') and col == TOTAL) or row == TIER_TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, underlined=True)
                        elif row.endswith('-TOTAL') or row == TIER_TOTAL or col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True)
                        elements.append( ((col,row), value))
                        break
                else:
                    elements.append( ((col,row), '') )

        matrix = dict(elements)
        table_content = htmltable.createHTMLTable(matrix, columns, row_names, column_labels=COLUMN_NAMES)

        # render page
        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option   = request.args.get('enddate', [''])[0]

        title = 'WLCG oversight view'
        unit_options = [ ( 'ksi2k-ne', 'KSI2K Node Equivalents (default)') , ( 'hs06-ne', 'HS06 Node Equivalents' ),
                         ( 'ksi2k-wallhours', 'KSI2K Walltime Hours' ) , ( 'hs06-wallhours', 'HS06 Walltime hours') ]
        unit_buttons = html.createRadioButtons('unit', unit_options, checked_value=unit)
        selector_form = dateform.createMonthSelectorForm(self.path, start_date_option, end_date_option, unit_buttons)

        quarters = dateform.generateFormQuarters()
        quarter_links = []
        for q in quarters:
            year, quart = dateform.parseQuarter(q)
            sd, ed = dateform.quarterStartEndDates(year, quart)
            quarter_links.append(html.createLink('%s?startdate=%s&enddate=%s' % (self.path, sd, ed), q ) )
        range_text = html.createParagraph('Date range: %s - %s (%s days)' % (start_date, end_date, days))

        request.write( html.HTML_VIEWBASE_HEADER % {'title': title} )
        request.write( html.createTitle(title) )
        request.write( html.createParagraph('Quarters: \n    ' + ('    ' + html.NBSP).join(quarter_links) ) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(selector_form) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(range_text) )
        request.write( table_content )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph('Query time: %s' % round(t_query, 2)) )
        request.write( html.createParagraph('Data process time: %s' % round(t_dataprocess, 2)) )
        request.write( html.HTML_VIEWBASE_FOOTER )

        request.finish()
        return server.NOT_DONE_YET
Beispiel #11
0
    def renderMachineView(self, results, request):

        machine_manifest = results[0][1][0]
        jobs_per_day = results[1][1]
        top_projects = results[2][1]
        top_users = results[3][1]

        first_record_registration = machine_manifest[0]
        last_record_registration = machine_manifest[1]
        first_job_start = machine_manifest[2]
        last_job_termination = machine_manifest[3]
        distinct_users = machine_manifest[4]
        distinct_projects = machine_manifest[5]
        n_jobs = machine_manifest[6]

        e_batches = [e[0] for e in jobs_per_day]  # dates
        e_groups = ['jobs']
        e_matrix = dict([((e[0], e_groups[0]), e[1]) for e in jobs_per_day])
        executed_table = htmltable.createHTMLTable(e_matrix,
                                                   e_batches,
                                                   e_groups,
                                                   base_indent=4)

        stats_batches = ['Walltime days', 'Efficiency', 'Number of jobs']

        p_groups = [e[0] for e in top_projects]  # user list
        p_dict_elements = []
        p_dict_elements += [((stats_batches[0], p[0]), p[1])
                            for p in top_projects]
        p_dict_elements += [((stats_batches[1], p[0]), p[2])
                            for p in top_projects]
        p_dict_elements += [((stats_batches[2], p[0]), p[3])
                            for p in top_projects]
        p_matrix = dict(p_dict_elements)
        project_table = htmltable.createHTMLTable(p_matrix,
                                                  stats_batches,
                                                  p_groups,
                                                  base_indent=4)

        u_groups = [e[0] for e in top_users]  # user list
        u_dict_elements = []
        u_dict_elements += [((stats_batches[0], u[0]), u[1])
                            for u in top_users]
        u_dict_elements += [((stats_batches[1], u[0]), u[2])
                            for u in top_users]
        u_dict_elements += [((stats_batches[2], u[0]), u[3])
                            for u in top_users]
        u_matrix = dict(u_dict_elements)
        user_table = htmltable.createHTMLTable(u_matrix,
                                               stats_batches,
                                               u_groups,
                                               base_indent=4)

        title = 'Machine view for %s' % self.machine_name

        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option = request.args.get('enddate', [''])[0]

        # generate year-month options one year back
        month_options = ['']
        gmt = time.gmtime()
        for i in range(gmt.tm_mon - 12, gmt.tm_mon + 1):
            if i <= 0:
                month_options.append('%i-%02d' % (gmt.tm_year - 1, 12 + i))
            elif i > 0:
                month_options.append('%i-%02d' % (gmt.tm_year, i))

        sel1 = html.createSelector('Start month', 'startdate', month_options,
                                   start_date_option)
        sel2 = html.createSelector('End month', 'enddate', month_options,
                                   end_date_option)
        selector_form = html.createSelectorForm(self.machine_name,
                                                [sel1, sel2])

        if start_date_option or end_date_option:
            range_text = 'selected date range'
        else:
            range_text = 'current month'

        # create page

        request.write(html.HTML_VIEWBASE_HEADER % {'title': title})
        request.write(html.createTitle(title))

        request.write('\n' + selector_form + '\n')

        if not (start_date_option or end_date_option):
            # skip manifest / inserts if date range is selected
            request.write(html.createSectionTitle('Manifest'))
            request.write(
                html.createParagraph('First record registration: %s' %
                                     first_record_registration))
            request.write(
                html.createParagraph('Last record registration: %s' %
                                     last_record_registration))
            request.write(
                html.createParagraph('First job start: %s' % first_job_start))
            request.write(
                html.createParagraph('Last job termination: %s' %
                                     last_job_termination))
            request.write(
                html.createParagraph('Distinct users: %s' % distinct_users))
            request.write(
                html.createParagraph('Distinct projects: %s' %
                                     distinct_projects))
            request.write(html.createParagraph('Number of jobs: %s' % n_jobs))
            request.write(html.SECTION_BREAK)

            request.write(
                html.createSectionTitle('Executed jobs in the last ten days'))
            request.write(executed_table)
            request.write(html.SECTION_BREAK)

        request.write(
            html.createSectionTitle('Top 10 projects for the %s' % range_text))
        request.write(project_table)
        request.write(html.SECTION_BREAK)

        request.write(
            html.createSectionTitle('Top 20 users for the %s' % range_text))
        request.write(user_table)
        request.write(html.P + '\n')

        request.write(html.HTML_VIEWBASE_FOOTER)

        request.finish()
        return server.NOT_DONE_YET
Beispiel #12
0
    def renderWLCGViewPage(self, wlcg_data, request, start_date, end_date,
                           unit, t_query_start):

        t_query = time.time() - t_query_start
        days = dateform.dayDelta(start_date, end_date)
        t_dataprocess_start = time.time()

        wlcg_records = dataprocess.rowsToDicts(wlcg_data)
        wlcg_records = [
            r for r in wlcg_records
            if r[dataprocess.VO_NAME] in ('alice', 'atlas')
        ]

        # Separate the records into computing and storage
        comp_records = []
        storage_records = []
        for r in wlcg_records:
            if r[dataprocess.HOST] == 'STORAGE':
                storage_records.append(r)
            else:
                comp_records.append(r)

        # massage data
        comp_records = dataprocess.addMissingScaleValues(
            comp_records, self.hepspec06)
        comp_records = dataprocess.collapseFields(comp_records, self.collapse)
        comp_records = dataprocess.tierMergeSplit(comp_records,
                                                  self.tier_mapping,
                                                  self.tier_shares,
                                                  self.default_tier)

        # Collapse the fields that couldn't be collapsed before the tier-splitting.
        comp_records = [r for r in comp_records if r['tier'] == u'NDGF-T1']
        comp_records = dataprocess.collapseFields(
            comp_records, [dataprocess.VO_ROLE, dataprocess.HOST])

        summary = {}
        for r in comp_records + storage_records:
            vo = r[dataprocess.VO_NAME]

            if vo not in summary:
                summary[vo] = {
                    'disk': 0.0,
                    'tape': 0.0,
                    dataprocess.HS06_WALL_TIME: 0.0,
                    dataprocess.HS06_CPU_TIME: 0.0
                }

            if r.get(dataprocess.HOST, '') == 'STORAGE':
                media = r[
                    'vo_group']  # We stored "storage_media" in "vo_group" ...
                summary[vo][media] += r[
                    dataprocess.
                    N_JOBS]  # ... and "resource_capacity_used" in "n_jobs".
            else:
                summary[vo][dataprocess.HS06_WALL_TIME] += r[
                    dataprocess.HS06_WALL_TIME]
                summary[vo][dataprocess.HS06_CPU_TIME] += r[
                    dataprocess.HS06_CPU_TIME]

        columns = [("Walltime (HS06 days)",
                    lambda r: r[dataprocess.HS06_WALL_TIME] / 24.0),
                   ("CPUtime (HS06 days)",
                    lambda r: r[dataprocess.HS06_CPU_TIME] / 24.0),
                   ("Disk (TiB)", lambda r: r['disk'] / 1024.0**4),
                   ("Tape (TiB)", lambda r: r['tape'] / 1024.0**4)]

        elements = []
        for row in summary.keys():
            for cname, cfunc in columns:
                value = "%.2f" % cfunc(summary[row])
                elements.append(((cname, row), value))

        t_dataprocess = time.time() - t_dataprocess_start

        matrix = dict(elements)
        table_content = htmltable.createHTMLTable(matrix,
                                                  [c[0] for c in columns],
                                                  summary.keys())

        # render page
        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option = request.args.get('enddate', [''])[0]

        title = 'WLCG T1 Summary'
        selector_form = dateform.createMonthSelectorForm(
            self.path, start_date_option, end_date_option)

        quarters = dateform.generateFormQuarters()
        quarter_links = []
        for q in quarters:
            year, quart = dateform.parseQuarter(q)
            sd, ed = dateform.quarterStartEndDates(year, quart)
            quarter_links.append(
                html.createLink(
                    '%s?startdate=%s&enddate=%s' % (self.path, sd, ed), q))
        range_text = html.createParagraph('Date range: %s - %s (%s days)' %
                                          (start_date, end_date, days))

        request.write(html.HTML_VIEWBASE_HEADER % {'title': title})
        request.write(html.createTitle(title))
        request.write(
            html.createParagraph('Quarters: \n    ' +
                                 ('    ' + html.NBSP).join(quarter_links)))
        request.write(html.SECTION_BREAK)
        request.write(html.createParagraph(selector_form))
        request.write(html.SECTION_BREAK)
        request.write(html.createParagraph(range_text))
        request.write(table_content)
        request.write(html.SECTION_BREAK)
        request.write(
            html.createParagraph('Query time: %s' % round(t_query, 2)))
        request.write(
            html.createParagraph('Data process time: %s' %
                                 round(t_dataprocess, 2)))
        request.write(html.HTML_VIEWBASE_FOOTER)

        request.finish()
        return server.NOT_DONE_YET
Beispiel #13
0
    def renderWLCGViewPage(self, wlcg_data, request, start_date, end_date, unit, t_query_start):

        t_query = time.time() - t_query_start
        days = dateform.dayDelta(start_date, end_date)
        t_dataprocess_start = time.time()

        wlcg_records = wlcg.rowsToDicts(wlcg_data, [ wlcg.MACHINE_NAME, wlcg.COUNTRY, wlcg.VO_NAME, unit ])

        t_dataprocess = time.time() - t_dataprocess_start

        # get tld groups
        tld_groups = {}
        for rec in wlcg_records:
            host = rec[wlcg.MACHINE_NAME]
            tld = _countryCode(rec)
            if tld not in tld_groups:
                tld_groups[tld] = [host]
            elif  host not in tld_groups[tld]:
                tld_groups[tld].append(host)

        vo_tiers = set()
        for rec in wlcg_records:
            vo_tiers.add(rec[wlcg.VO_NAME])

        TOTAL = 'Total'
        TIER_TOTAL = self.default_tier.split('-')[0].upper()

        site_totals = _collapseFields(wlcg_records, ( wlcg.VO_NAME, ) )
        for r in site_totals:
            r[wlcg.VO_NAME] = TOTAL

        country_vo_aggregate = {}
        for r in wlcg_records:
            machine = _countryCode(r) + '-TOTAL'
            if machine not in country_vo_aggregate:
                country_vo_aggregate[machine] = {}
            vo = r[wlcg.VO_NAME]
            if vo not in country_vo_aggregate[machine]:
                country_vo_aggregate[machine][vo] = 0
            country_vo_aggregate[machine][vo] += r[unit]

        country_tier_totals = []
        for machine in country_vo_aggregate:
            for vo in country_vo_aggregate[machine]:
                r = {wlcg.MACHINE_NAME: machine, wlcg.VO_NAME: vo, unit: country_vo_aggregate[machine][vo]}
                country_tier_totals.append(r)

        # calculate total per country
        country_totals = _collapseFields(country_tier_totals, ( wlcg.VO_NAME, ) )
        for rec in country_totals:
            rec[wlcg.VO_NAME] = TOTAL

        # calculate total per tier-vo
        tier_vo_totals = _collapseFields(wlcg_records, ( wlcg.MACHINE_NAME, wlcg.COUNTRY ) )
        for r in tier_vo_totals:
            r[wlcg.MACHINE_NAME] = TIER_TOTAL

        # calculate total
        total = _collapseFields(wlcg_records, ( wlcg.MACHINE_NAME, wlcg.VO_NAME, wlcg.COUNTRY ) )
        assert len(total) in (0,1), 'Records did not collapse into a single record when calculating grand total'
        if len(total) == 0:
            total = [ { wlcg.CPU_SECONDS : 0, wlcg.CORE_SECONDS : 0, wlcg.CPU_SECONDS_HS06 : 0, wlcg.CORE_SECONDS_HS06 : 0 } ]
        total_record = total[0]
        total_record[wlcg.MACHINE_NAME] = TIER_TOTAL
        total_record[wlcg.VO_NAME] = TOTAL

        # put all calculated records together and add equivalents
        wlcg_records += site_totals
        wlcg_records += country_tier_totals
        wlcg_records += country_totals
        wlcg_records += tier_vo_totals
        wlcg_records += [ total_record ]

        # create table
        columns = sorted(vo_tiers)
        columns.append(TOTAL)

        row_names = []
        for tld in sorted(tld_groups):
            row_names += sorted(tld_groups[tld])
            row_names.append(tld + '-TOTAL')
        row_names.append(TIER_TOTAL)

        #unit_extractor = WLCG_UNIT_MAPPING.get(unit, WLCG_UNIT_MAPPING_DEFAULT)
        unit_extractor = lambda rec : rec[unit]

        elements = []
        for row in row_names:
            for col in columns:
                for rec in wlcg_records:
                    if rec[wlcg.MACHINE_NAME] == row and rec[wlcg.VO_NAME] == col:
                        value = _formatValue( unit_extractor(rec) )
                        # hurrah for formatting
                        if row == TIER_TOTAL and col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, double_underlined=True)
                        elif (row.endswith('-TOTAL') and col == TOTAL) or row == TIER_TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True, underlined=True)
                        elif row.endswith('-TOTAL') or row == TIER_TOTAL or col == TOTAL:
                            value = htmltable.StyledTableValue(value, bold=True)
                        elements.append( ((col,row), value))
                        break
                else:
                    elements.append( ((col,row), '') )

        matrix = dict(elements)
        table_content = htmltable.createHTMLTable(matrix, columns, row_names, column_labels=COLUMN_NAMES)

        # render page
        start_date_option = request.args.get('startdate', [''])[0]
        end_date_option   = request.args.get('enddate', [''])[0]

        title = 'WLCG oversight view'
        unit_options = []
        for u in self.units:
            unit_options.append(( u, COLUMN_NAMES[u] ))
        unit_buttons = html.createRadioButtons('unit', unit_options, checked_value=unit)
        selector_form = dateform.createMonthSelectorForm(self.path, start_date_option, end_date_option, unit_buttons)

        quarters = dateform.generateFormQuarters()
        quarter_links = []
        for q in quarters:
            year, quart = dateform.parseQuarter(q)
            sd, ed = dateform.quarterStartEndDates(year, quart)
            quarter_links.append(html.createLink('%s?startdate=%s&enddate=%s' % (self.path, sd, ed), q ) )
        range_text = html.createParagraph('Date range: %s - %s (%s days)' % (start_date, end_date, days))

        request.write( html.HTML_VIEWBASE_HEADER % {'title': title} )
        request.write( html.createTitle(title) )
        request.write( html.createParagraph('Quarters: \n    ' + ('    ' + html.NBSP).join(quarter_links) ) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(selector_form) )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph(range_text) )
        request.write( table_content )
        request.write( html.SECTION_BREAK )
        request.write( html.createParagraph('Query time: %s' % round(t_query, 2)) )
        request.write( html.createParagraph('Data process time: %s' % round(t_dataprocess, 2)) )
        request.write( html.HTML_VIEWBASE_FOOTER )

        request.finish()
        return server.NOT_DONE_YET