Author: tmckay Date: 2011-03-09 16:17:14 +0000 (Wed, 09 Mar 2011) New Revision: 4594
Added: branches/scale_testing/cumin/python/cumin/grid/dashboard.py branches/scale_testing/cumin/python/cumin/grid/dashboard.strings Log: Adds for dashboard stuff somehow missed in merge
Added: branches/scale_testing/cumin/python/cumin/grid/dashboard.py =================================================================== --- branches/scale_testing/cumin/python/cumin/grid/dashboard.py (rev 0) +++ branches/scale_testing/cumin/python/cumin/grid/dashboard.py 2011-03-09 16:17:14 UTC (rev 4594) @@ -0,0 +1,881 @@ +import logging + +from wooly import Widget, Attribute +from wooly.util import StringCatalog +from wooly.datatable import DataAdapterOptions, DataAdapterField +from wooly.table import TableHeader +from wooly.widgets import TabbedModeSet + +from cumin.sqladapter import ObjectSqlAdapter +from cumin.stat import StatSet, PieChartPage, StatFlashChart +from rosemary.sqlquery import SqlQueryOptions +from cumin.objectselector import ObjectSelector, MonitorSelfStatColumn, ObjectTableColumn, ObjectTable,\ + ObjectLinkColumn +from cumin.util import rgb_to_string +from negotiator import NegotiatorAttribute + +strings = StringCatalog(__file__) +log = logging.getLogger("cumin.dashboard") + +class PoolDashboard(Widget): + def __init__(self, app, name, collector): + super(PoolDashboard, self).__init__(app, name) + + self.collector = collector + + overview = DashboardOverview(app, "overview", collector) + self.add_child(overview) + + performance = DashboardPerformance(app, "performance", collector) + self.add_child(performance) + + capacity = DashboardCapacity(app, "capacity", collector) + self.add_child(capacity) + + stats = DashboardNegotiatorStatSet(app, "other_stats", collector) + self.add_child(stats) + + def render_title(self, session): + return "Dashboard" + +class DashboardOverview(Widget): + def __init__(self, app, name, collector): + super(DashboardOverview, self).__init__(app, name) + + job_data = DashboardOverviewJobSummary(app, "jobs_summary", collector) + self.add_child(job_data) + + slot_data = GridSlotsSummary(app, "slots_summary", collector) + self.add_child(slot_data) + + os_data = GridOSBreakdown(app, "os_breakdown", collector) + self.add_child(os_data) + + def render_title(self, session): + return "Overview" + +class DashboardPerformance(Widget): + def __init__(self, app, name, collector): + super(DashboardPerformance, self).__init__(app, name) + + cls = app.model.com_redhat_grid.Collector + c = MonitorSelfStats(app, "ms_c", collector, cls, "Collector Performance", None) + self.add_child(c) + + cls = app.model.com_redhat_grid.Negotiator + frame = "main.grid.negotiator" + n = MonitorSelfStats(app, "ms_n", collector, cls, "Negotiator Performance", frame) + self.add_child(n) + + s = MonitorSelfSchedulerTable(app, "ms_s", collector) + self.add_child(s) + + neg_through = NegotiatorJobThroughput(app, "negotiator_throughput", collector) + self.add_child(neg_through) + + neg_cycles = NegotiatorCycles(app, "negotiator_cycles", collector) + self.add_child(neg_cycles) + + neg_slots = NegotiatorSlots(app, "negotiator_slots", collector) + self.add_child(neg_slots) + + def render_title(self, session): + return "Scheduler Performance" + +class DashboardCapacity(Widget): + def __init__(self, app, name, collector): + super(DashboardCapacity, self).__init__(app, name) + + capacity = DashboardCapacitySlotSummary(app, "slot_capacity", collector) + self.add_child(capacity) + + def render_title(self, session): + return "Slot Capacity" + +class DefinitionSet(StatSet): + pass + +# Displays a pie chart and a set of stats as the legend +# The legend is a definition list with the dt being the stat name +# and the dd being the value +# Supports a stat being marked as a "Totals" line, in which case +# the stat isn't put in the pie chart +class PieStatSet(DefinitionSet): + radius = 60 + def __init__(self, app, name, object): + super(PieStatSet, self).__init__(app, name, object) + + self.color_scheme = PieChartPage.RAINBOW + + # determines if a line is drawn under the legend + self.has_total = False + + # if this gets defined, this widget will have a link to display + # the chart in a popup + self.chart = None + + # don't show this if our object isn't there + def do_render(self, session): + object = self.object.get(session) + if object: + return super(PieStatSet, self).do_render(session) + + def get_pie_slices(self, session): + # don't include total lines, convert None to 0 + items = self.do_get_items(session) + return [self.render_item_value(session, x) or 0 for x in items if not self.is_total(x)] + + def render_width(self, session): + # basically hides the pie chart if all slices are 0 + slices = self.get_pie_slices(session) + return sum(slices) and self.radius * 2 or 0 + + def render_height(self, session): + return self.render_width(session) + + def render_pie_src(self, session): + slices = self.get_pie_slices(session) + return PieChartPage.get_href(slices, self.color_scheme, self.radius) + + # we generate the style sheet for the legend based on + # the colors defined in the PieChartPage + def render_legend_styles(self, session): + colors = PieChartPage.color_schemes[self.color_scheme] + styles = list() + for color in colors: + hx = rgb_to_string(*color) + styles.append(".DefinitionSet dt.legend%s span { background-color: #%s;}" % (hx, hx)) + return "\n".join(styles) + + # don't put a line under the legend if there is a total + def render_class(self, session): + cls = super(PieStatSet, self).render_class(session) + return "%s%s" % (cls, self.has_total and " hastotal" or "") + + def is_total(self, item): + return False + + # the color block next to each legend line is based on the class + def render_legend_class(self, session, item): + classes = list() + if self.is_total(item): + classes.append("total") + if self.render_width(session) == 0: + classes.append("blank") + + if not len(classes): + classes.append(self.get_legend_class(session, item)) + return " ".join(classes) + + def render_dd_class(self, session, item): + dt_class = self.render_legend_class(session, item) + return "total" is dt_class and " class="total"" or "" + + # don't show a chart icon on hover if there is no chart + def render_span_class(self, session): + return self.chart and "chart" or "blank" + + def render_chart_href(self, session): + if self.chart: + return self.page.get_popup_url(session, self.chart) + +class AliasSqlColumn(object): + def __init__(self, identifier, alias): + self.identifier = identifier + self.alias = alias + +class MinimalTable(ObjectTable): + def __init__(self, app, name, cls): + super(MinimalTable, self).__init__(app, name, cls) + + # no font or page control + self.header = self.MinimalTableHeader(app, "header") + self.replace_child(self.header) + + # no footer + self.footer = Widget(app, "footer") + self.replace_child(self.footer) + + # no checkboxes or id field + self.adapter = self.MinimalAdapter(app, cls) + + class MinimalAdapter(ObjectSqlAdapter): + def get_id_field(self, cls): + return None + + class MinimalTableHeader(TableHeader): + def __init__(self, app, name): + super(MinimalTable.MinimalTableHeader, self).__init__(app, name) + + self.font = Attribute(app, "font") + self.font.default = 0.9 + self.add_attribute(self.font) + + self.limit = Attribute(app, "limit") + self.limit.default = 25 + self.add_attribute(self.limit) + + self.offset = Attribute(app, "offset") + self.offset.default = 0 + self.add_attribute(self.offset) + +# The following columns and fields should probably be moved +# to a more general location +class DerivedTableColumn(ObjectTableColumn): + def __init__(self, app, name): + super(DerivedTableColumn, self).__init__(app, name, None) + + def init(self): + # avoid ObjectTableColumn's init() since we are setting up our own field + super(ObjectTableColumn, self).init() + + self.field = self.get_field() + + def get_field(self): + raise "Not Implemented" + +class QuotientSqlField(DataAdapterField): + def __init__(self, adapter, name, attr1, attr2, title=None): + super(QuotientSqlField, self).__init__(adapter, name, float) + + self.title = title and title or name + + # attr1 and 2 could be strings or they could be rosemary attributes + try: + id1 = attr1.sql_column.identifier + except AttributeError: + id1 = attr1 + try: + id2 = attr2.sql_column.identifier + except AttributeError: + id2 = attr2 + + # avoid divide by 0 by using nullif + nullif = "NULLIF(%s,0)" % id2 + identifier = "(%s/%s)" % (id1, nullif) + + # we need an alias if we sort on this column + alias = self.name + self.column = AliasSqlColumn(identifier, alias) + + self.adapter.columns.append(self.column) + self.format = "%0.02f%%" + + def get_title(self, session): + return self.title + +class SumSqlField(DataAdapterField): + def __init__(self, adapter, name, attr, title=None): + super(SumSqlField, self).__init__(adapter, name, float) + + self.title = title and title or name + + identifier = "sum(%s)" % (attr.sql_column.identifier) + + # we need an alias if we sort on this column + alias = self.name + self.column = AliasSqlColumn(identifier, alias) + + self.adapter.columns.append(self.column) + + def get_title(self, session): + return self.title + +# Create a sortable column that is the quotient of two extisting columns +class QuotientSqlColumn(DerivedTableColumn): + def __init__(self, app, name, attr1, attr2): + super(QuotientSqlColumn, self).__init__(app, name) + + self.attr1 = attr1 + self.attr2 = attr2 + self.title = name + + def get_field(self): + return QuotientSqlField(self.table.adapter, self.name, self.attr1, self.attr2, self.title) + + def init(self): + super(QuotientSqlColumn, self).init() + + # change quotient into a percent + self.field.column.identifier = "%s*100" % self.field.column.identifier + +# Create a sortable column that is the sum of two extisting columns +class SumSqlColumn(DerivedTableColumn): + def __init__(self, app, name, attr, title): + super(SumSqlColumn, self).__init__(app, name) + + self.attr = attr + self.title = title + self.format_method = getattr(attr, "unit", None) + + def get_field(self): + field = SumSqlField(self.table.adapter, self.name, self.attr, self.title) + if self.format_method: + if self.format_method == "MiB": + format_method = self.fmt_mb + elif self.format_method == "KiB": + format_method = self.fmt_kb + else: + format_method = self.fmt_b + + field.format = format_method + return field + +class DashboardSumData(ObjectSqlAdapter): + def __init__(self, app, columns, collector, cls): + super(DashboardSumData, self).__init__(app, cls) + + self.collector = collector + self.add_value_filter(cls.Pool) + + for column in columns: + col = "sum("%s")" % column + self.columns.append(col) + # add a count field so we can calculate averages + self.columns.append("count(1)") + + def get_id_field(self, cls): + return None + + def get_record(self, session): + values = dict() + obj = self.collector.get(session) + values[self.cls.Pool.name] = getattr(obj, self.cls.Pool.name) + + options = DataAdapterOptions() + options.offset = 0 + + records = self.get_data(values, options) + + return records[0] + +class DashboardSummarySet(PieStatSet): + def __init__(self, app, name, collector, cls, columns): + super(DashboardSummarySet, self).__init__(app, name, collector) + + self.record = Attribute(app, "totals") + self.add_attribute(self.record) + + # XXX - consider using an ObjectSelector with SumSqlColumns as the data source + self.columns = columns + self.sum_columns = [x.name for x in columns] + self.sum_cls = cls + self.data = DashboardSumData(app, self.sum_columns, collector, self.sum_cls) + + self.has_total = True + self.color_scheme = PieChartPage.BLUES + + def do_process(self, session): + record = self.data.get_record(session) + self.record.set(session, record) + + super(DashboardSummarySet, self).do_process(session) + + def do_get_items(self, session): + return self.columns + + def is_total(self, item): + index = self.sum_columns.index(item.name) + return index == len(self.sum_columns) - 1 + + def render_item_title(self, session, item): + return item.title + + def render_item_value(self, session, item): + record = self.record.get(session) + return record[self.sum_columns.index(item.name)] or 0 + + def render_html_title(self, session, item): + return item.description + + def get_legend_class(self, session, item): + index = self.sum_columns.index(item.name) + return "legend%s" % rgb_to_string(*(PieChartPage.color_schemes[self.color_scheme][index])) + +class DashboardOverviewJobSummary(DashboardSummarySet): + def __init__(self, app, name, collector): + cls = app.model.com_redhat_grid.Scheduler + columns = [cls.TotalRunningJobs, cls.TotalHeldJobs, cls.TotalIdleJobs, + cls.TotalRemovedJobs, cls.TotalJobAds] + + super(DashboardOverviewJobSummary, self).__init__(app, name, collector, cls, columns) + + self.color_scheme = PieChartPage.GREENS + + def render_title(self, session): + return "Job Summary Info" + +class DashboardCapacitySlotSummary(ObjectSelector): + def __init__(self, app, name, collector): + cls = app.model.com_redhat_grid.Slot + super(DashboardCapacitySlotSummary, self).__init__(app, name, cls) + + self.add_filter(collector, cls.Pool) + + stat = self.DiskTotalColumn(app, "disk", cls.Disk, "Disk Total") + stat.static_header = True + self.add_column(stat) + + stat = SumSqlColumn(app, "slot_mem_used", cls.ImageSize, "Memory Used") + stat.static_header = True + self.add_column(stat) + stat = SumSqlColumn(app, "memtotal", cls.Memory, "Memory Total") + stat.static_header = True + self.add_column(stat) + stat = QuotientSqlColumn(app, "percentmem", + "sum(%s)" % cls.ImageSize.sql_column.identifier, + "sum(%s)*1024" % cls.Memory.sql_column.identifier) + stat.static_header = True + stat.title = "% Memory Used" + self.add_column(stat) + + self.enable_csv_export(collector) + + def create_table(self, app, name, cls): + # avoid the checkboxes + return MinimalTable(app, name, cls) + + class DiskTotalColumn(SumSqlColumn): + def render_text_align(self, session): + return "left" + +class GridSlotsSummary(PieStatSet): + def __init__(self, app, name, collector): + super(GridSlotsSummary, self).__init__(app, name, collector) + + self.attrs = ["HostsClaimed", "HostsUnclaimed", + "HostsOwner", "HostsTotal"] + + self.has_total = True + self.color_scheme = PieChartPage.BLUES + + self.chart = StatFlashChart(app, "popup", collector) + self.chart.stats = self.attrs + self.add_child(self.chart) + + def render_title(self, session): + return "Host Summary Info" + + def is_total(self, item): + index = self.attrs.index(item[0].name) + return index == len(self.attrs) - 1 + + def get_legend_class(self, session, item): + index = self.attrs.index(item[0].name) + return "legend%s" % rgb_to_string(*(PieChartPage.color_schemes[self.color_scheme][index])) + +class DashboardOSData(ObjectSqlAdapter): + def __init__(self, app, collector): + cls = app.model.com_redhat_grid.Slot + super(DashboardOSData, self).__init__(app, cls) + + self.collector = collector + self.add_value_filter(cls.Pool) + + self.sum_column = ""OpSys"" + + col = "count(%s)" % self.sum_column + self.columns.append(self.sum_column) + self.columns.append(col) + + self.act_state_column = AliasSqlColumn(""Activity"||"State"", "actstate") + self.columns.append(self.act_state_column) + + def get_records(self, session): + values = dict() + obj = self.collector.get(session) + values[self.cls.Pool.name] = getattr(obj, self.cls.Pool.name) + + options = SqlQueryOptions() + options.sort_column = self.sum_column + options.group_column = ",".join((self.sum_column, self.act_state_column.identifier)) + + records = self.get_data(values, options) + + # accumulate records by os/used|unused manually + recs_by_os = dict() + for record in records: + os, count, act_state = record + if os not in recs_by_os: + recs_by_os[os] = dict() + recs_by_os[os]["total"] = 0 + recs_by_os[os]["used"] = 0 + recs_by_os[os]["unused"] = 0 + recs_by_os[os]["total"] += count + if act_state == "IdleUnclaimed": + recs_by_os[os]["unused"] += count + else: + recs_by_os[os]["used"] += count + + records = list() + for os in recs_by_os: + records.append(("%s Used" % os, recs_by_os[os]["used"])) + records.append(("%s Unused" % os, recs_by_os[os]["unused"])) + records.append(("%s Total" % os, recs_by_os[os]["total"])) + + ########## dummy test data + """ + records.append(("WINNT80 Used", 10)) + records.append(("WINNT80 Unused", 20)) + records.append(("WINNT80 Total", 30)) + records.append(("SOLARIS27 Used", 12)) + records.append(("SOLARIS27 Unused", 34)) + records.append(("SOLARIS27 Total", 46)) + """ + + return records + + def get_sql_options(self, options): + return options + + def get_id_field(self, cls): + return None + +class GridOSBreakdown(PieStatSet): + def __init__(self, app, name, collector): + super(GridOSBreakdown, self).__init__(app, name, collector) + + self.os_records = Attribute(app, "os_breakdown") + self.add_attribute(self.os_records) + + self.os_data = DashboardOSData(app, collector) + + self.color_scheme = PieChartPage.GROUP2 + self.has_total = True + + def do_process(self, session): + os_records = self.os_data.get_records(session) + self.os_records.set(session, os_records) + + super(GridOSBreakdown, self).do_process(session) + + def do_render(self, session): + records = self.os_records.get(session) + if len(records): + return super(GridOSBreakdown, self).do_render(session) + + def render_title(self, session): + return "Slot Breakdown by OS" + + def do_get_items(self, session): + return [(i, x) for i, x in enumerate(self.os_records.get(session))] + + def is_total(self, item): + _, record = item + return "Total" in record[0] + + def render_item_title(self, session, item): + _, record = item + return record[0] + + def render_item_value(self, session, item): + _, record = item + return record[1] + + def render_html_title(self, session, item): + return None + + def get_legend_class(self, session, this_item): + items = self.do_get_items(session) + real_index = 0 + for item in items: + if item[1][0] is this_item[1][0]: + break + if not self.is_total(item): + real_index += 1 + return "legend%s" % rgb_to_string(*(PieChartPage.color_schemes[self.color_scheme][real_index])) + +class NegotiatorDefinitionStatSet(PieStatSet): + def __init__(self, app, name, collector): + negotiator = NegotiatorAttribute(app, "neg", collector) + super(NegotiatorDefinitionStatSet, self).__init__(app, name, negotiator) + self.add_attribute(negotiator) + + self.chart = StatFlashChart(app, "popup", negotiator) + self.add_child(self.chart) + + def is_total(self, item): + if not self.has_total: + return False + index = self.attrs.index(item[0].name) + return index == len(self.attrs) - 1 + + def get_legend_class(self, session, item): + index = self.attrs.index(item[0].name) + return "legend%s" % rgb_to_string(*(PieChartPage.color_schemes[self.color_scheme][index])) + + def render_item_value(self, session, item): + _, value = item + if value is None: + return 0 + return super(NegotiatorDefinitionStatSet, self).render_item_value(session, item) + +class NegotiatorJobThroughput(NegotiatorDefinitionStatSet): + def __init__(self, app, name, collector): + super(NegotiatorJobThroughput, self).__init__(app, name, collector) + + self.attrs = ["NumJobsConsidered", "Rejections"] + self.chart.stats = self.attrs + + self.has_total = False + self.color_scheme = PieChartPage.RAINBOW + + def render_title(self, session): + return "Negotiator Job Throughput" + + +class NegotiatorCycles(NegotiatorDefinitionStatSet): + def __init__(self, app, name, collector): + super(NegotiatorCycles, self).__init__(app, name, collector) + + self.attrs = ["Phase1Duration", "Phase2Duration", "Phase3Duration", "Phase4Duration", "Duration"] + self.chart.stats = self.attrs + + self.has_total = True + self.color_scheme = PieChartPage.RAINBOW + + def render_title(self, session): + return "Negotiator Cycles" + +class NegotiatorSlots(NegotiatorDefinitionStatSet): + def __init__(self, app, name, collector): + super(NegotiatorSlots, self).__init__(app, name, collector) + + self.attrs = ["TrimmedSlots", "CandidateSlots", "TotalSlots"] + self.chart.stats = self.attrs + + self.has_total = True + + def render_title(self, session): + return "Negotiator Slots" + + def render_width(self, session): + # disable the pie chart since these stats don't add up to a total + return 0 + +class NegotiatorSlotsChart(Widget): + def __init__(self, app, name, pool): + super(NegotiatorSlotsChart, self).__init__(app, name) + + self.pool = pool + cls = app.model.com_redhat_grid.Negotiator + self.object = RosemaryPoolObjectAttribute(app, "obj", pool, cls) + self.add_attribute(self.object) + + slots = self.SlotsChart(app, "slots", self.object) + slots.stats = ("TotalSlots", "TrimmedSlots", "CandidateSlots") + self.add_child(slots) + + def do_render(self, session): + object = self.object.get(session) + if object: + return super(NegotiatorSlotsChart, self).do_render(session) + + class SlotsChart(StatFlashChart): + def render_title(self, session, *args): + return "Slots Precessed" + +class MonitorSelfSchedulerTable(Widget): + def __init__(self, app, name, pool): + super(MonitorSelfSchedulerTable, self).__init__(app, name) + + cls = app.model.com_redhat_grid.Scheduler + frame = "main.grid.scheduler" + + self.table = MonitorSelfTable(app, "current", pool, cls, frame, False) + self.add_child(self.table) + + def render_title(self, session): + return "Scheduler Performance" + + def render_table(self, session): + return self.table.render(session) + +class MonitorSelfStats(Widget): + def __init__(self, app, name, pool, cls, title, frame): + super(MonitorSelfStats, self).__init__(app, name) + + self.pool = pool + self.cls = cls + self.frame = frame + + self.object = RosemaryPoolObjectAttribute(app, "obj", pool, cls) + self.add_attribute(self.object) + + title_widget = self.MonitorSelfTitle(app, "title", self.object, title) + self.add_child(title_widget) + + tabs = TabbedModeSet(app, "tabs") + self.add_child(tabs) + + self.chart = MonitorSelfCharts(app, "history", pool, cls, self.object) + tabs.add_tab(self.chart) + tabs.add_tab(MonitorSelfTable(app, "current", pool, cls, frame, True)) + + def do_render(self, session): + if self.object.get(session): + return super(MonitorSelfStats, self).do_render(session) + + class MonitorSelfTitle(Widget): + def __init__(self, app, name, object, title): + super(MonitorSelfStats.MonitorSelfTitle, self).__init__(app, name) + + self.object = object + self.title = title + + def render_title(self, session): + return self.title + +class MonitorSelfTable(ObjectSelector): + def __init__(self, app, name, pool, cls, frame, static): + super(MonitorSelfTable, self).__init__(app, name, cls) + + self.pool = pool + self.add_filter(self.pool, cls.Pool) + + # Do we want the name column to link to the objects page? + # In the case of collector we don't + if frame: + col = ObjectLinkColumn(app, "name", cls.Name, cls._id, frame) + else: + col = ObjectTableColumn(app, cls.Name.name, cls.Name) + col.static_header = static + self.add_column(col) + + stat = MonitorSelfStatColumn(app, cls.MonitorSelfAge.name, cls.MonitorSelfAge) + stat.static_header = True + self.add_column(stat) + + stat = MonitorSelfStatColumn(app, cls.MonitorSelfRegisteredSocketCount.name, cls.MonitorSelfRegisteredSocketCount) + stat.static_header = True + self.add_column(stat) + + stat = MonitorSelfStatColumn(app, cls.MonitorSelfCPUUsage.name, cls.MonitorSelfCPUUsage) + stat.static_header = True + self.add_column(stat) + + stat = MonitorSelfStatColumn(app, cls.MonitorSelfResidentSetSize.name, cls.MonitorSelfResidentSetSize) + stat.static_header = True + self.add_column(stat) + + stat = MonitorSelfStatColumn(app, cls.MonitorSelfImageSize.name, cls.MonitorSelfImageSize) + stat.static_header = True + self.add_column(stat) + + stat = QuotientSqlColumn(app, "used", cls.MonitorSelfResidentSetSize, + cls.MonitorSelfImageSize) + stat.static_header = True + stat.title = "% Used" + self.add_column(stat) + + self.enable_csv_export(pool) + + def create_table(self, app, name, cls): + # avoid the checkboxes + return MinimalTable(app, name, cls) + + def render_title(self, session): + return "Current" + +class MonitorSelfCharts(Widget): + def __init__(self, app, name, pool, cls, object): + super(MonitorSelfCharts, self).__init__(app, name) + + self.pool = pool + self.cls = cls + + cpu = self.CPUFlashChart(app, "cpu", object) + cpu.stats = ("MonitorSelfCPUUsage",) + self.add_child(cpu) + + used = self.MemoryFlashChart(app, "used", object) + used.chart_type = "percent" + used.stats = ("MonitorSelfResidentSetSize",) + self.add_child(used) + + def render_title(self, session): + return "History" + + class CPUFlashChart(StatFlashChart): + def render_title(self, session, *args): + return "CPU Usage" + + class MemoryFlashChart(StatFlashChart): + def render_title(self, session, *args): + return "Memory Usage" + + def get_href_params(self, session): + params = super(MonitorSelfCharts.MemoryFlashChart, self).get_href_params(session) + params.append("tp=MonitorSelfImageSize") + + return params + +class RosemaryPoolObjectAttribute(Attribute): + def __init__(self, app, name, pool, cls): + super(RosemaryPoolObjectAttribute, self).__init__(app, name) + + self.pool = pool + self.cls = cls + + def get(self, session): + obj = self.pool.get(session) + return self.cls.get_object(session.cursor, Pool=obj.Pool) + +class DashboardNegotiatorStatSet(Widget): + def __init__(self, app, name, collector): + super(DashboardNegotiatorStatSet, self).__init__(app, name) + + self.stats = self.NegotiatorStatSet(app, "stats", collector) + self.add_child(self.stats) + + def do_render(self, session): + object = self.stats.object.get(session) + if object: + return super(DashboardNegotiatorStatSet, self).do_render(session) + + def render_title(self, session): + return "Other Negotiator Statistics" + + class NegotiatorStatSet(StatSet): + def __init__(self, app, name, collector): + negotiator = NegotiatorAttribute(app, "neg", collector) + super(DashboardNegotiatorStatSet.NegotiatorStatSet, self).__init__(app, name, negotiator) + self.add_attribute(negotiator) + + self.attrs = ("MatchRate", "MatchRateSustained", "Matches", + "SlotShareIter", "NumSchedulers", "ActiveSubmitterCount", "NumIdleJobs", + "SubmittersFailed", "SubmittersOutOfTime", "SubmittersShareLimit", + "Time", "End", "Period") + + # create a chart for each statistic + # I wonder if there is a way to use a single instance + # of the StatFlashChart class to do this + cls = app.model.com_redhat_grid.Negotiator + self.popup_charts = dict() + for attr in self.attrs: + # don't try and chart the strings + if cls._statistics_by_name[attr].type != "lstr": + chart = StatFlashChart(app, "popup%s" % attr, self.object) + chart.stats = [attr] + self.popup_charts[attr] = chart + self.add_child(chart) + + def render_chart_href(self, session, item): + stat, _ = item + try: + chart = self.popup_charts[stat.name] + except KeyError: + # some of the attrs were not added to the chart list + return + + return self.page.get_popup_url(session, chart) + + def render_chart_class(self, session, item): + stat, _ = item + return stat.name in self.popup_charts and "chart" or "blank" + + def render_span_title(self, session, item): + stat, _ = item + return stat.name in self.popup_charts and \ + "Click to show a chart of this statistic" or \ + ""
Added: branches/scale_testing/cumin/python/cumin/grid/dashboard.strings =================================================================== --- branches/scale_testing/cumin/python/cumin/grid/dashboard.strings (rev 0) +++ branches/scale_testing/cumin/python/cumin/grid/dashboard.strings 2011-03-09 16:17:14 UTC (rev 4594) @@ -0,0 +1,221 @@ +[PoolDashboard.html] + {overview} + <div style="clear:both;"></div> + {performance} + <div style="clear:both; padding-top:2em;"></div> + {capacity} + <div style="clear:both;"></div> + {other_stats} + <div style="clear:both;"></div> + +[DashboardOverview.html] + {os_breakdown} + {slots_summary} + {jobs_summary} + +[DashboardPerformance.css] +div.DashboardPerformance { + width: 1000px; +} + +div.DashboardPerformance div.TabbedModeSet { + min-height: 6em; +} + +[DashboardPerformance.html] + {negotiator_throughput} + {negotiator_cycles} + {negotiator_slots} +<div id="{id}" class="{class}"> + {ms_s} + {ms_c} + {ms_n} +</div> + +[MonitorSelfTitle.html] +<h3>{title}</h3> + +[MonitorSelfSchedulerTable.css] +div.MonitorSelfSchedulerTable { + margin-bottom: 3em; +} + +[MonitorSelfSchedulerTable.html] +<div id="{id}" class="{class}"> + <h3>{title}</h3> + {table} +</div> + +[DashboardCapacity.css] +div.DashboardCapacity { + width: 1000px; +} +[DashboardCapacity.html] +<div id="{id}" class="{class}"> +<h3>{title}</h3> + {slot_capacity} +</div> + +[DefinitionSet.css] +div.DefinitionSet { + float: left; + margin-right: 2em; +} + +div.DefinitionSet h3 { + margin-top: 0.5em; +} + +.DefinitionSet dl { + border-bottom: 1px solid #999999; + clear: left; + float: right; + margin: 0 0 1em; + padding: 0; +} +.DefinitionSet.hastotal dl { + border-bottom: 0px; +} + +.DefinitionSet img { + margin-right: 1em; + float: left; +} + +.DefinitionSet dt { + clear: left; + float: left; + width: 15em; + margin: 0; + padding: 5px; + border-top: 1px solid #999; +} + +.DefinitionSet dt.total, .DefinitionSet dd.total { + font-weight: bold; + padding-bottom: 1em; +} + +.DefinitionSet span { + display: block; + float: left; + width: 1em; + height: 1em; + margin-right: 0.33em; + position: relative; + top: 0.1em; +} + +.DefinitionSet dt.blank span { + width: 0; +} + +.DefinitionSet dd { + float: left; + width: 3em; + margin: 0; + padding: 5px; + border-top: 1px solid #999; +} + +.DefinitionSet h3 span { + display: block; + float: right; + width: 20px; + height: 15px; + background: url(resource?name=chart-20.png) repeat; + margin-right:1em; + visibility: hidden; +} + +.DefinitionSet:hover h3 span { + cursor: pointer; + visibility: visible; +} + +.DefinitionSet:hover h3 span.blank { + cursor: default; + visibility: hidden; +} + +/* hide the chart title since we have one in the popup title bar */ +div.DefaultStickyWin div.StatValueChart h2 { + display: none; +} + +[DefinitionSet.html] +<div id="{id}" class="{class}"> +<style type="text/css"> +{legend_styles} +</style> + <h3>{title}<span class="{span_class}" title="Click to show a timeseries chart" onclick="showHistoryChart('{title}', '{chart_href}')"></span></h3> + <dl> + {items} + </dl> + <img src="{pie_src}" alt="pie_chart" title="{title}" width="{width}" height="{height}"/> +</div> + +[DefinitionSet.item_html] + <dt title="{html_title}" class="{legend_class}"><span></span>{item_title}</dt> + <dd{dd_class}>{item_value}</dd> + + +[MinimalTableHeader.html] +<thead> + <tr>{headers}</tr> +</thead> + +[MonitorSelfCharts.css] +div.MonitorSelfCharts div.chart { + float: left; +} + +[MonitorSelfCharts.html] +<div id="{id}" class="{class}"> + <div class="chart">{cpu}</div><div class="chart">{used}</div> + <div style="clear:both;"></div> +</div> + +[DashboardNegotiatorStatSet.css] +div.DashboardNegotiatorStatSet { + width: 1000px; +} + +div.DashboardNegotiatorStatSet th span { + display: block; + float: left; + width: 20px; + height: 15px; + background: url(resource?name=chart-20.png) repeat; + margin-right:1em; + visibility: hidden; +} + +div.DashboardNegotiatorStatSet tr.item:hover th span { + cursor: pointer; + visibility: visible; +} + +div.DashboardNegotiatorStatSet tr.item:hover th span.blank { + cursor: default; + visibility: hidden; +} +.mask { + z-index: 500; +} + +div.DashboardNegotiatorStatSet tr.item:hover { + background-color: #E7E7F7; +} + +[DashboardNegotiatorStatSet.html] +<div class="{class}"> + <h3>{title}</h3> + {stats} +</div> + +[NegotiatorStatSet.item_html] +<tr title="{html_title}" class="item"> + <th><span class="{chart_class}" onclick="showHistoryChart('{item_title}', '{chart_href}')" title="{span_title}"></span>{item_title}</th> + <td class="ralign"> {item_value}</td> +</tr>
cumin-developers@lists.fedorahosted.org