Пример #1
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 oversight view', subject)

        # access allowed
        start_date, end_date = dateform.parseStartEndDates(request)

        # set dates if not specified (defaults are current month, which is not what we want)
        year, quart = dateform.currentYearQuart()
        if not 'startdate' in request.args or request.args['startdate'] == ['']:
            start_date, _ = dateform.quarterStartEndDates(year, quart)
        if not 'enddate' in request.args or request.args['enddate'] == ['']:
            _, end_date = dateform.quarterStartEndDates(year, quart)
        if 'unit' in request.args and request.args['unit'][0] not in WLCG_UNIT_MAPPING:
            return self.renderErrorPage('Invalid units parameters')
        unit = request.args.get('unit', ['ksi2k-ne'])[0]

        t_query_start = time.time()
        d = self.retrieveWLCGData(start_date, end_date)
        d.addCallback(self.renderWLCGViewPage, request, start_date, end_date, unit, t_query_start)
        d.addErrback(self.renderErrorPage, request)
        return server.NOT_DONE_YET
Пример #2
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 oversight view', subject)

        # access allowed
        start_date, end_date = dateform.parseStartEndDates(request)

        # set dates if not specified (defaults are current month, which is not what we want)
        year, quart = dateform.currentYearQuart()
        if not 'startdate' in request.args or request.args['startdate'] == ['']:
            start_date, _ = dateform.quarterStartEndDates(year, quart)
        if not 'enddate' in request.args or request.args['enddate'] == ['']:
            _, end_date = dateform.quarterStartEndDates(year, quart)
        if 'unit' in request.args and request.args['unit'][0] not in WLCG_UNIT_MAPPING:
            return self.renderErrorPage('Invalid units parameters')
        unit = request.args.get('unit', ['ksi2k-ne'])[0]

        t_query_start = time.time()
        d = self.retrieveWLCGData(start_date, end_date)
        d.addCallback(self.renderWLCGViewPage, request, start_date, end_date, unit, t_query_start)
        d.addErrback(self.renderErrorPage, request)
        return server.NOT_DONE_YET
Пример #3
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
Пример #4
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
Пример #5
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
Пример #6
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