Author: eallen
Date: 2011-03-02 22:36:27 +0000 (Wed, 02 Mar 2011)
New Revision: 4565
Added:
trunk/cumin/python/cumin/grid/dashboard.py
trunk/cumin/python/cumin/grid/dashboard.strings
Modified:
trunk/cumin/model/condor.xml
trunk/cumin/model/rosemary.xml
trunk/cumin/python/cumin/charts.py
trunk/cumin/python/cumin/formats.py
trunk/cumin/python/cumin/grid/pool.py
trunk/cumin/python/cumin/grid/pool.strings
trunk/cumin/python/cumin/main.py
trunk/cumin/python/cumin/objectselector.py
trunk/cumin/python/cumin/qmfadapter.py
trunk/cumin/python/cumin/sqladapter.py
trunk/cumin/python/cumin/stat.py
trunk/cumin/python/cumin/util.py
trunk/cumin/python/cumin/widgets.strings
Log:
1st rough cut at grid dashboard with lots of data still missing
Modified: trunk/cumin/model/condor.xml
===================================================================
--- trunk/cumin/model/condor.xml 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/model/condor.xml 2011-03-02 22:36:27 UTC (rev 4565)
@@ -35,9 +35,9 @@
<statistic name="MonitorSelfAge" type="uint32"/>
<statistic name="MonitorSelfCPUUsage" type="double"/>
- <statistic name="MonitorSelfImageSize" type="double"/>
+ <statistic name="MonitorSelfImageSize" type="double" unit="KiB"/>
<statistic name="MonitorSelfRegisteredSocketCount" type="uint32"/>
- <statistic name="MonitorSelfResidentSetSize" type="uint32"/>
+ <statistic name="MonitorSelfResidentSetSize" type="uint32" unit="KiB"/>
<statistic name="MonitorSelfTime" type="absTime"/>
</group>
Modified: trunk/cumin/model/rosemary.xml
===================================================================
--- trunk/cumin/model/rosemary.xml 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/model/rosemary.xml 2011-03-02 22:36:27 UTC (rev 4565)
@@ -302,19 +302,19 @@
</object>
<statistic name="HostsClaimed">
- <title>Claimed Slots</title>
+ <title>Claimed Hosts</title>
</statistic>
<statistic name="HostsUnclaimed">
- <title>Unclaimed Slots</title>
+ <title>Unclaimed Hosts</title>
</statistic>
<statistic name="HostsOwner">
- <title>Unavailable Slots</title>
+ <title>Unavailable Hosts</title>
</statistic>
<statistic name="HostsTotal">
- <title>Total Slots</title>
+ <title>Total Hosts</title>
</statistic>
</class>
Modified: trunk/cumin/python/cumin/charts.py
===================================================================
--- trunk/cumin/python/cumin/charts.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/charts.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -10,6 +10,72 @@
log = logging.getLogger("cumin.chart")
+class CuminPieChart(object):
+ def __init__(self, radius=400):
+ self.width = radius * 2
+ self.height = self.width
+ self.centerx = radius
+ self.centery = radius
+ self.radius = radius
+
+ def plot_pie(self, slices, colors):
+ surface = ImageSurface(FORMAT_ARGB32, int(self.width), int(self.height))
+ cr = Context(surface)
+ cr.set_line_width(2)
+
+ total = float(sum(slices))
+
+ wedge = cum_wedges = end = 0.0
+
+ for slice, color in zip(slices, colors):
+ if slice > 0:
+ wedge = slice / total
+
+ start = end
+ end = (cum_wedges + wedge) * 2 * pi
+ cum_wedges += wedge
+
+ # draw the wedge
+ self.draw_slice_path(cr, self.centerx, self.centery,
+ self.radius, start, end)
+ cr.set_source_rgb(*color)
+ cr.fill()
+
+ # draw a white border
+ self.draw_slice_path(cr, self.centerx, self.centery,
+ self.radius, start, end)
+ cr.set_source_rgb(1,1,1)
+ cr.stroke()
+
+ return surface
+
+ def draw_slice_path(self, cr, x, y, radius, start, end):
+ cr.new_path()
+ cr.move_to(x, y)
+ cr.arc(x, y, radius, start, end)
+ cr.close_path()
+
+ def test(self):
+ surface = ImageSurface(FORMAT_ARGB32, int(self.width), int(self.height))
+ cr = Context(surface)
+ cr.set_line_width(1)
+
+ cr.save()
+ cr.set_source_rgba(0, 0, 0, 0.15)
+
+ centerx = int(self.width/2)
+ centery = int(self.height/2)
+ radius = int(self.width/2)
+ cr.new_path()
+ cr.move_to(centerx, centery)
+ cr.arc(centerx, centery, radius, 0, pi * 2)
+ cr.line_to(centerx, centery)
+ cr.close_path()
+ cr.fill()
+ cr.restore()
+
+ return surface
+
class HeatMapChart(object):
def __init__(self, width=400, height=400):
self.width = width # final width
Modified: trunk/cumin/python/cumin/formats.py
===================================================================
--- trunk/cumin/python/cumin/formats.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/formats.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -171,7 +171,8 @@
which_size += 1
if which_size < len(sizes):
- return "%.0d%s%s" % (floor(b), sizes[which_size], base)
+ if b:
+ return "%0.0d%s%s" % (floor(b), sizes[which_size], base)
return str(num)
def fmt_bits(bits):
Added: trunk/cumin/python/cumin/grid/dashboard.py
===================================================================
--- trunk/cumin/python/cumin/grid/dashboard.py (rev 0)
+++ trunk/cumin/python/cumin/grid/dashboard.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -0,0 +1,493 @@
+import logging
+
+from wooly import Widget, Attribute
+from wooly.util import StringCatalog
+from wooly.datatable import DataAdapterOptions, DataAdapterField
+
+from cumin.sqladapter import ObjectSqlAdapter
+from cumin.stat import StatSet, PieChartPage
+from rosemary.sqlquery import SqlQueryOptions
+from cumin.objectselector import ObjectSelector, ObjectLinkColumn,\
+ MonitorSelfStatColumn, ObjectTableColumn, ObjectTable
+from wooly.table import TableHeader
+from cumin.util import rgb_to_string
+
+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)
+
+ 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)
+
+ # scheduled vs all jobs
+ throughput = Widget(app, "jobs_summary")
+ self.add_child(throughput)
+
+ #collector_perf = DashboardCollectorSelector(app, "collector_performance", collector)
+ #self.add_child(collector_perf)
+
+ negotiator = DashboardNegotiatorSelector(app, "negotiator_performance", collector)
+ self.add_child(negotiator)
+
+ sched = DashboardSchedulerSelector(app, "scheduler_performance", collector)
+ self.add_child(sched)
+
+ 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):
+ radius = 60
+ def __init__(self, app, name, object):
+ super(DefinitionSet, self).__init__(app, name, object)
+
+ self.color_scheme = PieChartPage.RAINBOW
+ self.has_total = False
+
+ def render_width(self, session):
+ return self.radius * 2
+
+ def render_height(self, session):
+ return self.radius * 2
+
+ def render_pie_src(self, session):
+ items = list(self.do_get_items(session))
+ if self.has_total and len(items):
+ del items[len(items)-1]
+ slices = [self.render_item_value(session, x) for x in items]
+ return PieChartPage.get_href(slices, self.color_scheme, self.radius)
+
+ 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 span.legend%s { background-color: #%s;}" % (hx, hx))
+ return "\n".join(styles)
+
+ def render_class(self, session):
+ cls = super(DefinitionSet, self).render_class(session)
+ return "%s%s" % (cls, self.has_total and " hastotal" or "")
+
+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)
+
+ self.header = self.MinimalTableHeader(app, "header")
+ self.replace_child(self.header)
+
+ self.footer = Widget(app, "footer")
+ self.replace_child(self.footer)
+
+ self.adapter = self.MinimalAdapter(app, cls)
+
+ class MinimalAdapter(ObjectSqlAdapter):
+ def get_id_field(self, cls):
+ return None
+
+ def get_data(self, values, options):
+ return super(MinimalTable.MinimalAdapter, self).get_data(values, options)
+
+ 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)
+
+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
+
+ 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
+
+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
+
+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 DashboardObjectSelector(ObjectSelector):
+ def __init__(self, app, name, collector, cls, frame):
+
+ super(DashboardObjectSelector, self).__init__(app, name, cls)
+
+ self.pool = collector
+
+ self.add_filter(self.pool, cls.Pool)
+
+ col = ObjectLinkColumn(app, "name", cls.Name, cls._id, frame)
+ self.add_column(col)
+
+ stat = MonitorSelfStatColumn(app, cls.MonitorSelfCPUUsage.name, cls.MonitorSelfCPUUsage)
+ self.add_column(stat)
+
+ stat = MonitorSelfStatColumn(app, cls.MonitorSelfResidentSetSize.name, cls.MonitorSelfResidentSetSize)
+ self.add_column(stat)
+
+ stat = MonitorSelfStatColumn(app, cls.MonitorSelfImageSize.name, cls.MonitorSelfImageSize)
+ self.add_column(stat)
+
+ stat = QuotientSqlColumn(app, "used", cls.MonitorSelfResidentSetSize,
+ cls.MonitorSelfImageSize)
+ stat.title = "% 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 DashboardCollectorSelector(DashboardObjectSelector):
+ def __init__(self, app, name, collector):
+ cls = app.model.com_redhat_grid.Collector
+ frame = "main.grid"
+
+ super(DashboardCollectorSelector, self).__init__(app, name, collector, cls, frame)
+
+class DashboardNegotiatorSelector(DashboardObjectSelector):
+ def __init__(self, app, name, collector):
+ cls = app.model.com_redhat_grid.Negotiator
+ frame = "main.grid.negotiator"
+
+ super(DashboardNegotiatorSelector, self).__init__(app, name, collector, cls, frame)
+
+class DashboardSchedulerSelector(DashboardObjectSelector):
+ def __init__(self, app, name, collector):
+ cls = app.model.com_redhat_grid.Scheduler
+ frame = "main.grid.scheduler"
+
+ super(DashboardSchedulerSelector, self).__init__(app, name, collector, cls, frame)
+
+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(DefinitionSet):
+ 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)
+
+ 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_get_items(self, session):
+ return self.columns
+
+ def do_process(self, session):
+ record = self.data.get_record(session)
+ self.record.set(session, record)
+
+ super(DashboardSummarySet, self).do_process(session)
+
+ 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)]
+
+ def render_legend_class(self, session, item):
+ index = self.sum_columns.index(item.name)
+ if index == len(self.sum_columns) - 1:
+ return "blank"
+ 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")
+ self.add_column(stat)
+
+ stat = SumSqlColumn(app, "slot_mem_used", cls.ImageSize, "Memory Used")
+ self.add_column(stat)
+ stat = SumSqlColumn(app, "memtotal", cls.Memory, "Memory Total")
+ self.add_column(stat)
+ stat = QuotientSqlColumn(app, "percentmem",
+ "sum(%s)" % cls.ImageSize.sql_column.identifier,
+ "sum(%s)*1024" % cls.Memory.sql_column.identifier)
+ 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(DefinitionSet):
+ 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
+
+ def render_title(self, session):
+ return "Host Summary Info"
+
+ def render_legend_class(self, session, item):
+ index = self.attrs.index(item[0].name)
+ if index == len(self.attrs) - 1:
+ return "blank"
+ 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.sum_column = "\"OpSys\""
+
+ self.columns = list()
+ col = "count(%s)" % self.sum_column
+ self.columns.append(self.sum_column)
+ self.columns.append(col)
+
+ 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 = self.sum_column
+
+ records = self.get_data(values, options)
+
+ return records
+
+ def get_sql_options(self, options):
+ return options
+
+class GridOSBreakdown(DefinitionSet):
+ 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)
+
+ 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))]
+ return self.os_records.get(session)
+
+ 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 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_legend_class(self, session, item):
+ index, _ = item
+ return "legend%s" % rgb_to_string(*(PieChartPage.color_schemes[self.color_scheme][index]))
Added: trunk/cumin/python/cumin/grid/dashboard.strings
===================================================================
--- trunk/cumin/python/cumin/grid/dashboard.strings (rev 0)
+++ trunk/cumin/python/cumin/grid/dashboard.strings 2011-03-02 22:36:27 UTC (rev 4565)
@@ -0,0 +1,110 @@
+[PoolDashboard.html]
+ {overview}
+ <div style="clear:both; padding-top:2em;"></div>
+ {performance}
+ <div style="clear:both; padding-top:2em;"></div>
+ {capacity}
+ <div style="clear:both; padding-top:2em;"></div>
+
+[DashboardOverview.html]
+ {os_breakdown}
+ {slots_summary}
+ {jobs_summary}
+
+[DashboardPerformance.css]
+div.DashboardPerformance {
+ width: 1000px;
+}
+[DashboardPerformance.html]
+<div id="{id}" class="{class}">
+<!-- <h2>{title}</h2> -->
+<!-- {throughput} -->
+ <h3>Scheduler Performance</h3>
+ {scheduler_performance}
+ <h3>Negotiator Performance</h3>
+ {negotiator_performance}
+</div>
+
+[DashboardCapacity.css]
+div.DashboardCapacity {
+ width: 1000px;
+}
+[DashboardCapacity.html]
+<div id="{id}" class="{class}">
+<h2>{title}</h2>
+ {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;
+}
+
+.DefinitionSet dt {
+ clear: left;
+ float: left;
+ width: 15em;
+ margin: 0;
+ padding: 5px;
+ border-top: 1px solid #999;
+}
+
+.DefinitionSet span {
+ display: block;
+ float: left;
+ height: 1em;
+ margin-right: 0.33em;
+ position: relative;
+ top: 0.1em;
+ width: 1em;
+}
+
+.DefinitionSet dd {
+ float: left;
+ width: 3em;
+ margin: 0;
+ padding: 5px;
+ border-top: 1px solid #999;
+}
+
+[DefinitionSet.html]
+<div id="{id}" class="{class}">
+<style type="text/css">
+{legend_styles}
+</style>
+ <h3>{title}</h3>
+ <dl>
+ {items}
+ </dl>
+ <img src="{pie_src}" alt="pie_chart" title="{title}" width="{width}" height="{height}"/>
+</div>
+
+[DefinitionSet.item_html]
+ <dt><span class="{legend_class}"></span>{item_title}</dt>
+ <dd>{item_value}</dd>
+
+
+[MinimalTableHeader.html]
+<thead>
+ <tr>{headers}</tr>
+</thead>
Modified: trunk/cumin/python/cumin/grid/pool.py
===================================================================
--- trunk/cumin/python/cumin/grid/pool.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/grid/pool.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -14,6 +14,7 @@
from cumin.parameters import CollectorGridAttribute, RosemaryObjectParameter
from collector import CollectorGeneralStatSet
from cumin.stat import StatFlashChart, StatSet, FlashFullPage
+from cumin.grid.dashboard import PoolDashboard
strings = StringCatalog(__file__)
@@ -47,6 +48,9 @@
overview = PoolOverview(app, "overview", self.object)
self.view.add_tab(overview)
+ dashboard = PoolDashboard(app, "dashboard", self.object)
+ self.view.add_tab(dashboard)
+
submissions = PoolSubmissionJoinSelector(app, "pool_submissions", self.object)
self.view.add_tab(submissions)
Modified: trunk/cumin/python/cumin/grid/pool.strings
===================================================================
--- trunk/cumin/python/cumin/grid/pool.strings 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/grid/pool.strings 2011-03-02 22:36:27 UTC (rev 4565)
@@ -95,18 +95,18 @@
chart.src = branch.marshal();
updatePoolSlotVis("{id}", "load");
}
- function vis_treemap_over(value) {
+ function vis_treemap_over(type, value) {
var chart = cumin.getFlashChart("{id}");
if (chart) {
if (typeof chart.highlight != "undefined")
- chart.highlight("category", value);
+ chart.highlight(type, value);
}
}
- function vis_treemap_out(value) {
+ function vis_treemap_out(type, value) {
var chart = cumin.getFlashChart("{id}");
if (chart) {
if (typeof chart.lowlight != "undefined")
- chart.lowlight("category", value);
+ chart.lowlight(type, value);
}
}
function slot_vis(state, a, id, href) {
@@ -157,3 +157,4 @@
//]]>
</script>
+
\ No newline at end of file
Modified: trunk/cumin/python/cumin/main.py
===================================================================
--- trunk/cumin/python/cumin/main.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/main.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -23,6 +23,7 @@
from widgets import *
from wooly import Session
+from cumin.stat import PieChartPage
strings = StringCatalog(__file__)
log = logging.getLogger("cumin")
@@ -109,6 +110,7 @@
self.add_page(StatChartPage(self, "stats.png"))
self.add_page(StatStackedPage(self, "stacked.png"))
+ self.add_page(PieChartPage(self, PieChartPage.PAGE_NAME))
self.add_page(StatFlashPage(self, "chart.json"))
self.add_page(FlashFullPage(self, "flashpage.html"))
Modified: trunk/cumin/python/cumin/objectselector.py
===================================================================
--- trunk/cumin/python/cumin/objectselector.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/objectselector.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -17,6 +17,7 @@
from wooly.widgets import WidgetSet, ItemSet
import wooly
from wooly.template import WidgetTemplate
+from cumin.formats import fmt_bytes
strings = StringCatalog(__file__)
@@ -172,18 +173,28 @@
try:
self.field = self.table.adapter.fields_by_attr[self.attr]
except KeyError:
- # XXX do this instead:
- #cls = self.table.adapter.default_field_cls
- #self.field = cls(self.table.adapter, self.attr)
+ cls = getattr(self.table.adapter, "default_field_cls", ObjectSqlField)
+ self.field = cls(self.table.adapter, self.attr)
- if isinstance(self.table.adapter, ObjectQmfAdapter):
- self.field = ObjectQmfField(self.table.adapter, self.attr)
- else:
- self.field = ObjectSqlField(self.table.adapter, self.attr)
+ format = self.attr.unit
+ if format:
+ if format == "MiB":
+ self.field.format = self.fmt_mb
+ elif format == "KiB":
+ self.field.format = self.fmt_kb
def render_header_link_class(self, session):
return "TableColumnHeader"
+ def fmt_b(self, value):
+ return fmt_bytes(value)
+
+ def fmt_kb(self, value):
+ return fmt_bytes(value * 1024)
+
+ def fmt_mb(self, value):
+ return fmt_bytes(value * 1024 * 1024)
+
class ObjectSelector(Form):
def __init__(self, app, name, cls, object=None):
super(ObjectSelector, self).__init__(app, name)
Modified: trunk/cumin/python/cumin/qmfadapter.py
===================================================================
--- trunk/cumin/python/cumin/qmfadapter.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/qmfadapter.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -125,6 +125,7 @@
super(ObjectQmfAdapter, self).__init__(app, cls._name)
self.cls = cls
+ self.default_field_cls = ObjectQmfField
self.fields_by_attr = dict()
Modified: trunk/cumin/python/cumin/sqladapter.py
===================================================================
--- trunk/cumin/python/cumin/sqladapter.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/sqladapter.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -63,9 +63,11 @@
self.fields_by_attr = dict()
- # XXX eliminate this?
- self.id_field = ObjectSqlField(self, self.cls._id)
+ self.id_field = self.get_id_field(cls)
+ def get_id_field(self, cls):
+ return ObjectSqlField(self, self.cls._id)
+
def add_join(self, cls, this, that):
assert cls
assert this
Modified: trunk/cumin/python/cumin/stat.py
===================================================================
--- trunk/cumin/python/cumin/stat.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/stat.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -226,6 +226,61 @@
self.__files[name] = {"time": datetime.now(), "file": file, "cookie": args}
return file
+class PieChartPage(Page):
+ # handles pie.png request
+ PAGE_NAME = "pie.png"
+ RAINBOW = "rainbow"
+ GREENS = "greens"
+ BLUES = "blues"
+ color_schemes = {"rainbow": [(1,0,0), (0,1,0), (0,0,1), (1,1,0), (1,0,1), (0,1,1)],
+ "greens": [(0,1,0), (.9,1,.9), (.6,1,.6), (.3,1,.3), (0,.5,0)],
+ "blues": [(0,0,1), (.9,.9,1), (.6,.6,1), (.3,.3,1), (0,0,.5)]}
+
+ def __init__(self, app, name):
+ super(PieChartPage, self).__init__(app, name)
+
+ param = IntegerParameter(app, "param")
+ self.slices = ListParameter(app, "slice", param)
+ self.add_parameter(self.slices)
+
+ self.color_scheme = Parameter(app, "cs")
+ self.add_parameter(self.color_scheme)
+
+ self.radius = IntegerParameter(app, "r")
+ self.add_parameter(self.radius)
+
+ # used to keep all the parameter names and the page name in one class
+ @classmethod
+ def get_href(cls, items, scheme, radius):
+ page = cls.PAGE_NAME
+
+ tokens = ["slice=%d" % x for x in items]
+
+ tokens.append("r=%s" % radius)
+
+ assert scheme in cls.color_schemes.keys()
+ tokens.append("cs=%s" % scheme)
+
+ src = "%s?%s" % (page, ";".join(tokens))
+ return src
+
+ def do_render(self, session):
+ radius = self.radius.get(session)
+
+ slices = self.slices.get(session)
+ color_scheme = self.color_scheme.get(session)
+ colors = self.color_schemes[color_scheme]
+
+ pie = CuminPieChart(radius)
+ surface = pie.plot_pie(slices, colors)
+
+ writer = Writer()
+ surface.write_to_png(writer)
+ return writer.to_string()
+
+ def get_content_type(self, session):
+ return "image/png"
+
class StatChartPage(Page):
# handles stats.png request
def __init__(self, app, name):
Modified: trunk/cumin/python/cumin/util.py
===================================================================
--- trunk/cumin/python/cumin/util.py 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/util.py 2011-03-02 22:36:27 UTC (rev 4565)
@@ -199,3 +199,13 @@
rows.append(row)
return rows
+
+def rgb_to_string(r, g, b):
+ hr, hg, hb = [hex(min(int(n*255), 255))[2:] for n in(r, g, b)]
+ hList = []
+
+ for n in (hr, hg, hb):
+ hList.append(n.rjust(2, '0').upper())
+
+ return "".join(hList)
+
Modified: trunk/cumin/python/cumin/widgets.strings
===================================================================
--- trunk/cumin/python/cumin/widgets.strings 2011-03-02 22:33:07 UTC (rev 4564)
+++ trunk/cumin/python/cumin/widgets.strings 2011-03-02 22:36:27 UTC (rev 4565)
@@ -104,6 +104,10 @@
top: -2px;
}
+div.TabbedModeSet.mode {
+ /* min-width: 954px; */
+}
+
[CuminMainView.javascript]
cumin.set_updated = function () {
$('heartbeat').set('text', "Updated " + new Date().format("%Y-%m-%d %H:%M:%S"));
@@ -896,3 +900,4 @@
[StaticColumnHeader.html]
<th class="{class}">{content}</th>
+