Author: eallen
Date: 2009-04-08 14:33:07 -0400 (Wed, 08 Apr 2009)
New Revision: 3269
Modified:
mgmt/trunk/cumin/python/cumin/charts.py
mgmt/trunk/cumin/python/cumin/collector.py
mgmt/trunk/cumin/python/cumin/collector.strings
mgmt/trunk/cumin/python/cumin/stat.py
mgmt/trunk/cumin/python/cumin/stat.strings
Log:
Stacked value charts
Modified: mgmt/trunk/cumin/python/cumin/charts.py
===================================================================
--- mgmt/trunk/cumin/python/cumin/charts.py 2009-04-08 18:32:22 UTC (rev 3268)
+++ mgmt/trunk/cumin/python/cumin/charts.py 2009-04-08 18:33:07 UTC (rev 3269)
@@ -258,3 +258,62 @@
def write(self, writer):
self.surface.write_to_png(writer)
+
+class StackedValueChart(TimeSeriesChart):
+ def __init__(self, width, height, legend_height = 0):
+ super(StackedValueChart, self).__init__(width, height)
+
+ if legend_height:
+ self.surface = ImageSurface(FORMAT_ARGB32, width, height + legend_height)
+ self.surface.set_device_offset(1.5, 5.5)
+
+ self.legend_height = legend_height
+ self.bar_width = 2
+
+ def plot_values(self, dt, samples, colors):
+ cr = Context(self.surface)
+ cr.set_line_width(self.bar_width)
+
+ tnow = time()
+ y = self.height
+ v = 0
+ for value, color in zip(samples, colors):
+ if value is None:
+ continue
+
+ t = secs(dt)
+
+ if tnow - t < 0:
+ t = tnow
+
+ x = self.width - ((tnow - t) / float(self.x_max)) * self.width
+ cr.move_to(x, y)
+ cr.set_source_rgba(color[0], color[1], color[2], 0.66)
+
+ v = v + value
+ y = self.height - (v / float(self.y_max)) * self.height
+ cr.line_to(x, y)
+ cr.stroke()
+
+ def plot_legend(self, titles, colors):
+ cr = Context(self.surface)
+
+ for i, item in enumerate(zip(titles, colors)):
+ title, color = item
+ y = self.height + 16 + i * 16
+
+ cr.set_source_rgba(1, 1, 1, 0.75)
+ cr.rectangle(4, y - 12, 16 + 6.5 * len(title), 16)
+ cr.fill()
+ cr.stroke()
+
+ cr.set_source_rgb(*color)
+ cr.rectangle(8, y, 8, -8)
+ cr.fill()
+ cr.stroke()
+
+ cr.move_to(20, y)
+ cr.set_source_rgb(0, 0, 0)
+ cr.show_text(title)
+ cr.stroke()
+
Modified: mgmt/trunk/cumin/python/cumin/collector.py
===================================================================
--- mgmt/trunk/cumin/python/cumin/collector.py 2009-04-08 18:32:22 UTC (rev 3268)
+++ mgmt/trunk/cumin/python/cumin/collector.py 2009-04-08 18:33:07 UTC (rev 3269)
@@ -108,6 +108,23 @@
self.add_mode(view)
self.set_view_mode(view)
+class CollectorStats(Widget):
+ def __init__(self, app, name):
+ super(CollectorStats, self).__init__(app, name)
+
+ self.add_child(StatSet(app, "stats", "general"))
+
+ chart = self.StackedChart(app, "utilization")
+ chart.stats = ("HostsIdle", "HostsClaimed",
"HostsUnclaimed")
+ self.add_child(chart)
+
+ def render_title(self, session):
+ return "Statistics"
+
+ class StackedChart(StatStackedChart):
+ def render_title(self, session, *args):
+ return "Slot Utilization"
+
class CollectorView(CuminView):
def __init__(self, app, name):
super(CollectorView, self).__init__(app, name)
@@ -118,6 +135,9 @@
self.__tabs = TabbedModeSet(app, "tabs")
self.add_child(self.__tabs)
+ stats = CollectorStats(app, "stats")
+ self.__tabs.add_tab(stats)
+
details = CuminDetails(app, "details")
self.__tabs.add_tab(details)
Modified: mgmt/trunk/cumin/python/cumin/collector.strings
===================================================================
--- mgmt/trunk/cumin/python/cumin/collector.strings 2009-04-08 18:32:22 UTC (rev 3268)
+++ mgmt/trunk/cumin/python/cumin/collector.strings 2009-04-08 18:33:07 UTC (rev 3269)
@@ -38,3 +38,18 @@
<div>{hidden_inputs}</div>
</form>
+
+[CollectorStats.html]
+<table class="twocol">
+ <tbody>
+ <tr>
+ <td>
+ <h2>Collector Stats</h2>
+ {stats}
+ </td>
+ <td>
+ {utilization}
+ </td>
+ </tr>
+ </tbody>
+</table>
Modified: mgmt/trunk/cumin/python/cumin/stat.py
===================================================================
--- mgmt/trunk/cumin/python/cumin/stat.py 2009-04-08 18:32:22 UTC (rev 3268)
+++ mgmt/trunk/cumin/python/cumin/stat.py 2009-04-08 18:33:07 UTC (rev 3269)
@@ -73,7 +73,14 @@
#else:
return stat.rate_html(object)
+class DurationSwitch(StateSwitch):
+ def __init__(self, app, name):
+ super(DurationSwitch, self).__init__(app, name)
+ self.add_state("m", "10 minutes")
+ self.add_state("h", "1 hour")
+ self.add_state("d", "1 day")
+
class StatValueChart(Widget):
def __init__(self, app, name):
super(StatValueChart, self).__init__(app, name)
@@ -112,8 +119,11 @@
if self.mode:
params.append("mode=%s" % self.mode)
- return "stats.png?" + ";".join(params)
+ return "%s?" % self.get_chart_name(session) +
";".join(params)
+ def get_chart_name(self, session):
+ return "stats.png"
+
def render_title(self, session, object):
cls = self.app.model.get_class_by_object(object)
return getattr(cls, self.stats[0]).title
@@ -145,6 +155,40 @@
def render_stat_name(self, session, stat, object):
return stat.name
+ def render_width(self, session, object):
+ return 360
+
+ def render_height(self, session, object):
+ return 100
+
+class StatStackedChart(StatValueChart):
+ def __init__(self, app, name):
+ super(StatStackedChart, self).__init__(app, name)
+
+ self.duration = self.JSDurationSwitch(app, "duration")
+ self.add_child(self.duration)
+
+ def get_chart_name(self, session):
+ return "stacked.png"
+
+ def render_height(self, session, object):
+ return 200
+
+ class JSDurationSwitch(DurationSwitch):
+ def render_item_link(self, session, state):
+ href = "javascript:void(0)"
+
+ title = self.get_title(state)
+ hover = self.get_hover(state)
+ class_ = self.get(session) == state and "selected"
+ duration = 600
+ if state == "h":
+ duration = 3600
+ elif state == "d":
+ duration = 86400
+ click = "stackedDuration('%s', this, %i)" % (state,
duration)
+ return fmt_link(href, title, class_, link_title=hover, click=click)
+
class ImageCache(object):
def __init__(self):
self.__files = dict() # {name: {"time": time_created, "file":
file object, "cookie": (cookie values)}}
@@ -409,10 +453,102 @@
chart.write(writer)
return writer.to_string()
-class DurationSwitch(StateSwitch):
+from random import *
+class StatStackedPage(StatChartPage):
def __init__(self, app, name):
- super(DurationSwitch, self).__init__(app, name)
+ super(StatStackedPage, self).__init__(app, name)
- self.add_state("m", "10 minutes")
- self.add_state("h", "1 hour")
- self.add_state("d", "1 day")
+ self.container_width = IntegerParameter(app, "width")
+ self.container_width.default = 360
+ self.add_parameter(self.container_width)
+
+ self.container_height = IntegerParameter(app, "height")
+ self.container_height.default = 100
+ self.add_parameter(self.container_height)
+
+ def fake_samples(self, count, t, diff):
+ start = randrange(1, 100)
+
+ return [ (t+timedelta(seconds=-i*diff), start + randrange(-5, 5)) for i in
range(0, count) ]
+
+ def do_render(self, session, object):
+ chart = StackedValueChart(self.container_width.get(session),
self.container_height.get(session), 100)
+
+ cls = self.class_.get(session)
+ stats = [getattr(cls, x) for x in self.stats.get(session)]
+
+ samples = dict()
+ values = dict()
+
+ duration = self.duration.get(session)
+
+ #XXX fake stats for now
+ count = self.container_width.get(session) / 3
+ t = datetime.now()
+ seed(1)
+ for stat in stats:
+ samples[stat] = self.fake_samples(min(count, 200), t, 10)
+
+ #samples[stat] = stat.samples(object, duration)
+
+ points = dict()
+ for stat in stats:
+ for sample in samples[stat]:
+ t = sample[0]
+ if not t in points:
+ points[t] = list()
+ points[t].append(sample[1])
+
+ for t in points:
+ values[t] = 0
+ for v in points[t]:
+ values[t] = values[t] + v
+
+ max_value = 1
+ min_value = 0
+
+ for t in points:
+ vals = values[t]
+ if vals:
+ max_value = max(nvl(vals, 0), max_value)
+ min_value = min(nvl(vals, 0), min_value)
+
+ max_value = round(max_value * 1.1 + 1)
+
+ if min_value < 0:
+ min_value = round(min_value * 1.1 - 1)
+
+ chart.x_max = duration
+ chart.x_min = 0
+ chart.y_max = max_value
+ chart.y_min = min_value
+
+ x_intervals = 8
+ x_step = 2
+
+ if chart.x_max == 600:
+ x_intervals = 10
+
+ y_intervals = 6
+ y_step = 2
+
+ if chart.y_max < 3:
+ y_step = 3
+
+ chart.plot_x_axis(x_intervals, x_step)
+ chart.plot_y_axis(y_intervals, y_step)
+
+ colors = ((1,0,0), (0,0,1), (0,1,0), (1,0,1), (1,1,0), (0,1,1), (0,0,0))
+
+ for t in points:
+ chart.plot_values(t, points[t], colors)
+
+ chart.plot_frame()
+
+ titles = [x.title for x in stats]
+
+ chart.plot_legend(reversed(titles), reversed(colors[:len(titles)]))
+
+ writer = Writer()
+ chart.write(writer)
+ return writer.to_string()
Modified: mgmt/trunk/cumin/python/cumin/stat.strings
===================================================================
--- mgmt/trunk/cumin/python/cumin/stat.strings 2009-04-08 18:32:22 UTC (rev 3268)
+++ mgmt/trunk/cumin/python/cumin/stat.strings 2009-04-08 18:33:07 UTC (rev 3269)
@@ -78,7 +78,7 @@
<div class="duration">{duration}</div>
- <img id="{id}" src="{href}" height="100"
width="360" alt="stats" />
+ <img id="{id}" src="{href}" height="{height}"
width="{width}" alt="stats" />
</div>
<script type="text/javascript">
(function() {
@@ -95,3 +95,58 @@
<span class="swatch" style="background-color:
{stat_color}"> </span>
<span class="ph" statname="{stat_name}"
statmode="{mode}">{stat_value}</span>
</li>
+
+[StatStackedChart.html]
+<div class="StatValueChart fullpageable" id="{id}">
+ <h2>{title}</h2>
+
+ <div class="duration">{duration}</div>
+
+ <img id="{id}" src="{href}" alt="Stacked Value Chart"
/>
+</div>
+<script type="text/javascript">
+//<![CDATA[
+(function() {
+ function update() {
+ cumin.updateChart("{id}");
+ }
+
+ wooly.addPageUpdateListener(update);
+}())
+function stackedNotify(full, width, height) {
+ var oImg = document.images['{id}'];
+ if (oImg) {
+ var src = oImg.src;
+ var branch = wooly.session.branch(src);
+ branch.width = Math.max(width - 100, 360);
+ branch.height = Math.max(height - 100, 100);
+ src = branch.marshal();
+
+ src = cumin.refreshTime(src);
+ oImg.style.visibility = "hidden"
+ oImg.src = src;
+ }
+}
+function stackedDuration(state, a, duration) {
+ li = a.parentNode;
+ ul = li.parentNode;
+ as = ul.getElementsByTagName('a');
+ for (var i=0; i < as.length; i++) {
+ as[i].className = (as[i].firstChild.nodeValue.indexOf(state) != -1) ?
"selected" : "";
+ }
+ var oImg = document.images['{id}'];
+ if (oImg) {
+ var src = oImg.src;
+ var branch = wooly.session.branch(src);
+ branch.duration = duration;
+ src = branch.marshal();
+ src = cumin.refreshTime(src);
+ oImg.src = src;
+ }
+}
+ $('{id}').onfullpage = function (width, height) { stackedNotify(true, width,
height); };
+ $('{id}').onrestore = function () { stackedNotify(false, {width}, 100); };
+ document.images['{id}'].onload = function () {this.style.visibility =
"visible"};
+ document.images['{id}'].ondblclick = function ()
{fullpage(this.parentNode.parentNode.parentNode.getElementsByTagName('p')[0])};
+//]]>
+</script>