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> +
cumin-developers@lists.fedorahosted.org