Author: eallen
Date: 2011-03-08 21:44:09 +0000 (Tue, 08 Mar 2011)
New Revision: 4585
Modified:
trunk/cumin/model/condor.xml
trunk/cumin/model/rosemary.xml
trunk/cumin/python/cumin/charts.py
trunk/cumin/python/cumin/grid/dashboard.py
trunk/cumin/python/cumin/grid/dashboard.strings
trunk/cumin/python/cumin/objectframe.py
trunk/cumin/python/cumin/objectframe.strings
trunk/cumin/python/cumin/objectselector.py
trunk/cumin/python/cumin/stat.py
trunk/cumin/python/cumin/stat.strings
Log:
BZ 673194: Next iteration of the grid dashboard. This requires a reschema.
Modified: trunk/cumin/model/condor.xml
===================================================================
--- trunk/cumin/model/condor.xml 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/model/condor.xml 2011-03-08 21:44:09 UTC (rev 4585)
@@ -32,13 +32,13 @@
</group>
<group name="DaemonStatistics">
- <statistic name="MonitorSelfAge" type="uint32"/>
- <statistic name="MonitorSelfCPUUsage" type="double"/>
+ <statistic name="MonitorSelfAge" type="uint32" optional="y"/>
+ <statistic name="MonitorSelfCPUUsage" type="double" optional="y"/>
- <statistic name="MonitorSelfImageSize" type="double" unit="KiB"/>
- <statistic name="MonitorSelfRegisteredSocketCount" type="uint32"/>
- <statistic name="MonitorSelfResidentSetSize" type="uint32" unit="KiB"/>
- <statistic name="MonitorSelfTime" type="absTime"/>
+ <statistic name="MonitorSelfImageSize" type="double" unit="KiB" optional="y"/>
+ <statistic name="MonitorSelfRegisteredSocketCount" type="uint32" optional="y"/>
+ <statistic name="MonitorSelfResidentSetSize" type="uint32" unit="KiB" optional="y"/>
+ <statistic name="MonitorSelfTime" type="absTime" optional="y"/>
</group>
<group name="GridMethods">
@@ -581,6 +581,30 @@
<group name="DaemonStatistics"/>
<group name="GridMethods"/>
+ <statistic name="MatchRateSustained" type="uint32" desc="Matcher per second, since end of previous cycle" optional="y"/>
+ <statistic name="MatchRate" type="uint32" desc="Matches per second over current cycle" optional="y"/>
+ <statistic name="Matches" type="uint32" desc="Number of matches generated" optional="y"/>
+ <statistic name="Duration" type="uint32" desc="Negotiation cycle duration, in seconds" optional="y"/>
+ <statistic name="Phase1Duration" type="uint32" desc="Duration of phase 1, in seconds" optional="y"/>
+ <statistic name="Phase2Duration" type="uint32" desc="Duration of phase 2, in seconds" optional="y"/>
+ <statistic name="Phase3Duration" type="uint32" desc="Duration of phase 3, in seconds" optional="y"/>
+ <statistic name="Phase4Duration" type="uint32" desc="Duration of phase 4, in seconds" optional="y"/>
+ <statistic name="SlotShareIter" type="uint32" desc="Number of share iterations" optional="y"/>
+ <statistic name="NumSchedulers" type="uint32" desc="Number of submitters" optional="y"/>
+ <statistic name="ActiveSubmitterCount" type="uint32" desc="Number of submitters with jobs" optional="y"/>
+ <statistic name="SubmittersFailed" type="lstr" desc="List of submitters that encountered a failure" optional="y"/>
+ <statistic name="SubmittersOutOfTime" type="lstr" desc="List of submitters that hit their match time limit" optional="y"/>
+ <statistic name="SubmittersShareLimit" type="lstr" desc="List of submitters that hit their share limit" optional="y"/>
+ <statistic name="NumIdleJobs" type="uint32" desc="Number of idle jobs" optional="y"/>
+ <statistic name="NumJobsConsidered" type="uint32" desc="Number of jobs that were considered for matching" optional="y"/>
+ <statistic name="Rejections" type="uint32" desc="Number of rejections" optional="y"/>
+ <statistic name="TotalSlots" type="uint32" desc="Number of slots found" optional="y"/>
+ <statistic name="TrimmedSlots" type="uint32" desc="Number of slots after trimming claimed slots" optional="y"/>
+ <statistic name="CandidateSlots" type="uint32" desc="Number of slots considered for matching" optional="y"/>
+ <statistic name="Time" type="uint32" desc="Start of cycle, seconds since Epoch" optional="y"/>
+ <statistic name="End" type="uint32" desc="End of cycle, seconds since Epoch" optional="y"/>
+ <statistic name="Period" type="uint32" desc="Time from end of previous cycle to end of current cycle" optional="y"/>
+
<method name="GetLimits">
<arg name="Limits" dir="O" type="map"/>
</method>
@@ -625,6 +649,7 @@
<class name="Collector">
<group name="DaemonProperties"/>
+ <group name="DaemonStatistics"/>
<!-- TODO: for now this doesn't seem to be worth the headache
of the query worker socket handler interaction & crashes
<group name="GridMethods"/>
Modified: trunk/cumin/model/rosemary.xml
===================================================================
--- trunk/cumin/model/rosemary.xml 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/model/rosemary.xml 2011-03-08 21:44:09 UTC (rev 4585)
@@ -294,6 +294,16 @@
</package>
<package name="com.redhat.grid">
+ <class name="Scheduler">
+ <statistic name="MonitorSelfCPUUsage">
+ <title>CPU Usage</title>
+ </statistic>
+
+ <statistic name="MonitorSelfResidentSetSize">
+ <title>Memory Used</title>
+ </statistic>
+ </class>
+
<class name="Collector">
<title>Pool</title>
@@ -316,6 +326,14 @@
<statistic name="HostsTotal">
<title>Total Hosts</title>
</statistic>
+
+ <statistic name="MonitorSelfCPUUsage">
+ <title>CPU Usage</title>
+ </statistic>
+
+ <statistic name="MonitorSelfResidentSetSize">
+ <title>Memory Used</title>
+ </statistic>
</class>
<class name="Slot">
@@ -371,5 +389,19 @@
<title>Description</title>
</property>
</class>
+
+ <class name="Negotiator">
+ <statistic name="Duration">
+ <title>Total Duration</title>
+ </statistic>
+
+ <statistic name="MonitorSelfCPUUsage">
+ <title>CPU Usage</title>
+ </statistic>
+
+ <statistic name="MonitorSelfResidentSetSize">
+ <title>Memory Used</title>
+ </statistic>
+ </class>
</package>
</model>
Modified: trunk/cumin/python/cumin/charts.py
===================================================================
--- trunk/cumin/python/cumin/charts.py 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/python/cumin/charts.py 2011-03-08 21:44:09 UTC (rev 4585)
@@ -27,6 +27,9 @@
wedge = cum_wedges = end = 0.0
+ # are we going to draw more than one slice
+ draw_border = sum([1 for x in slices if x > 0]) > 1
+
for slice, color in zip(slices, colors):
if slice > 0:
wedge = slice / total
@@ -42,10 +45,11 @@
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()
+ if draw_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
@@ -55,27 +59,6 @@
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/grid/dashboard.py
===================================================================
--- trunk/cumin/python/cumin/grid/dashboard.py 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/python/cumin/grid/dashboard.py 2011-03-08 21:44:09 UTC (rev 4585)
@@ -3,14 +3,16 @@
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
+from cumin.stat import StatSet, PieChartPage, StatFlashChart
from rosemary.sqlquery import SqlQueryOptions
-from cumin.objectselector import ObjectSelector, ObjectLinkColumn,\
- MonitorSelfStatColumn, ObjectTableColumn, ObjectTable
-from wooly.table import TableHeader
+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")
@@ -30,6 +32,9 @@
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"
@@ -53,19 +58,27 @@
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)
+ cls = app.model.com_redhat_grid.Collector
+ c = MonitorSelfStats(app, "ms_c", collector, cls, "Collector Performance", None)
+ self.add_child(c)
- #collector_perf = DashboardCollectorSelector(app, "collector_performance", collector)
- #self.add_child(collector_perf)
+ 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)
- negotiator = DashboardNegotiatorSelector(app, "negotiator_performance", collector)
- self.add_child(negotiator)
+ s = MonitorSelfSchedulerTable(app, "ms_s", collector)
+ self.add_child(s)
- sched = DashboardSchedulerSelector(app, "scheduler_performance", collector)
- self.add_child(sched)
+ 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"
@@ -80,22 +93,48 @@
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(DefinitionSet, self).__init__(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):
- return self.radius * 2
+ # 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.radius * 2
+ return self.render_width(session)
def render_pie_src(self, session):
- items = self.do_get_items(session)
- slices = [self.render_item_value(session, x) for x in items if not self.is_total(x)]
+ slices = self.get_pie_slices(session)
return PieChartPage.get_href(slices, self.color_scheme, self.radius)
def render_legend_styles(self, session):
@@ -107,19 +146,34 @@
return "\n".join(styles)
def render_class(self, session):
- cls = super(DefinitionSet, self).render_class(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
def render_legend_class(self, session, item):
- return ""
+ 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 ""
+ 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
@@ -129,21 +183,21 @@
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
- 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)
@@ -160,6 +214,8 @@
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)
@@ -256,59 +312,6 @@
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)
@@ -337,13 +340,14 @@
return records[0]
-class DashboardSummarySet(DefinitionSet):
+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
@@ -370,11 +374,12 @@
def render_item_value(self, session, item):
record = self.record.get(session)
- return record[self.sum_columns.index(item.name)]
+ return record[self.sum_columns.index(item.name)] or 0
- def render_legend_class(self, session, item):
- if self.is_total(item):
- return "total"
+ 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]))
@@ -399,15 +404,19 @@
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)
@@ -421,7 +430,7 @@
def render_text_align(self, session):
return "left"
-class GridSlotsSummary(DefinitionSet):
+class GridSlotsSummary(PieStatSet):
def __init__(self, app, name, collector):
super(GridSlotsSummary, self).__init__(app, name, collector)
@@ -431,6 +440,10 @@
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"
@@ -438,9 +451,7 @@
index = self.attrs.index(item[0].name)
return index == len(self.attrs) - 1
- def render_legend_class(self, session, item):
- if self.is_total(item):
- return "total"
+ 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]))
@@ -495,9 +506,9 @@
########## dummy test data
"""
- records.append(("WINNT70 Used", 10))
- records.append(("WINNT70 Unused", 20))
- records.append(("WINNT70 Total", 30))
+ 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))
@@ -511,7 +522,7 @@
def get_id_field(self, cls):
return None
-class GridOSBreakdown(DefinitionSet):
+class GridOSBreakdown(PieStatSet):
def __init__(self, app, name, collector):
super(GridOSBreakdown, self).__init__(app, name, collector)
@@ -529,6 +540,11 @@
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"
@@ -547,9 +563,10 @@
_, record = item
return record[1]
- def render_legend_class(self, session, this_item):
- if self.is_total(this_item):
- return "total"
+ 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:
@@ -559,3 +576,291 @@
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)
+
+ 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")
+
+ cls = app.model.com_redhat_grid.Negotiator
+ self.popup_charts = dict()
+ for attr in self.attrs:
+ 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 \
+ ""
Modified: trunk/cumin/python/cumin/grid/dashboard.strings
===================================================================
--- trunk/cumin/python/cumin/grid/dashboard.strings 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/python/cumin/grid/dashboard.strings 2011-03-08 21:44:09 UTC (rev 4585)
@@ -1,10 +1,12 @@
[PoolDashboard.html]
{overview}
+ <div style="clear:both;"></div>
+ {capacity}
<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>
+ <div style="clear:both;"></div>
+ {other_stats}
+ <div style="clear:both;"></div>
[DashboardOverview.html]
{os_breakdown}
@@ -15,23 +17,42 @@
div.DashboardPerformance {
width: 1000px;
}
+
+div.DashboardPerformance div.TabbedModeSet {
+ min-height: 6em;
+}
+
[DashboardPerformance.html]
<div id="{id}" class="{class}">
-<!-- <h2>{title}</h2> -->
-<!-- {throughput} -->
- <h3>Scheduler Performance</h3>
- {scheduler_performance}
- <h3>Negotiator Performance</h3>
- {negotiator_performance}
+ {ms_s}
+ {ms_c}
+ {ms_n}
</div>
+ {negotiator_throughput}
+ {negotiator_cycles}
+ {negotiator_slots}
+[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}">
-<h2>{title}</h2>
+<h3>{title}</h3>
{slot_capacity}
</div>
@@ -78,13 +99,17 @@
.DefinitionSet span {
display: block;
float: left;
+ width: 1em;
height: 1em;
margin-right: 0.33em;
position: relative;
top: 0.1em;
- width: 1em;
}
+.DefinitionSet dt.blank span {
+ width: 0;
+}
+
.DefinitionSet dd {
float: left;
width: 3em;
@@ -93,12 +118,32 @@
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;
+}
+
[DefinitionSet.html]
<div id="{id}" class="{class}">
<style type="text/css">
{legend_styles}
</style>
- <h3>{title}</h3>
+ <h3>{title}<span class="{span_class}" title="Click to show a timeseries chart" onclick="showHistoryChart('{title}', '{chart_href}')"></span></h3>
<dl>
{items}
</dl>
@@ -106,7 +151,7 @@
</div>
[DefinitionSet.item_html]
- <dt class="{legend_class}"><span></span>{item_title}</dt>
+ <dt title="{html_title}" class="{legend_class}"><span></span>{item_title}</dt>
<dd{dd_class}>{item_value}</dd>
@@ -114,3 +159,77 @@
<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>
+<script type="text/javascript">
+//<![CDATA[
+function showHistoryChart(title, src) {
+
+ if (src != '') {
+ new StickyWin.Modal.Ajax({
+ url: src,
+ wrapWithUi: true,
+ caption: title,
+ useIframeShim: false,
+ allowMultiple: false,
+ closeOnEsc: true,
+ uiOptions: {width: 420}
+ }).update();
+ }
+ return false;
+}
+//]]>
+</script>
+
+[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>
Modified: trunk/cumin/python/cumin/objectframe.py
===================================================================
--- trunk/cumin/python/cumin/objectframe.py 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/python/cumin/objectframe.py 2011-03-08 21:44:09 UTC (rev 4585)
@@ -94,16 +94,17 @@
for attr in self.get_attributes(session):
name = attr.title
value = obj.get_formatted_value(attr.name)
+ title = attr.description
- writer.write(self.entry.render(session, name, value))
+ writer.write(self.entry.render(session, name, value, title))
return writer.to_string()
class ObjectAttributesEntry(Widget):
- def render_name(self, session, name, value):
+ def render_name(self, session, name, value, description):
return name
- def render_value(self, session, name, value):
+ def render_value(self, session, name, value, description):
if value is None:
return fmt_none()
@@ -112,6 +113,9 @@
return xml_escape(str(value))
+ def render_html_title(self, session, name, value, description):
+ return description
+
def break_up_long_lines(self, string):
if " " in string[0:80]:
return string
Modified: trunk/cumin/python/cumin/objectframe.strings
===================================================================
--- trunk/cumin/python/cumin/objectframe.strings 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/python/cumin/objectframe.strings 2011-03-08 21:44:09 UTC (rev 4585)
@@ -30,7 +30,7 @@
</div>
[ObjectAttributesEntry.html]
-<tr><th>{name}</th><td>{value}</td></tr>
+<tr><th title="{html_title}">{name}</th><td>{value}</td></tr>
[ObjectTasks.css]
table.ObjectTasks {
@@ -118,6 +118,7 @@
}
[ObjectView.html]
+<div id="{id}" class="{class}">
{context}
{heading}
@@ -125,6 +126,7 @@
{summary}
{body}
+</div>
[ObjectViewHeading.html]
<div class="{class}">
Modified: trunk/cumin/python/cumin/objectselector.py
===================================================================
--- trunk/cumin/python/cumin/objectselector.py 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/python/cumin/objectselector.py 2011-03-08 21:44:09 UTC (rev 4585)
@@ -124,7 +124,7 @@
self.selector = selector
# avoid name collisions on csv page
- if self.name in self.app.export_page.modes.children_by_name:
+ while self.name in self.app.export_page.modes.children_by_name:
self.name = self.name + ".1"
self.app.export_page.modes.add_mode(self)
Modified: trunk/cumin/python/cumin/stat.py
===================================================================
--- trunk/cumin/python/cumin/stat.py 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/python/cumin/stat.py 2011-03-08 21:44:09 UTC (rev 4585)
@@ -29,6 +29,8 @@
def do_get_items(self, session):
object = self.object.get(session)
+ if not object:
+ return []
stats = list()
for field in self.get_fields(session):
@@ -40,6 +42,10 @@
stat, value = item
return stat.title
+ def render_html_title(self, session, item):
+ stat, _ = item
+ return stat.description
+
def render_item_value(self, session, item):
stat, value = item
return CuminStatistic.fmt_value(value)
@@ -494,8 +500,8 @@
deviated_values = list()
for stat in stats:
for x in samples[stat]:
- d1 = nvl(x[1], 0)
- d2 = nvl(x[2], 0) / 2
+ d1 = float(nvl(x[1], 0))
+ d2 = float(nvl(x[2], 0) / 2)
dv = (d1 + d2, d1 - d2)
deviated_values.append(dv)
@@ -1290,7 +1296,10 @@
min_dt = tnow - duration - end_secs
for dt, value, dev in samples[stat]:
if value is not None:
- vals.append({"dt": secs(dt), "y": (float(value) / float(total)) * 100.0})
+ if total is None or total == 0.0:
+ vals.append({"dt": secs(dt), "y": 0.0})
+ else:
+ vals.append({"dt": secs(dt), "y": (float(value) / float(total)) * 100.0})
if secs(dt) < min_dt:
break
Modified: trunk/cumin/python/cumin/stat.strings
===================================================================
--- trunk/cumin/python/cumin/stat.strings 2011-03-08 21:40:10 UTC (rev 4584)
+++ trunk/cumin/python/cumin/stat.strings 2011-03-08 21:44:09 UTC (rev 4585)
@@ -46,7 +46,7 @@
</table>
[StatSet.item_html]
-<tr class="item">
+<tr class="item" title="{html_title}">
<th>{item_title}</th>
<td class="ralign"> {item_value}</td>
</tr>