Author: eallen
Date: 2009-07-04 14:17:09 -0400 (Sat, 04 Jul 2009)
New Revision: 3477
Modified:
mgmt/trunk/cumin/python/cumin/stat.py
Log:
Refactor flash charts to prepare for stacked charts.
Use area charts instead of line charts.
Modified: mgmt/trunk/cumin/python/cumin/stat.py
===================================================================
--- mgmt/trunk/cumin/python/cumin/stat.py 2009-07-04 18:15:44 UTC (rev 3476)
+++ mgmt/trunk/cumin/python/cumin/stat.py 2009-07-04 18:17:09 UTC (rev 3477)
@@ -99,8 +99,8 @@
super(StatValueChart, self).__init__(app, name)
self.mode = None
-
self.stats = ()
+ self.chart_type = None
self.stats_tmpl = Template(self, "stat_html")
@@ -126,6 +126,9 @@
if self.mode:
params.append("mode=%s" % self.mode)
+ if self.chart_type:
+ params.append("type=%s" % self.chart_type)
+
return params
def render_href(self, session, object):
@@ -608,9 +611,13 @@
self.mode = Parameter(app, "mode")
self.add_parameter(self.mode)
+ self.chart_type = Parameter(app, "type")
+ self.add_parameter(self.chart_type)
+
def render_content(self, session):
self.flash_chart.stats = self.stats.get(session)
self.flash_chart.mode = self.mode.get(session)
+ self.flash_chart.chart_type = self.chart_type.get(session)
return self.flash_chart.render(session)
class GenericChart(StatFlashChart):
@@ -631,191 +638,139 @@
params.append("height=%i" % self.render_height(session, object))
return "%s?" % self.get_chart_name(session) +
";".join(params)
-class StatFlashPage(StatChartPage):
- def __init__(self, app, name):
- super(StatFlashPage, self).__init__(app, name)
+ def render_href(self, session, object):
+ params = self.get_href_params(session, object)
+ params.append("width=%i" % self.render_width(session, object))
+ params.append("height=%i" % self.render_height(session, object))
+ return escape_entity("%s?" % self.get_flash_name(session) +
";".join(params))
- self.elapsed = Parameter(app, "elapsed")
- self.elapsed.default = None
- self.add_parameter(self.elapsed)
+class FlashChart(Widget):
+ colors = ('#FF0000', '#0000FF', '#00FF00', '#FF00FF',
'#FFFF00', '#00FFFF', '#000000')
+ one_day = 24 * 60 * 60
+ def __init__(self, app, name, page):
+ super(FlashChart, self).__init__(app, name)
+ self.page = page
- self.axis_max = IntegerParameter(app, "amax")
- self.axis_max.default = 0;
- self.add_parameter(self.axis_max)
+ def create(self, session, object, stats):
+ # get the page parameters
+ width = self.page.container_width.get(session)
+ height = self.page.container_height.get(session)
+ mode = self.page.mode.get(session)
+ duration = self.get_duration(session)
+ time_period = self.page.duration.get(session)
+ delta = self.get_delta(session)
+ interval = self.page.get_interval(session, duration, width)
+ method = self.page.method.get(session)
- self.vals_max = IntegerParameter(app, "vmax")
- self.vals_max.default = 0;
- self.add_parameter(self.vals_max)
+ # get the most recent samples
+ samples = self.fetch_samples(object, duration, interval, method, mode, delta,
stats)
+ max_value, min_value = self.get_max_min(session, stats, samples)
- def get_content_type(self, session):
- return "text/plain"
+ append = False
+ # if we should append the new samples, see if the y-axis has changed
+ if delta:
+ axis_max = self.page.axis_max.get(session)
+ vals_max = self.page.vals_max.get(session)
+ axis_for_vals = round(float(vals_max) * 1.1 + 1)
+ max_of_axiis = max(max_value, axis_for_vals)
+ # the most recent value(s) changed the y-axis range
+ if axis_max != max_of_axiis:
+ #print "change in axis: axis_max=%s vals_max=%s max_new_values=%s
vals_axis=%s desired_axis=%s" % (str(axis_max), str(vals_max), str(max_value),
str(axis_for_vals), str(max_of_axiis))
+ duration = time_period
+ samples = self.fetch_samples(object, duration, interval, method, mode,
False, stats)
+ max_value, min_value = self.get_max_min(session, stats, samples)
+ else:
+ append = True
+ # create the chart dict
+ chart = self.get_chart(session, stats, samples, duration, max_value, min_value,
append)
+ return chart.create()
+
+ def get_chart(self, session, stats, samples, duration, max_value, min_value,
append):
+ return Chart()
+
def get_duration(self, session):
- duration = self.duration.get(session)
- elapsed = self.elapsed.get(session)
- if elapsed:
- js_milliseconds = long(elapsed)
- seconds = js_milliseconds / 1000
- elapsed = int(seconds + 1)
- if elapsed <= 24 * 60 * 60:
- duration = elapsed
+ duration = self.page.duration.get(session)
+ elapsed = self.get_elapsed(session)
+ if elapsed['seconds'] <= self.one_day:
+ duration = elapsed['seconds'] + 1
return duration
def get_elapsed(self, session):
- elapsed = self.elapsed.get(session)
- if elapsed:
- js_milliseconds = long(elapsed)
- seconds = js_milliseconds / 1000
+ elapsed = {'seconds': 0, 'milliseconds': 0.0, 'value':
0.0}
+ e = self.page.elapsed.get(session)
+ if e:
+ js_milliseconds = long(e)
+ seconds = int(js_milliseconds / 1000)
milliseconds = (js_milliseconds % 1000.0) / 1000.0
- return seconds + milliseconds
- return 0
+ elapsed['seconds'] = seconds
+ elapsed['milliseconds'] = milliseconds
+ elapsed['value'] = seconds + milliseconds
+ return elapsed
def get_delta(self, session):
""" if we get an elapsed parameter, we want
to send only the new values, that is
unless the elapsed value is too large
in which case we want to get the entire sample """
-
- elapsed = self.elapsed.get(session)
- if elapsed:
- js_milliseconds = long(elapsed)
- seconds = js_milliseconds / 1000
- elapsed = int(seconds)
- if elapsed <= 24 * 60 * 60:
- return True
+ elapsed = self.get_elapsed(session)
+ if elapsed['seconds'] <= self.one_day:
+ return True
return False
- def do_render(self, session):
- object = self.get_object(session)[0]
- colors = ('#FF0000', '#0000FF', '#00FF00')
- samples = dict()
- values = dict()
- cls = self.class_.get(session)
- stats = [getattr(cls, x) for x in self.stats.get(session)]
+ def get_x_labels(self, duration, intervals, step):
+ x_step = duration / intervals
+ labels = list()
+ for i in range(0, intervals + 1):
+ label = dict()
+ if i % step == 0:
+ value = duration - i * x_step
+ text = fmt_duration_brief(value)
- width = self.container_width.get(session)
- height = self.container_height.get(session)
- mode = self.mode.get(session)
- duration = self.get_duration(session)
- time_period = self.duration.get(session)
- delta = self.get_delta(session)
- interval = self.get_interval(session, duration, width)
- #interval = 1
- method = self.method.get(session)
-
- def fetch_samples(object, dur, interval, method, mode, delta):
- if mode == "rate":
- method = None # don't do averaging
- if delta: # need more than 1 point for calculating rate
- dur *= 2;
- for stat in stats:
- os = stat.samples(object, dur, interval, method)
- ns = list()
- prev = None
-
- for sample in reversed(os):
- if prev is not None:
- rate = calc_rate(sample[1], prev[1],
- secs(sample[0]), secs(prev[0]))
-
- ns.insert(0, (sample[0], rate, None))
-
- prev = sample
-
- samples[stat] = ns
+ label["text"] = text
else:
- for stat in stats:
- samples[stat] = stat.samples(object, dur, interval, method)
+ label["text"] = ""
+ label["x"] = i * x_step
- def get_max_min():
- # take stddev into account for max and min y values
- max_value = 0
- min_value = 0
- try:
- deviated_values = [[(nvl(x[1],0) + float(nvl(x[2],0))/2,
- nvl(x[1],0) - float(nvl(x[2],0))/2)
- for x in samples[stat]] for stat in stats]
- for deviated_list in deviated_values:
- if len(deviated_list):
- max_list = max(max(deviated_list)) # list of tuples
- max_value = max(max_list, max_value)
- min_list = min(min(deviated_list))
- min_value = min(min_list, min_value)
+ labels.append(label)
+ return labels
- max_value = round(max_value * 1.1 + 1)
+ def get_y_labels(self, absy, intervals, step):
+ y_step = absy / intervals
+ labels = list()
+ for i in range(0, intervals + 1):
+ label = dict()
- if min_value < 0:
- min_value = round(min_value * 1.1 - 1)
+ if i % step == 0:
+ value = int(round(i * y_step, 0))
- except:
- pass
- return max_value, min_value
+ if value >= 10000000:
+ svalue = "%im" % int(round(value / 1000000.0, -1))
+ elif value >= 10000:
+ svalue = "%ik" % int(round(value / 1000.0, -1))
+ else:
+ svalue = str(value)
- #print "fetching duration=%i time_period=%i" % (duration, time_period)
- fetch_samples(object, duration, interval, method, mode, delta)
- max_value, min_value = get_max_min()
-
- append = False
- if delta:
- axis_max = self.axis_max.get(session)
- vals_max = self.vals_max.get(session)
- axis_for_vals = round(float(vals_max) * 1.1 + 1)
- max_of_axiis = max(max_value, axis_for_vals)
- # the most recent value(s) changed the y-axis range
- if axis_max != max_of_axiis:
- #print "change in axis: axis_max=%s vals_max=%s max_new_values=%s
vals_axis=%s desired_axis=%s" % (str(axis_max), str(vals_max), str(max_value),
str(axis_for_vals), str(max_of_axiis))
- duration = time_period
- fetch_samples(object, duration, interval, method, mode, False)
- max_value, min_value = get_max_min()
+ label["text"] = svalue
else:
- append = True
+ label["text"] = ""
+ label["y"] = int(i * y_step)
- chart = Chart()
- chart.elements = list()
- tnow = time()
- for stat, color in zip(stats, colors):
- line = Element()
- line.type = "scatter_line"
- #line.dot_size = 3
- #line.dot_style = {"type": "solid-dot",
- # "dot-size": 5,
- # "halo-size": 0}
- line.colour = color
- line.width = 1
- line.text = mode == "rate" and "%s / sec" % stat.title or
stat.title
+ labels.append(label)
+ return labels
- vals = [{"x":int(time_period -(tnow - secs(dt))),
- "y":value,
- "uid": dt.strftime("%m%d%Y%H%M%S"),
- "tip": "<br>%s: #val#<br>Time: %s" %
(line.text, dt.strftime("%m/%d/%Y %H:%M:%S"))}
- for dt, value, dev in reversed(samples[stat])
- if value is not None]
- line.values = vals
- chart.elements.append(line)
-
- if append:
- chart.append = self.get_elapsed(session)
- #print "append: time_period=%i duration=%i elapsed=%f" %
(time_period, duration, float(self.get_elapsed(session)))
- return chart.create()
-
- chart.title.text = ""
- chart.title.style = "{text-align: left; font-weight: bold; font-size:
14px;}"
- chart.tooltip = {"colour": "#000033",
- "background": "#FFFFCC",
- "stroke": 1,
- "title": "{background-color: #000022; color:
#FFFFFF; font-size: 1em;}",
- "body": "{font-size: 10px; color:
#000000;}"
- }
+ def get_x_axis(self, duration):
x_intervals = 8
x_steps = 2
if duration == 600:
x_intervals = 10
steps = int(duration / x_intervals)
- chart.x_axis.colour = "#CCCCCC"
- chart.x_axis.grid_colour = "#DDDDDD"
- chart.x_axis.stroke = 1
- chart.x_axis.steps = steps
+ x_axis = Element()
+ x_axis.colour = "#CCCCCC"
+ x_axis.grid_colour = "#DDDDDD"
+ x_axis.stroke = 1
+ x_axis.steps = steps
xlbls = Element()
#xlbls.steps = steps
xlbls.size = 12
@@ -824,8 +779,10 @@
xlbls.rotate = 0.0001
lbls = self.get_x_labels(duration, x_intervals, x_steps)
xlbls.labels = lbls
- chart.x_axis.labels = xlbls
+ x_axis.labels = xlbls
+ return x_axis
+ def get_y_axis(self, max_value, min_value):
y_intervals = 6
absy = max_value - min_value
y_steps = int(absy / y_intervals)
@@ -834,7 +791,7 @@
y_steps = 3
# .swf won't show grid lines with a y_axis on the left
- chart.y_axis = {
+ y_axis = {
"min": int(min_value),
"max": int(max_value),
"steps": y_steps,
@@ -843,58 +800,253 @@
"colour": "#CCCCCC",
"grid-colour": "#DDDDDD",
"labels": {"labels": []}}
- chart.y_axis_right = {
+ y_axis_right = {
"min": int(min_value),
"max": int(max_value),
"tick-length": 0,
"stroke": 1,
"colour": "#BBBBBB",
"labels": {"colour": "#AAAAAA",
"labels": self.get_y_labels(absy, y_intervals, 2)}}
+
+ return y_axis, y_axis_right
+
+class StackedChart(FlashChart):
+ def __init__(self, app, name, page):
+ super(StackedChart, self).__init__(app, name, page)
+
+ self.points = self.Points(app, "points")
+ self.add_attribute(self.points)
+
+ def fetch_samples(self, object, dur, interval, method, mode, delta, stats):
+ samples = dict()
+
+ # don't average for stacked charts
+ interval = 1
+ for stat in stats:
+ samples[stat] = stat.samples(object, dur, interval, method)
+ return samples
+
+ def get_max_min(self, session, stats, samples):
+ max_value = 0
+ min_value = 0
+ points = dict()
+ values = dict()
+ collapsed = dict()
+
+ for stat in stats:
+ for sample in samples[stat]:
+ collapsed[sample[0]] = sample[1]
+
+ for t in collapsed:
+ if not t in points:
+ points[t] = list()
+ values[t] = 0
+ points[t].append(collapsed[t])
+ values[t] = values[t] + collapsed[t]
+ max_value = max(values[t], max_value)
+ min_value = min(values[t], min_value)
+
+ # save the accumulated values for each timestamp
+ self.points.set(session, points)
+
+ max_value = round(max_value * 1.1 + 1)
+ if min_value < 0:
+ min_value = round(min_value * 1.1 - 1)
+
+ return max_value, min_value
+
+ def get_chart(self, session, stats, samples, duration, max_value, min_value,
append):
+ time_period = self.page.duration.get(session)
+ chart = Chart()
+ chart.title.text = ""
chart.bg_colour = "#FFFFFF"
+ chart.elements = list()
+ element = Element()
+ element.type = "bar_stack"
+ element.colours = list(self.colors)
+
+ points = self.points.get(session)
+ values = list()
+ for point in points:
+ values.append(points[point])
+ element.values = values
+
+ keys = list()
+ for stat, color in zip(stats, self.colors):
+ key = dict()
+ key['colour'] = color
+ key['text'] = stat.title
+ keys.append(key)
+ element.keys = keys
+
+ element.tip = "#key#: #val#<br>Total: #total#"
+ chart.elements.append(element)
+
+ if append:
+ chart.append = self.get_elapsed(session)['value']
+ #print "append: time_period=%i duration=%i elapsed=%f" %
(time_period, duration, float(self.get_elapsed(session)['value']))
+ return chart
+ #chart.x_axis = self.get_x_axis(duration)
+ y_axis, y_axis_right = self.get_y_axis(max_value, min_value)
+ chart.y_axis = y_axis
+ chart.y_axis_right = y_axis_right
+
+ return chart
+
+ class Points(Attribute):
+ def get_default(self, session):
+ return dict()
+
+class AreaChart(FlashChart):
+ def fetch_samples(self, object, dur, interval, method, mode, delta, stats):
+ samples = dict()
+ if mode == "rate":
+ method = None # don't do averaging
+ if delta: # need more than 1 point for calculating rate
+ dur *= 4;
+ for stat in stats:
+ os = stat.samples(object, dur, interval, method)
+ ns = list()
+ prev = None
+
+ for sample in reversed(os):
+ if prev is not None:
+ rate = calc_rate(sample[1], prev[1],
+ secs(sample[0]), secs(prev[0]))
+
+ ns.insert(0, (sample[0], rate, None))
+
+ prev = sample
+
+ samples[stat] = ns
+ else:
+ for stat in stats:
+ samples[stat] = stat.samples(object, dur, interval, method)
+
+ return samples
+
+ def get_max_min(self, session, stats, samples):
+ # take stddev into account for max and min y values
+ max_value = 0
+ min_value = 0
+
+ for stat in stats:
+ for x in samples[stat]:
+ devmax = nvl(x[1], 0) + float(nvl(x[2], 0))
+ devmin = nvl(x[1], 0) - float(nvl(x[2], 0))
+ max_value = max(max_value, devmax)
+ min_value = min(min_value, devmin)
+
+ max_value = round(max_value * 1.1 + 1)
+ if min_value < 0:
+ min_value = round(min_value * 1.1 - 1)
+
+ return max_value, min_value
+
+ def get_chart(self, session, stats, samples, duration, max_value, min_value,
append):
+ time_period = self.page.duration.get(session)
+ mode = self.page.mode.get(session)
+
+ width = self.page.container_width.get(session)
+ dot_size = 1
+ halo_size = 0
+ line_width = 1
+ if width > 400:
+ dot_size = 3
+ halo_size = 1
+ line_width = 2
+
+ chart = Chart()
+ chart.bg_colour = "#FFFFFF"
+ chart.elements = list()
+ tnow = time()
+ for stat, color in zip(stats, self.colors):
+ line = Element()
+ line.type = "area"
+ line.fill = color
+ line.fill_alpha = 0.3
+ line.on_show.type = "No"
+
+ line.dot_style = {"type": "solid-dot",
+ "dot-size": dot_size,
+ "halo-size": halo_size}
+ line.colour = color
+ line.width = line_width
+ line.text = mode == "rate" and "%s / sec" % stat.title or
stat.title
+
+ vals = [{"x":int(time_period -(tnow - secs(dt))),
+ "y":value,
+ "uid": dt.strftime("%m%d%Y%H%M%S"),
+ "tip": "<br>%s: #val#<br>Time: %s" %
(line.text, dt.strftime("%m/%d/%Y %H:%M:%S"))}
+ for dt, value, dev in reversed(samples[stat])
+ if value is not None]
+ line.values = vals
+ chart.elements.append(line)
+
+ if append:
+ chart.append = self.get_elapsed(session)['value']
+ #print "append: time_period=%i duration=%i elapsed=%f" %
(time_period, duration, float(self.get_elapsed(session)['value']))
+ return chart
+
+ chart.title.text = ""
+ chart.title.style = "{text-align: left; font-weight: bold; font-size:
14px;}"
+ chart.tooltip = {"colour": "#000033",
+ "background": "#FFFFCC",
+ "stroke": 1,
+ "title": "{background-color: #000022; color:
#FFFFFF; font-size: 1em;}",
+ "body": "{font-size: 10px; color:
#000000;}"
+ }
+
+ chart.x_axis = self.get_x_axis(duration)
+ y_axis, y_axis_right = self.get_y_axis(max_value, min_value)
+ chart.y_axis = y_axis
+ chart.y_axis_right = y_axis_right
+
#print "sending entire sample set with y_axis.max=%i" %
chart.y_axis["max"]
- return chart.create()
+ return chart
- def get_x_labels(self, duration, intervals, step):
- x_step = duration / intervals
- labels = list()
- for i in range(0, intervals + 1):
- label = dict()
- if i % step == 0:
- value = duration - i * x_step
- text = fmt_duration_brief(value)
+class StatFlashPage(StatChartPage):
+ def __init__(self, app, name):
+ super(StatFlashPage, self).__init__(app, name)
- label["text"] = text
- else:
- label["text"] = ""
- label["x"] = i * x_step
+ # number of milliseconds since the last update
+ self.elapsed = Parameter(app, "elapsed")
+ self.elapsed.default = None
+ self.add_parameter(self.elapsed)
- labels.append(label)
- return labels
+ # the current y-axis max reported by the .swf
+ self.axis_max = IntegerParameter(app, "amax")
+ self.axis_max.default = 0;
+ self.add_parameter(self.axis_max)
- def get_y_labels(self, absy, intervals, step):
- y_step = absy / intervals
- labels = list()
- for i in range(0, intervals + 1):
- label = dict()
+ # the max y value reported by the .swf
+ self.vals_max = IntegerParameter(app, "vmax")
+ self.vals_max.default = 0;
+ self.add_parameter(self.vals_max)
- if i % step == 0:
- value = int(round(i * y_step, 0))
+ self.chart_type = Parameter(app, "type")
+ self.chart_type.default = "area"
+ self.add_parameter(self.chart_type)
- if value >= 10000000:
- svalue = "%im" % int(round(value / 1000000.0, -1))
- elif value >= 10000:
- svalue = "%ik" % int(round(value / 1000.0, -1))
- else:
- svalue = str(value)
+ def get_content_type(self, session):
+ return "text/plain"
- label["text"] = svalue
- else:
- label["text"] = ""
- label["y"] = int(i * y_step)
+ def do_render(self, session):
+ object = self.get_object(session)[0]
+ cls = self.class_.get(session)
+ stats = [getattr(cls, x) for x in self.stats.get(session)]
- labels.append(label)
- return labels
+ chart = self.chart_factory(self.chart_type.get(session))
+ return chart.create(session, object, stats)
+ def chart_factory(self, chart_type):
+ if chart_type == "area":
+ chart_obj = AreaChart(self.app, chart_type, self)
+ elif chart_type == "stacked":
+ chart_obj = StackedChart(self.app, chart_type, self)
+
+ return chart_obj
+
class StatStackedPage(StatChartPage):
def do_render(self, session):
object = self.get_object(session)[0]