Author: croberts
Date: 2013-03-21 15:01:16 +0000 (Thu, 21 Mar 2013)
New Revision: 5749
Modified:
trunk/cumin/bin/cumin-web
trunk/cumin/python/cumin/config.py
trunk/cumin/python/cumin/grid/dashboard.py
trunk/cumin/python/cumin/grid/dashboard.strings
trunk/cumin/python/cumin/model.py
trunk/cumin/python/cumin/stat.py
trunk/cumin/python/cumin/stat.strings
trunk/cumin/resources/app.css
trunk/cumin/resources/app.js
trunk/wooly/python/wooly/pages.strings
Log:
Changes for BZ 924289. Notably, the "history" radio button is history in favor
of "Accounting", but will only show up when plumage data is present. The
acccounting group chart is also totally revamped.
Modified: trunk/cumin/bin/cumin-web
===================================================================
--- trunk/cumin/bin/cumin-web 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/bin/cumin-web 2013-03-21 15:01:16 UTC (rev 5749)
@@ -73,6 +73,11 @@
def set_kerberos_configs(cumin, values):
cumin.kerberos_realm = values.kerberos_realm
+def set_plumage_configs(cumin, values):
+ cumin.plumage_host = values.plumage_host
+ cumin.plumage_port = values.plumage_port
+
+
def set_hadoop_configs(cumin, values):
cumin.hadoop_bin_tarball = values.hadoop_bin_tarball
@@ -216,6 +221,7 @@
set_ldap_configs(cumin, values)
set_kerberos_configs(cumin, values)
set_hadoop_configs(cumin, values)
+ set_plumage_configs(cumin, values)
# Not used right now
#cumin.auth_create_ondemand = values.auth_create_ondemand
Modified: trunk/cumin/python/cumin/config.py
===================================================================
--- trunk/cumin/python/cumin/config.py 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/python/cumin/config.py 2013-03-21 15:01:16 UTC (rev 5749)
@@ -161,6 +161,12 @@
param = ConfigParameter(self, "debug", bool)
param.default = False
+
+ param = ConfigParameter(self, "plumage_host", str)
+ param.default = "localhost"
+
+ param = ConfigParameter(self, "plumage_port", int)
+ param.default = 27017
class BrokeredConfigSection(CuminConfigSection):
def __init__(self, config, name, strict_section=False):
Modified: trunk/cumin/python/cumin/grid/dashboard.py
===================================================================
--- trunk/cumin/python/cumin/grid/dashboard.py 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/python/cumin/grid/dashboard.py 2013-03-21 15:01:16 UTC (rev 5749)
@@ -1,4 +1,5 @@
import logging
+import re
from wooly import Widget, Attribute
from wooly.util import StringCatalog, Writer
@@ -7,6 +8,7 @@
from wooly.widgets import RadioModeSet, WidgetSet
from wooly.template import WidgetTemplate
from wooly.forms import StringInput
+from wooly.sql import SqlOperation
from parsley.stringex import rpartition
from rosemary.sqlquery import SqlQueryOptions
@@ -17,7 +19,9 @@
ObjectLinkColumn, CsvStatsExporter, ExportButton, MonitorSelfAgeColumn
from cumin.parameters import YoungestAttribute
from cumin.util import rgb_to_string, xml_escape
+from cumin.model import PlumageChecker
+
strings = StringCatalog(__file__)
log = logging.getLogger("cumin.dashboard")
@@ -36,61 +40,35 @@
capacity = DashboardCapacity(app, "c")
self.add_tab(capacity)
- history = DashboardHistory(app, "h")
- self.add_tab(history)
+ # if plumage data is available, we will have the accounting radio button,
+ # otherwise, it will not appear
+ plumageChecker = PlumageChecker(app)
+ havePlumage = plumageChecker.isPlumageAvailable()
+
+ if havePlumage:
+ accounting = DashboardAccounting(app, "a")
+ self.add_tab(accounting)
def render_title(self, session):
return "Overview"
-class DashboardHistory(Widget):
+class DashboardAccounting(Widget):
def __init__(self, app, name):
- super(DashboardHistory, self).__init__(app, name)
+ super(DashboardAccounting, self).__init__(app, name)
charts = WidgetSet(app, "charts")
self.add_child(charts)
-
- chart = self.PoolEfficiencyChart(app, "eff",
app.model.com_redhat_grid_plumage.OSUtil)
- chart.stats = ["efficiency"]
- chart.max_samples = 250
- chart.duration.param.default = "86400"
- charts.add_child(chart)
+
+ chartholder = AccountingChartWidget(app, "accchart")
+ charts.add_child(chartholder)
- chart = self.PoolMemoryChart(app, "fmem",
app.model.com_redhat_grid_plumage.OSUtil)
- chart.stats = ["freemem", "usedmem"]
- chart.colors = ["#992600", "#009926"]
- chart.max_samples = 250
- chart.duration.param.default = "86400"
- chart.chart_type = "stacked"
- charts.add_child(chart)
-
- chart = self.PoolCpuChart(app, "fcpu",
app.model.com_redhat_grid_plumage.OSUtil)
- chart.stats = [ "freecpu", "usedcpu"]
- chart.colors = ["#992600", "#009926"]
- chart.max_samples = 250
- chart.duration.param.default = "86400"
- chart.chart_type = "stacked"
- charts.add_child(chart)
-
- chart = self.UserUsageChart(app, "useruse",
app.model.com_redhat_grid_plumage.Accountant)
- chart.stats = ["resused"]
- chart.max_samples = 250
- filter = ReportingChart.FilterInput(app, "namefilter",
app.model.com_redhat_grid_plumage.Accountant,
app.model.com_redhat_grid_plumage.Accountant.user.name, "userc", "Filter
user")
- chart.filters.append(filter)
- chart.add_child(filter)
- chart.duration.param.default= "86400"
- charts.add_child(chart)
-
def render_title(self, session):
- return "History"
+ return "Accounting"
class PoolMemoryChart(ReportingChart):
def render_title(self, session):
return "Pool memory"
- class PoolCpuChart(ReportingChart):
- def render_title(self, session):
- return "Pool cpus"
-
class PoolEfficiencyChart(ReportingChart):
def render_title(self, session):
return "Pool efficiency"
@@ -101,7 +79,99 @@
class AccountingChart(ReportingChart):
def render_title(self, session):
- return "Usage by parent accounting group"
+ return "Usage by accounting group (cpus)"
+
+class AccountingChartWidget(Widget):
+ def __init__(self, app, name):
+ super(AccountingChartWidget, self).__init__(app, name)
+
+ control = self.AccountingChartControl(app, "actrl")
+ self.add_child(control)
+
+ chart = self.AccountingChart(app, "accuse",
app.model.com_redhat_grid_plumage.Accountant)
+ chart.max_samples = 250
+ chart.chart_type = "genusechart"
+ chart.duration.param.default = "86400"
+ chart.leftlegend = False
+ chart.height = 165
+ chart.width = 440
+ self.add_child(chart)
+
+ class AccountingChartControl(Widget):
+ def __init__(self, app, name):
+ super(AccountingChartWidget.AccountingChartControl, self).__init__(app,
name)
+ self.app = app
+ self.groupLoader = AccGroupSqlOperation(app)
+
+ def render_chart_div_name(self, session):
+ return self.parent.children_by_name['accuse'].path
+
+ def render_accounting_groups(self, session):
+ display_groups = []
+ trees = {}
+ groups_string = ""
+ groups = self.groupLoader.execute(session).fetchall()
+ groups = [g[0] for g in groups]
+ top_groups = self.groupLoader.getTopLevelGroups(groups)
+ for t_group in top_groups:
+ trees[t_group] = self.groupLoader.build_tree(groups, t_group)
+
+ groups_string = self.processSubTree(trees, "", True)
+
+ return groups_string
+
+ def processSubTree(self, subtree, startString, initialUL=False):
+ if initialUL:
+ subtree_string = "%s <ul id=\"%s\">" %
(startString, self.path)
+ else:
+ subtree_string = "%s <ul>" % startString
+ for node in subtree:
+ if len(subtree[node]) > 0:
+ subtree_string += """<li><a
href="#">%s</a>""" % node.replace("<",
"<").replace(">", ">")
+ subtree_string = self.processSubTree(subtree[node], subtree_string)
+ subtree_string += "</li>"
+ else:
+ subtree_string += """<li><a
href="#">%s</a></li>""" %
node.replace("<", "<").replace(">",
">")
+ subtree_string += "</ul>"
+ return subtree_string
+
+
+ class AccountingChart(ReportingChart):
+ def render_title(self, session):
+ return "Usage by accounting group (cpus)"
+
+class AccGroupSqlOperation(SqlOperation):
+ def __init__(self, app):
+ super(AccGroupSqlOperation, self).__init__(app)
+
+ def get_connection(self, session):
+ return self.app.database.get_read_connection()
+
+ def getTopLevelGroups(self, groups):
+ topLevelGroups = []
+ for group in groups:
+ split_name = group.split(".",1)
+ if split_name[0] not in topLevelGroups:
+ topLevelGroups.append(split_name[0])
+ return topLevelGroups
+
+ def build_tree(self, nodes, topNode):
+ tree = {}
+ self.build_tree_recursive(tree, topNode, nodes)
+
+ return tree
+
+ def build_tree_recursive(self, tree, parent, nodes):
+ children = []
+ for n in nodes:
+ (nparent, sep, leaf) = n.rpartition(".")
+ if nparent == parent:
+ children.append(n)
+
+ for child in children:
+ tree[child] = {}
+ self.build_tree_recursive(tree[child], child, nodes)
+
class DashboardSummary(Widget):
def __init__(self, app, name, collector):
@@ -110,8 +180,19 @@
job_data = DashboardOverviewJobSummary(app, "job_info")
self.add_child(job_data)
- slot_data = GridSlotsSummary(app, "host_info", collector)
- self.add_child(slot_data)
+ plumageChecker = PlumageChecker(app)
+ havePlumage = plumageChecker.isPlumageAvailable()
+
+ if havePlumage:
+ chart = self.PoolCpuChart(app, "host_info",
app.model.com_redhat_grid_plumage.OSUtil)
+ chart.stats = [ "usedcpu", "freecpu"]
+ chart.max_samples = 250
+ chart.duration.param.default = "86400"
+ chart.chart_type = "stacked"
+ self.add_child(chart)
+ else:
+ slot_data = GridSlotsSummary(app, "host_info", collector)
+ self.add_child(slot_data)
neg_through = NegotiatorJobThroughput(app, "negotiator_throughput")
self.add_child(neg_through)
@@ -128,6 +209,10 @@
def render_title(self, session):
return "Summary"
+ class PoolCpuChart(ReportingChart):
+ def render_title(self, session):
+ return "Pool cpus"
+
class DashboardPerformance(Widget):
def __init__(self, app, name):
super(DashboardPerformance, self).__init__(app, name)
Modified: trunk/cumin/python/cumin/grid/dashboard.strings
===================================================================
--- trunk/cumin/python/cumin/grid/dashboard.strings 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/python/cumin/grid/dashboard.strings 2013-03-21 15:01:16 UTC (rev 5749)
@@ -7,6 +7,11 @@
</div>
<div style="clear:both;"></div>
+[DashboardSummary.css]
+div.DashboardSummary div.ReportingStatValueChart {
+ float:left;
+}
+
[MonitorSelfStats.css]
div.MonitorSelfStats div.data {
margin-left: 2em;
@@ -312,20 +317,89 @@
</div>
</div>
-[DashboardHistory.css]
-div.DashboardHistory ul.WidgetSet {
+
+
+[DashboardAccounting.html]
+<div class="{class}">
+ {charts}
+</div>
+<div style="clear:both;"></div>
+
+[DashboardAccounting.css]
+div.DashboardAccounting ul.WidgetSet {
padding: 0;
margin: 0;
list-style: none;
}
-div.DashboardHistory ul.WidgetSet li {
- display: inline;
- float: left;
-}
-[DashboardHistory.html]
+[AccountingChartWidget.html]
<div class="{class}">
- {charts}
+ {actrl}
+ {accuse}
</div>
-<div style="clear:both;"></div>
+
+[AccountingChartWidget.css]
+div.AccountingChartWidget>div {
+ float: left;
+}
+
+
+[AccountingChartControl.html]
+<div id="controlTree" class="{class}"></div>
+<div style="display:none;">
+ {accounting_groups}
+</div>
+
+<script type="text/javascript">
+//<![CDATA[
+var controlTree;
+window.addEvent('domready', function () {
+ controlTree = new MooTreeControl({
+ div: 'controlTree',
+ mode: null,
+ grid: true,
+ onSelect: function(node, state) {
+ //do nothing
+ },
+ onExpand: function(node, state) {
+ var exposed_nodes = [];
+ for(i=0; i < node.nodes.length; i++) {
+ exposed_nodes[i] = node.nodes[i].text;
+ }
+ var chart = cumin.getChart("{chart_div_name}");
+ var chart_url =
chart.getParent().getElement("a").getAttribute("href");
+ var nodes = [];
+ getExposedNodes(nodes, this.root);
+ var new_url = get_new_chart_url("{chart_div_name}", chart_url,
"groupex", nodes);
+ chart.getParent().getElements('a')[chart_href].set('href', new_url);
+ drawSingleChart(chart, true);
+ }
+ },{
+ text: 'Accounting Groups',
+ open: true
+ });
+ controlTree.adopt('{id}');
+
+});
+
+function getExposedNodes(nodeList, node) {
+ if(node.open == true) {
+ for(var i=0, count=node.nodes.length; i < count; i++) {
+ getExposedNodes(nodeList, node.nodes[i]);
+ }
+ } else {
+ nodeList.push(node.text);
+ }
+}
+//]]>
+</script>
+
+[AccountingChartControl.css]
+div.AccountingChartControl {
+ width : 300px;
+}
+
+[AccGroupSqlOperation.sql]
+select distinct(agroup) from
"com.redhat.grid.plumage"."Accountant_samples"
+ order by agroup asc;
Modified: trunk/cumin/python/cumin/model.py
===================================================================
--- trunk/cumin/python/cumin/model.py 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/python/cumin/model.py 2013-03-21 15:01:16 UTC (rev 5749)
@@ -950,3 +950,18 @@
class NegotiatorStaticGroupConfigValuesStore(NegotiatorGroupConfigValuesStore):
def __init__(self, model, negotiator, groups, config):
super(NegotiatorStaticGroupConfigValuesStore, self).__init__(model, negotiator,
groups, config)
+
+# Utility class to check for the plumage (data store)
+class PlumageChecker(object):
+ def __init__(self, app):
+ self.app = app
+
+ def isPlumageAvailable(self):
+ plumageAvail = False
+ try:
+ from pymongo import Connection
+ self.connection = Connection(self.app.plumage_host, self.app.plumage_port)
+ plumageAvail = True
+ except:
+ log.info("No plumage data store connection achieved.")
+ return plumageAvail
\ No newline at end of file
Modified: trunk/cumin/python/cumin/stat.py
===================================================================
--- trunk/cumin/python/cumin/stat.py 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/python/cumin/stat.py 2013-03-21 15:01:16 UTC (rev 5749)
@@ -10,6 +10,7 @@
from wooly.pages import HtmlPage
from wooly.parameters import IntegerParameter, ListParameter, StringParameter,\
BooleanParameter
+from wooly.sql import SqlOperation
from cumin.formats import fmt_bytes, fmt_duration_brief
from cumin.model import CuminStatistic, SamplesSqlAdapter
@@ -288,6 +289,10 @@
return self.render_id(session).replace(".", "_")
class ReportingChart(ReportingStatValueChart):
+ def __init__(self, app, name, cls):
+ super(ReportingChart, self).__init__(app, name, cls)
+ self.leftlegend = True #hack to force "leftlegend" class on containing
div
+
def get_flash_name(self, session):
return "chart.json"
@@ -317,6 +322,16 @@
def render_id_nodots(self, session):
return self.render_id(session).replace(".", "_")
+ def render_leftlegend(self, session):
+ if self.leftlegend:
+ return "leftlegend"
+ return ""
+
+ def render_legendmargin(self, session):
+ if not self.leftlegend:
+ return "legendmargin"
+ return ""
+
def render_filters(self, session):
filtertext = ""
for f in self.filters:
@@ -814,7 +829,14 @@
return chart.create()
class FlashLineChart(Widget):
+ legendCols = 6
one_day = 24 * 60 * 60
+ stackSeries = False
+ legendLocation = "n"
+ fillAndStroke = True
+ gridPadding = "{top:35, right:20, bottom:20, left:20}"
+ fillAlpha = 0.1
+
def __init__(self, app, name, page):
super(FlashLineChart, self).__init__(app, name)
self.page = page
@@ -912,7 +934,7 @@
msg = "Chart parameters: duration: %s end_secends_ago: %s interval:
%s" % (str(duration), str(end_seconds_ago), str(interval))
log.debug(msg)
# get the most recent samples
- samples = self.fetch_samples(adapter, duration, interval,
+ samples = self.fetch_samples(session, adapter, duration, interval,
self.fix_method(method, mode),
mode, delta, stats,
end_seconds_ago=end_seconds_ago)
@@ -929,7 +951,7 @@
if axis_max != max_of_axiis:
msg = "Y-Axis changed. New chart parameters: time_span: %s
end_secends_ago: %s interval: %s" % (str(time_span), str(end_seconds_ago),
str(interval))
log.debug(msg)
- samples = self.fetch_samples(adapter, time_span, interval,
+ samples = self.fetch_samples(session, adapter, time_span, interval,
self.fix_method(method, mode),
mode, False, stats,
end_seconds_ago=end_seconds_ago)
@@ -943,13 +965,17 @@
# create the chart dict
chart = self.get_chart(session, adapter, stats, samples, time_span, max_value,
min_value, append, end_seconds_ago)
#print "**********\n"+chart.create()
- chart.colors = self.colors
chart.graph_div = "%s_chart" % chart.id.replace(".",
"_")
- # doing this the ugly way for python 2.4
if self.stackSeries == True:
- chart.stackSeries = 1
+ chart.stackSeries = 1
else:
chart.stackSeries = 0
+ chart.legendLocation = self.legendLocation
+ chart.colors = self.get_colors(samples, session)
+ chart.legendCols = self.legendCols
+ chart.fillAndStroke = self.fillAndStroke
+ chart.gridPadding = self.gridPadding
+ chart.fillAlpha = self.fillAlpha
return chart.create()
def get_y_labels(self, absy, intervals, step):
@@ -1006,7 +1032,7 @@
return y_axis, y_axis_right
- def fetch_samples(self, adapter, dur, interval, method, mode, delta, stats,
end_seconds_ago=0):
+ def fetch_samples(self, session, adapter, dur, interval, method, mode, delta, stats,
end_seconds_ago=0):
return dict()
def get_max_min(self, session, stats, samples):
@@ -1026,11 +1052,11 @@
# get more samples than needed to allow chart to clip correctly (explains the +
600)
samples[stat] = adapter[stat].samples(stat, dur+600, interval, method,
secs2=end_seconds_ago, delta=delta)
- def fetch_samples(self, adapter, dur, interval, method, mode, delta, stats,
end_seconds_ago=0):
+ def fetch_samples(self, session, adapter, dur, interval, method, mode, delta, stats,
end_seconds_ago=0):
samples = dict()
if mode == "rate":
for stat in stats:
- os = adapter[stat].samples(stat, dur+600, interval, method,
secs2=end_seconds_ago, delta=delta)
+ os = adapter.samples(stat, dur+600, interval, method,
secs2=end_seconds_ago, delta=delta)
ns = list()
prev = None
@@ -1168,9 +1194,195 @@
new_samples.append((dt, value, dev))
samples[stat] = new_samples
+ def get_colors(self, samples, session):
+ return self.colors
+
class ReportAreaChart(AreaChart):
pass
+class GeneralUseChart(AreaChart):
+ def __init__(self, app, name, page):
+ super(GeneralUseChart, self).__init__(app, name, page)
+ self.stackSeries = True
+ self.legendLocation = "e"
+ self.fillAndStroke = False
+ self.gridPadding = "{top:10, right:20, bottom:20, left:20}" #gets
eval'ed in js to form gridPadding object
+ self.legendCols = 2
+ self.fillAlpha = 1
+ self.accGroups = self.AccGroupSqlOperation(app)
+ self.accUse = self.AccGroupUseSqlOperation(app, self)
+
+ self.colors = ("#7eb5d6", "#ad7460", "#397249",
"#88382d", "#628b61", "#644436",\
+ "#2a75a9", "#dfc184", "#a84a5c",
"#9cb770", "#ec633f", "#2f2f2f",\
+ "#274257", "#8f6048", "#d6a354",
"#ccb647", "#f9d654", "#4d4e24")
+
+ def fetch_samples(self, session, adapter, dur, interval, method, mode, delta, stats,
end_seconds_ago=0):
+ samples = dict()
+
+ groups_expanded = self.page.group_expand.get(session)
+ #use groups from url, if present, otherwise grab "all" form database
+ groups = groups_expanded if len(groups_expanded) > 0 else
self.getTopLevelGroups(self.accGroups.execute(session).fetchall())
+ groups = sorted(groups)
+ for group in groups:
+ group_name = group if isinstance(group, unicode) or isinstance(group, str)
else group[0]
+ result = self.accUse.execute(session, group_name, dur, interval).fetchall()
+ if len(result) > 0:
+ samples[group_name] = result
+
+ return samples
+
+ def getTopLevelGroups(self, groups):
+ topLevelGroups = []
+ for group in groups:
+ group = group if isinstance(group, unicode) or isinstance(group, str) else
group[0]
+ split_name = group.split(".",1)
+ if split_name[0] not in topLevelGroups:
+ topLevelGroups.append(split_name[0])
+
+ return topLevelGroups
+
+ def get_colors(self, samples, session):
+ ''' takes the chart's default set of colors and maps them
+ to a stat name (group name) to provide persistent colors
+ in the resultant chart even when switching groups around
+ '''
+ colors = []
+ trees = {}
+ color_map = {}
+ all_groups = []
+
+ # figure out what all of our groups are
+ groups = self.accGroups.execute(session).fetchall()
+ groups = [g[0] for g in groups]
+ top_groups = self.getTopLevelGroups(groups)
+ for t_group in top_groups:
+ trees[t_group] = self.build_tree(groups, t_group)
+ self.flatten_tree(trees, all_groups)
+ all_groups = sorted(all_groups)
+
+ # create a designated color for each group
+ for i, group in enumerate(all_groups):
+ color_map[group] = self.colors[i]
+
+ # For all displayed stats, pull the mapped color into the array
+ sample_keys = samples.keys()
+ for stat in sorted(sample_keys):
+ colors.append(color_map[stat])
+ if len(colors) == 0:
+ colors.append("#000000") #default no data color
+
+ return colors
+
+ def flatten_tree(self, dictIn, listOut):
+ for key, value in dictIn.iteritems():
+ if isinstance(value, dict) and len(value) > 0: # If value itself is
dictionary
+ if key not in listOut:
+ listOut.append(key)
+ self.flatten_tree(value, listOut)
+ else:
+ listOut.append(key)
+
+ def get_line_title(self, stat):
+ return stat.replace("<", "").replace(">",
"")
+
+ def fix_samples(self, samples):
+ # each set of samples needs to be the same length and contain the same x-values
+ x_vals = list()
+ for sample in samples:
+ for point in samples[sample]:
+ if point[0] not in x_vals:
+ x_vals.append(point[0])
+ for sample in samples:
+ for x in x_vals:
+ try:
+ if len(samples[sample]) == 0 or [item[0] for item in
samples[sample]].index(x) < 0:
+ samples[sample].append((x, 0, None))
+ except ValueError, e:
+ samples[sample].append((x, 0, None))
+
+
+ def make_chart_lines(self, session, chart, line_type, stats, dot_size, halo_size,
line_width, samples, duration, end_secs, mode):
+ chart.elements = list()
+ self.fix_samples(samples)
+
+ for stat in sorted(samples):
+ line = Element()
+ line.type = line_type
+ line.fill_alpha = self.alpha
+
+ line.width = line_width
+ tip_title = stat
+ line.text = mode == "rate" and "%s / sec" % tip_title or
self.get_line_title(stat)
+
+ vals = self.get_vals(session, samples, stat, line.text, duration, end_secs)
+ line.values = vals
+ chart.elements.append(line)
+
+ def get_vals(self, session, samples, stat, text, duration, end_secs):
+ tnow = time()
+ vals = list()
+
+ min_dt = tnow - duration - end_secs
+ for dt, value, dev in samples[stat]:
+ if value is not None:
+ vals.append({"dt": secs(dt), "y": value})
+ if secs(dt) < min_dt:
+ break
+
+ #here, we sort by dt to be sure that our graphs are valid
+ vals.sort(key=lambda stat: stat["dt"], reverse=False)
+ return vals
+
+ def build_tree(self, nodes, topNode):
+ tree = {}
+ self.build_tree_recursive(tree, topNode, nodes)
+ return tree
+
+ def build_tree_recursive(self, tree, parent, nodes):
+ children = []
+ for n in nodes:
+ (nparent, sep, leaf) = n.rpartition(".")
+ if nparent == parent:
+ children.append(n)
+
+ for child in children:
+ tree[child] = {}
+ self.build_tree_recursive(tree[child], child, nodes)
+
+ class AccGroupUseSqlOperation(SqlOperation):
+ def __init__(self, app, chart):
+ super(GeneralUseChart.AccGroupUseSqlOperation, self).__init__(app)
+ self.chart = chart
+
+ def get_connection(self, session):
+ return self.app.database.get_read_connection()
+
+ def execute(self, session, group, duration, interval):
+ sql = self.render_sql(session, [group, duration, interval])
+
+ return self.do_execute(session, sql)
+
+ def render_sql(self, session, args):
+ writer = Writer()
+ self.sql_tmpl.render(writer, session, args)
+ return writer.to_string()
+
+ def render_accgroup(self, session, args):
+ return args[0]
+
+ def render_duration(self, session, args):
+ return args[1]
+
+ def render_interval(self, session, args):
+ return args[2]
+
+ class AccGroupSqlOperation(SqlOperation):
+ def __init__(self, app):
+ super(GeneralUseChart.AccGroupSqlOperation, self).__init__(app)
+
+ def get_connection(self, session):
+ return self.app.database.get_read_connection()
+
class StackedAreaChart(AreaChart):
colors = ('#FFABAB', '#ABABFF', '#ABFFAB', '#FFABFF',
'#FFFFAB', '#ABFFFF', '#ABABAB')
def __init__(self, app, name, page):
@@ -1179,7 +1391,8 @@
self.points = self.Points(app, "points")
self.add_attribute(self.points)
- self.alpha = 1
+ self.fillAlpha = 0.7
+ self.fillAndStroke = False
self.stackSeries = True
# removed get_vals method since the new way of rendering stacked charts
@@ -1239,12 +1452,12 @@
self.elapsed.default = None
self.add_parameter(self.elapsed)
- # the current y-axis max reported by the .swf
+ # the current y-axis max reported
self.axis_max = IntegerParameter(app, "amax")
self.axis_max.default = 0;
self.add_parameter(self.axis_max)
- # the max y value reported by the .swf
+ # the max y value reported
self.vals_max = IntegerParameter(app, "vmax")
self.vals_max.default = 0;
self.add_parameter(self.vals_max)
@@ -1291,6 +1504,13 @@
self.group_selection = Parameter(app, "groupc")
self.group_selection.default = None
self.add_parameter(self.group_selection)
+
+ groupex_val = Parameter(app, "gp")
+ self.add_parameter(groupex_val)
+
+ self.group_expand = ListParameter(app, "groupex", groupex_val)
+ self.group_expand.default = []
+ self.add_parameter(self.group_expand)
def get_content_type(self, session):
return "text/plain"
@@ -1306,15 +1526,15 @@
chart_obj = AreaChart(self.app, chart_type, self)
elif chart_type == "stacked":
chart_obj = StackedAreaChart(self.app, chart_type, self)
- #chart_obj = StackedChart(self.app, chart_type, self)
elif chart_type == "percent":
chart_obj = PercentAreaChart(self.app, chart_type, self)
elif chart_type == "pie":
chart_obj = FlashPieChart(self.app, chart_type, self)
elif chart_type == "reportarea":
- chart_obj = ReportAreaChart(self.app, chart_type, self)
+ chart_obj = ReportAreaChart(self.app, chart_type, self)
+ elif chart_type == "genusechart":
+ chart_obj = GeneralUseChart(self.app, chart_type, self)
-
return chart_obj
class PercentAreaChart(AreaChart):
Modified: trunk/cumin/python/cumin/stat.strings
===================================================================
--- trunk/cumin/python/cumin/stat.strings 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/python/cumin/stat.strings 2013-03-21 15:01:16 UTC (rev 5749)
@@ -137,6 +137,30 @@
}
[StatValueChart.css]
+/* This gives us a left justified 'n' position for our legend, just have the
container div use the leftlegend class */
+div.leftlegend>table.jqplot-table-legend{
+ left:0px !important;
+}
+
+.jqplot-cursor-tooltip {
+ border: 1px solid #cccccc;
+ font-size: 0.75em;
+ white-space: nowrap;
+ background: rgba(255,255,255,0.9);
+ padding: 1px;
+ z-index:10;
+}
+
+.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip {
+ border: 1px solid #cccccc;
+ font-size: 0.75em;
+ white-space: nowrap;
+ background: rgba(255,255,255,0.9);
+ padding: 1px;
+ z-index:10;
+}
+
+
div.StatValueChart {
font-size: 0.9em;
margin: 0 0 1.5em 0;
@@ -261,7 +285,7 @@
setTimeout(function () {changeDuration(state, a, id, attempt+1);}, 100);
return false;
}
- newurl = get_chart_url(id,
$(chart).getParent().getElements('a')[chart_href].get('href'), state);
+ newurl = get_new_chart_url(id,
$(chart).getParent().getElements('a')[chart_href].get('href'),
'duration', state);
$(chart).getParent().getElements('a')[chart_href].set('href',
newurl);
drawSingleChart(chart, true);
}
@@ -348,7 +372,7 @@
<div>
<!-- don't put this data url inside the holder div, jqplot will kill it
on a zoom -->
<a rel="api" type="application/json"
href="{href}" style="display:none">Data</a>
- <div id="{id_nodots}_chart" class="jqplotgraph leftlegend"
style="height:{height};width:{width};">
+ <div id="{id_nodots}_chart" class="jqplotgraph {leftlegend}
{legendmargin}" style="height:{height};width:{width};">
<div class="loading"
style="display:none;"><span>Loading...</span></div>
</div>
</div>
@@ -358,6 +382,10 @@
</div>
</div>
+[ReportingChart.css]
+div.ReportingStatValueChart div.legendmargin table.jqplot-table-legend {
+ margin-left: 30px;
+}
[ReportingChart.javascript]
function updateFilter(filter, a, id, param, attempt) {
@@ -407,20 +435,6 @@
return false;
}
-function get_new_chart_url(id, url, param, value) {
- var chart = cumin.getChart(id);
- if (chart == null)
- return false;
-
- var branch = wooly.session.branch(url);
- var now = new Date().getTime();
- branch['elapsed'] = now; // force an entire update
- branch[param] = value;
- url = branch.marshal();
-
- return url;
-}
-
[FilterInput.html]
<!-- <input id="{id}" type="text"
name="{name}"></input><input type="button"
value="Update filter" {onclick}/> -->
{title}: <select {onchange}>{user_list}</select>
@@ -445,3 +459,16 @@
<div id="{id}_chart">
</div>
</div>
+
+
+[AccGroupSqlOperation.sql]
+select distinct(agroup) from
"com.redhat.grid.plumage"."Accountant_samples"
+ order by agroup asc;
+
+[AccGroupUseSqlOperation.sql]
+select max("Accountant_samples"."ts") as interval_end,
+cast(max("Accountant_samples"."resused") as integer) as value,
+stddev("Accountant_samples"."resused") as dev from
"com.redhat.grid.plumage"."Accountant_samples"
+where ("Accountant_samples"."user" like '{accgroup}%') and
"Accountant_samples"."ts" >= now() - interval '{duration}
seconds'
+group by floor(extract(epoch from "Accountant_samples"."ts") /
{interval}) order by interval_end desc limit all offset 0;
+
\ No newline at end of file
Modified: trunk/cumin/resources/app.css
===================================================================
--- trunk/cumin/resources/app.css 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/resources/app.css 2013-03-21 15:01:16 UTC (rev 5749)
@@ -824,3 +824,25 @@
{
color: #9FCFFF;
}
+
+.mooTree_node {
+ font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+.mooTree_text {
+ padding-top: 3px;
+ height: 15px;
+}
+
+.mooTree_img {
+ float: left;
+ width: 18px;
+ height: 18px;
+ overflow: hidden;
+}
+
+.mooTree_selected {
+
+}
\ No newline at end of file
Modified: trunk/cumin/resources/app.js
===================================================================
--- trunk/cumin/resources/app.js 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/cumin/resources/app.js 2013-03-21 15:01:16 UTC (rev 5749)
@@ -590,8 +590,23 @@
dataContainer['x_axis_values'] = dataContainer['x_axis_values']
.append([ json.x_axis.labels.labels[i].text ]);
}
+
+ dataContainer['colors'] = json.colors;
+
dataContainer['stackSeries'] = json.stackSeries == 1 ? true:false;
- dataContainer['colors'] = json.colors;
+ dataContainer['legendLocation'] = json.legendLocation;
+ dataContainer['legendCols'] = json.legendCols;
+ dataContainer['fillAndStroke'] = json.fillAndStroke;
+ eval('var gridPad ='+json.gridPadding);
+ dataContainer['gridPadding'] = gridPad;
+ dataContainer['fillAlpha'] = json.fillAlpha;
+
+ if(dataContainer['labels'].length == 0) {
+ dataContainer['labels'][0] = "No data";
+ dataContainer['y_coordinate_values'][0] = [1,0];
+ dataContainer['colors'] = ["#009926"];
+ }
+
return dataContainer;
}
@@ -622,17 +637,17 @@
text: '',
show: false,
},
- gridPadding: {top:35, right:20, bottom:20, left:20},
+ gridPadding: dataContainer['gridPadding'],
grid: { background: '#FFFFFF' },
legend: {
- show:true,
- location: 'n',
+ show: dataContainer['labels'][0] == "No data" ? false:true,
+ location: dataContainer['legendLocation'],
renderer: $j.jqplot.EnhancedLegendRenderer,
yoffset: 0,
xoffset: 0,
placement: "outside",
rendererOptions:{
- numberColumns:6,
+ numberColumns:dataContainer['legendCols'],
seriesToggle:false,
disableIEFading:true
}
@@ -654,13 +669,13 @@
}
},
series: getSeries(dataContainer),
- stackSeries: dataContainer['stackSeries'],
+ stackSeries: true,
seriesColors: dataContainer['colors'],
seriesDefaults: {
lineWidth:1,
fill:true,
- fillAndStroke:true,
- fillAlpha: 0.1,
+ fillAndStroke:dataContainer['fillAndStroke'],
+ fillAlpha: dataContainer['fillAlpha'],
rendererOptions: {
highlightMouseOver: false,
highlightMouseDown: false,
@@ -730,11 +745,16 @@
allCharts[divName] = $j.jqplot(holder,
dataContainer['y_coordinate_values'],chartOptionsObject);
allCharts[divName].target.bind('jqplotResetZoom', cuminChartResetZoom);
allCharts[divName].target.bind('jqplotZoom', cuminChartZoom);
+ allCharts[divName].target.bind('jqplotDataHighlight', cuminChartDataClick);
startChartMonitor(divName);
}
} // end function drawChart
+cuminChartDataClick = function (ev, seriesIndex, pointIndex, data) {
+ //console.log('series: '+seriesIndex+', point: '+pointIndex+',
data: '+data);
+}
+
// called on the jqplotZoom event
cuminChartZoom = function(gridpos, datapos, plot, cursor) {
// console.log("cuminZoom");
@@ -755,6 +775,17 @@
// console.log("customPreDraw");
}
+function plot1ClickHandler(ev, gridpos, datapos, neighbor, plot) {
+ if(plot.targetId == "#bargraph") {
+
+ var column = getColumnLocation(plot,gridpos,line1.length);
+
+ //make2nd('doc'+plot.data[0][parseInt(column)][2],
plot.data[0][parseInt(column)][0]);
+
+ }
+}
+
+
// used to let us tweak the contents of the tooltip according to our whim, gets called
from highlighter plugin showTooltip function
customTooltip = function(str, seriesIndex, pointIndex, plot) {
value = plot.series[seriesIndex].data[pointIndex][1];
@@ -890,3 +921,16 @@
return "anonymous";
}
+function get_new_chart_url(id, url, param, value) {
+ var chart = cumin.getChart(id);
+ if (chart == null)
+ return false;
+
+ var branch = wooly.session.branch(url);
+ var now = new Date().getTime();
+ branch['elapsed'] = now; // force an entire update
+ branch[param] = value;
+ url = branch.marshal();
+
+ return url;
+}
Modified: trunk/wooly/python/wooly/pages.strings
===================================================================
--- trunk/wooly/python/wooly/pages.strings 2013-03-19 19:27:17 UTC (rev 5748)
+++ trunk/wooly/python/wooly/pages.strings 2013-03-21 15:01:16 UTC (rev 5749)
@@ -28,6 +28,7 @@
<script type="text/javascript"
src="resource?name=jqplot.highlighter.min.js"></script>
<script type="text/javascript"
src="resource?name=jqplot.cursor.js"></script>
<script type="text/javascript"
src="resource?name=jqplot.dateAxisRenderer.js"></script>
+ <script type="text/javascript"
src="resource?name=mootree.js"></script>
<script type="text/javascript">
$j = jQuery.noConflict();