Author: eallen
Date: 2009-09-30 10:58:46 -0400 (Wed, 30 Sep 2009)
New Revision: 3662
Modified:
mgmt/trunk/cumin/python/cumin/grid/job.py
mgmt/trunk/cumin/python/cumin/grid/main.py
mgmt/trunk/cumin/python/cumin/grid/pool.py
mgmt/trunk/cumin/python/cumin/grid/pool.strings
mgmt/trunk/cumin/python/cumin/grid/slot.py
mgmt/trunk/cumin/python/cumin/grid/slot.strings
Log:
Consolidated an error message in job.py
Added a page for the fullpage flash slot vis
Implemented a flash slot vis if flash >= 9 is available
Modified: mgmt/trunk/cumin/python/cumin/grid/job.py
===================================================================
--- mgmt/trunk/cumin/python/cumin/grid/job.py 2009-09-30 14:53:24 UTC (rev 3661)
+++ mgmt/trunk/cumin/python/cumin/grid/job.py 2009-09-30 14:58:46 UTC (rev 3662)
@@ -709,6 +709,7 @@
self.process_cancel(session)
class OutputFile(Widget):
+ err_msg = "Output, Error, and UserLog file names are invalid."
def __init__(self, app, name, which_file, first_last):
super(OutputFile, self).__init__(app, name)
@@ -725,6 +726,7 @@
action = self.app.model.scheduler.Fetch
data = action.qmfcall(scheduler, job, file, start, end)
return escape_entity(data)
+ return self.err_msg
def get_file_args(self, session):
first_last = self.first_last.get(session)
@@ -739,7 +741,7 @@
def render_loading(self, session, *args):
file = self.which_file.get_current_file_name(session)
- return file and "loading..." or "Output, Error, and UserLog file
names are invalid."
+ return file and "loading..." or self.err_msg
class JobOutput(Form):
def __init__(self, app, name, job):
Modified: mgmt/trunk/cumin/python/cumin/grid/main.py
===================================================================
--- mgmt/trunk/cumin/python/cumin/grid/main.py 2009-09-30 14:53:24 UTC (rev 3661)
+++ mgmt/trunk/cumin/python/cumin/grid/main.py 2009-09-30 14:58:46 UTC (rev 3662)
@@ -55,9 +55,12 @@
app.main_page.main.grid = self.frame
app.main_page.main.add_tab(self.frame)
- self.pool_slots_page = PoolSlotMapPage(app, "poolslots.png")
+ self.pool_slots_page = PoolSlotMapPage(app, "poolslots.vis")
app.add_page(self.pool_slots_page)
+ self.pool_slots_fullpage = PoolSlotFullPage(app, "poolslotsfull.vis")
+ app.add_page(self.pool_slots_fullpage)
+
def init_test(self, test):
GridTest("grid", test)
Modified: mgmt/trunk/cumin/python/cumin/grid/pool.py
===================================================================
--- mgmt/trunk/cumin/python/cumin/grid/pool.py 2009-09-30 14:53:24 UTC (rev 3661)
+++ mgmt/trunk/cumin/python/cumin/grid/pool.py 2009-09-30 14:58:46 UTC (rev 3662)
@@ -18,7 +18,7 @@
from collector import CollectorSet, CollectorFrame
from negotiator import NegotiatorSet, NegotiatorFrame
from limit import LimitSet, LimitFrame
-from slot import SlotSet, SlotFrame, SlotMap, SlotMapPage
+from slot import SlotSet, SlotFrame, SlotMap, SlotMapPage, SlotActivities
from cumin.widgets import Session
@@ -253,9 +253,12 @@
stats = GridStats(app, "grid_stats", self.grid)
self.add_child(stats)
- slot_map = PoolSlotMap(app, "slot_map", self.pool)
+ slot_map = PoolSlotMap(app, "slot_png", self.pool)
self.add_child(slot_map)
+ slot_vis = PoolSlotFlashVis(app, "slot_map", self.pool)
+ self.add_child(slot_vis)
+
chart = self.JobStackedChart(app, "jobs", self.collector)
chart.duration.param.default = "3600"
chart.stats = ("RunningJobs", "IdleJobs")
@@ -349,11 +352,15 @@
return sess.marshal()
+ def get_scheduler_select(self, session):
+ pool = self.pool.get(session)
+ return "pool='%s'" % pool.id
+
class PoolSlotMapPage(SlotMapPage):
def __init__(self, app, name):
- super(PoolSlotMapPage, self).__init__(app, name)
+ self.pool = PoolParameter(app, "id")
+ super(PoolSlotMapPage, self).__init__(app, name, self.pool, "Pool")
- self.pool = PoolParameter(app, "id")
self.add_parameter(self.pool)
def do_process(self, session):
@@ -367,3 +374,97 @@
if pool:
self.slots.add_where_expr(session, "s.pool = '%s'",
pool.id)
+
+class PoolSlotFlashVis(PoolSlotMap):
+ def __init__(self, app, name, object):
+ super(PoolSlotFlashVis, self).__init__(app, name, object)
+
+ self.group_by = self.GroupBySwitch(app, "group_by")
+ self.add_child(self.group_by)
+
+ self.activities = SlotActivities(app, "activities_legend")
+ self.add_child(self.activities)
+ self.fullpageable = True
+
+ def render_slots_href(self, session):
+ pool = self.pool.get(session)
+
+ page = main.module.pool_slots_page
+ sess = Session(page)
+
+ page.pool.set(sess, pool)
+ page.json.set(sess, "slots")
+ page.groups.set(sess, [self.group_by.get(session)])
+
+ return sess.marshal()
+
+ def render_image_href(self, session):
+ pool = self.pool.get(session)
+
+ page = main.module.pool_slots_page
+ sess = Session(page)
+
+ page.pool.set(sess, pool)
+
+ return sess.marshal()
+
+ def render_ctrl_href(self, session):
+ pool = self.pool.get(session)
+
+ page = main.module.pool_slots_page
+ sess = Session(page)
+
+ page.pool.set(sess, pool)
+ page.json.set(sess, "ctrl")
+ page.groups.set(sess, [self.group_by.get(session)])
+
+ return sess.marshal()
+
+ def render_slot_chart_width(self, session):
+ return self.render_slot_clip_size(session)
+
+ def render_slot_chart_height(self, session):
+ return self.render_slot_clip_size(session)
+
+ def render_slot_ctrl_height(self, session):
+ return 80
+
+ def render_fullpageable(self, session):
+ return self.fullpageable and "fullpageable" or ""
+
+ def render_fullpage_href(self, session):
+ pool = self.pool.get(session)
+
+ page = main.module.pool_slots_fullpage
+ sess = Session(page)
+
+ page.pool.set(sess, pool)
+
+ return sess.marshal()
+
+ class GroupBySwitch(StateSwitch):
+ def __init__(self, app, name):
+ super(PoolSlotFlashVis.GroupBySwitch, self).__init__(app, name)
+
+ self.add_state("system", "By system")
+ #self.add_state("state", "By slot state")
+ self.add_state("None", "No Grouping")
+
+ def get_click(self, session, state):
+ href = self.parent.render_slots_href(session)
+ return "return slot_vis('%s', this, '%s',
'%s')" % (state, self.parent.path, href)
+
+class PoolSlotFullPage(FlashFullPage):
+ def __init__(self, app, name):
+ super(PoolSlotFullPage, self).__init__(app, name)
+
+ self.pool = PoolParameter(app, "id")
+ self.add_parameter(self.pool)
+
+ self.flash_chart = PoolSlotFlashVis(app, "chart", self.pool)
+ self.flash_chart.fullpageable = False
+ self.add_child(self.flash_chart)
+
+ def render_content(self, session):
+ return self.flash_chart.render(session)
+
\ No newline at end of file
Modified: mgmt/trunk/cumin/python/cumin/grid/pool.strings
===================================================================
--- mgmt/trunk/cumin/python/cumin/grid/pool.strings 2009-09-30 14:53:24 UTC (rev 3661)
+++ mgmt/trunk/cumin/python/cumin/grid/pool.strings 2009-09-30 14:58:46 UTC (rev 3662)
@@ -22,6 +22,9 @@
div#PoolOverview div.col1 {
padding-right: 2em;
}
+div#flashSlotMap, div#pngSlotMap {
+ display: none;
+}
[PoolOverview.html]
<div id="PoolOverview">
@@ -34,7 +37,8 @@
</div>
<div class="col2">
<h2>Slots</h2>
- <div>{slot_map}</div>
+ <div id="flashSlotMap">{slot_map}</div>
+ <div id="pngSlotMap">{slot_png}</div>
</div>
</div>
<div style="clear:left;"><!-- --></div>
@@ -45,3 +49,85 @@
{stats}
<div>{jobs_chart}</div>
<div>{submit_chart}</div>
+
+[PoolSlotFlashVis.html]
+<div>
+ <div class="StatValueChart {fullpageable}" id="{id}">
+ <h2>{title}</h2>
+ <div class="duration">{group_by}</div>
+ <div id="{id}_chart"></div>
+ <div id="{id}ctrl_chart"></div>
+ </div>
+</div>
+<script type="text/javascript">
+//<![CDATA[
+ function updatePoolSlotVis(id, loadType) {
+ var chart = cumin.getFlashChart(id);
+ if ((chart != null) && (typeof chart.src != "undefined")) {
+ if (typeof loadType != "undefined")
+ chart.reload(chart.src, false);
+ else
+ chart.load(chart.src, false);
+ }
+ }
+ function vis_ready(vis) {
+ if (vis == "slots") {
+ var chart = cumin.getFlashChart("{id}");
+ chart.src = "{slots_href}";
+ updatePoolSlotVis('{id}');
+ } else {
+ var chart = cumin.getFlashChart("{id}ctrl");
+ chart.src = "{ctrl_href}";
+ updatePoolSlotVis('{id}ctrl');
+ }
+ }
+ function vis_treemap_over(type, value) {
+ var chart = cumin.getFlashChart("{id}");
+ if (chart) {
+ chart.highlight(type, value);
+ }
+ }
+ function vis_treemap_out(type, value) {
+ var chart = cumin.getFlashChart("{id}");
+ if (chart) {
+ chart.lowlight(type, value);
+ }
+ }
+ function slot_vis(state, a, id, href) {
+ var li = a.parentNode;
+ var ul = li.parentNode;
+ var as = ul.getElementsByTagName('a');
+ for (var i=0; i < as.length; i++) {
+ as[i].className = (as[i] == a) ? "selected" : "";
+ }
+
+ var branch = wooly.session.branch(href);
+ if (state == "None") {
+ if ("group" in branch)
+ delete branch.group;
+ } else {
+ branch.group = state;
+ }
+ var src = branch.marshal();
+ var chart = cumin.getFlashChart(id);
+ chart.src = src
+ updatePoolSlotVis(id, "reload");
+ return false;
+ }
+ var flashversion = swfobject.getFlashPlayerVersion();
+ if (flashversion.major >= 9) {
+ var fsm = document.getElementById('flashSlotMap');
+ if (fsm)
+ fsm.style.display = "block";
+
+ swfobject.embedSWF("resource?name=slots.swf", "{id}_chart",
"{slot_chart_width}", "{slot_chart_height}", "9.0.0",
"", {vis:"slots"});
+ swfobject.embedSWF("resource?name=slots.swf",
"{id}ctrl_chart", "{slot_chart_width}",
"{slot_ctrl_height}", "9.0.0", "", {vis:"ctrl"});
+
+ wooly.addPageUpdateListener(function () { updatePoolSlotVis('{id}',
"reload"); });
+ wooly.addPageUpdateListener(function () { updatePoolSlotVis('{id}ctrl',
"reload"); });
+ window.addEvent('domready',function () {
+ cumin.setFullpageHandler('{id}', '{fullpage_href}');
+ });
+ }
+//]]>
+</script>
Modified: mgmt/trunk/cumin/python/cumin/grid/slot.py
===================================================================
--- mgmt/trunk/cumin/python/cumin/grid/slot.py 2009-09-30 14:53:24 UTC (rev 3661)
+++ mgmt/trunk/cumin/python/cumin/grid/slot.py 2009-09-30 14:58:46 UTC (rev 3662)
@@ -141,70 +141,68 @@
return "Load"
class SlotMapPage(Page):
- interiors = {"Idle": (.7, .7, .7),
- "Busy": (0.0, 0.4, 0.0),
- "Suspended": (1, 0, 0),
- "Vacating": (1, .73, .367),
- "Killing": (0, 0, 1),
- "Benchmarking": (1, .8, .8),
- "Retiring": (.8, .2, .8),
- None: (.8, .8, .8)}
+ """ handles the slots.vis request """
+ interiors = {"Idle": (.7,.7,.7),
+ "Busy": (0.0, 0.4, 0.0),
+ "Suspended": (1,0,0),
+ "Vacating": (1,.73,.367),
+ "Killing": (0,0,1),
+ "Benchmarking": (1,.8,.8),
+ "Retiring": (.8,.2,.8),
+ "Unknown": (.8,.8,.8)}
+
+ shapes = ["rectangle", "circle", "circle"]
max_width = 400
max_png_age = 30
-
- def __init__(self, app, name):
+ def __init__(self, app, name, param, desc):
super(SlotMapPage, self).__init__(app, name)
+ self.object_param = param
+ self.object_description = desc
+
self.zoom_level = IntegerParameter(app, "zl")
self.zoom_level.default = 1
self.add_parameter(self.zoom_level)
- self.zoom_x = IntegerParameter(app, "zx")
- self.zoom_x.default = 0
- self.add_parameter(self.zoom_x)
-
- self.zoom_y = IntegerParameter(app, "zy")
- self.zoom_y.default = 0
- self.add_parameter(self.zoom_y)
-
self.dot = Parameter(app, "dot")
self.add_parameter(self.dot)
+ self.json = Parameter(app, "json")
+ self.add_parameter(self.json)
+
+ group = Parameter(app, "agroup")
+ self.groups = ListParameter(app, "group", group)
+ self.add_parameter(self.groups)
+
self.cache = ImageCache()
self.slots = SlotDataSet(app)
def get_content_type(self, session):
- return "image/png"
+ return self.json.get(session) and "text/plain" or
"image/png"
def get_cache_control(self, session):
return "no-cache"
def render_dot(self, session, dot):
map = HeatMapChart()
-
- if dot == "Unknown":
- dot = None
-
surface = map.plot_dot(self.interiors[dot], 12, 12)
-
writer = Writer()
surface.write_to_png(writer)
return writer.to_string()
def do_render(self, session):
dot = self.dot.get(session)
-
if dot:
return self.render_dot(session, dot)
+ if self.json.get(session):
+ return self.render_json(session)
+
zl = self.zoom_level.get(session)
- zx = self.zoom_x.get(session)
- zy = self.zoom_y.get(session)
# determine if cached copy is recent enough
cached_png, args = self.get_cached(session, zl)
-
if cached_png:
self.set_cookie(session, args)
return cached_png
@@ -221,8 +219,10 @@
activity = columns.index("activity")
state = columns.index("state")
- slots = [(self.interiors[x[activity]], x[state]) for x in records]
- size = map.plot_slots(slots, zl, zx, zy)
+ interiors = self.interiors.copy()
+ interiors[None] = interiors["Unknown"]
+ slots = [(interiors[x[activity]], x[state]) for x in records]
+ size = map.plot_slots(slots, zl)
args = (size, map.width, map.height, len(slots), map.rows, map.cols)
self.set_cookie(session, args)
@@ -232,6 +232,67 @@
map.write(writer)
return writer.to_string()
+ def get_info_array(self, records, array, attr):
+ d = dict()
+ for i in array:
+ atr = records[i][attr]
+ if not atr in d:
+ d[atr] = list()
+ d[atr].append(i)
+ return d
+
+ def render_json(self, session):
+ cursor = self.slots.execute(session)
+ records = cursor_to_rows(cursor)
+
+ if len(records) == 0:
+ return "[]"
+
+ groups = self.groups.get(session)
+ slot_count = len(records)
+
+ root = Element()
+ root.name = self.object_description
+ root.value = self.object_param.get(session).id
+ root.slots = slot_count
+ root.vis = self.json.get(session)
+ root.activity_colors = self.interiors
+ root.tree = self.treeify(records, range(slot_count), groups, 0)
+ return "[%s]" % root.create()
+
+ def treeify(self, records, plist, groups, level):
+ level_list = list()
+ # leaf
+ if level == len(groups):
+ el = Element()
+ el.name = "Slot"
+ interiors = self.interiors.copy()
+ interiors[None] = interiors["Unknown"]
+ for i in sorted(plist, key=lambda x:records[x]["name"]):
+ el = Element()
+ el.job_id = records[i]["job_id"] and
records[i]["job_id"] or ""
+ el.activity = records[i]["activity"] and
records[i]["activity"] or "Unknown"
+ el.state = records[i]["state"] and
records[i]["state"] or "Unknown"
+ el.value = records[i]["name"] and records[i]["name"]
or ""
+ el.load_avg = records[i]["load_avg"] and
round(records[i]["load_avg"], 2) or 0
+ el.name = "slot"
+ level_list.append(el)
+ return level_list
+
+ # not a leaf
+ group = groups[level]
+ level_dict = self.get_info_array(records, plist, group)
+ for key in sorted(level_dict):
+ el = Element()
+ el.name = group
+ el.value = key
+ el.slots = len(level_dict[key])
+ el.level = level
+ if level < len(groups):
+ el.tree = self.treeify(records, level_dict[key], groups, level + 1)
+ level_list.append(el)
+ return level_list
+
def get_cached(self, session, zl):
filename = self.gen_filename(session)
return self.cache.find_recent(filename, self.max_png_age)
@@ -268,14 +329,17 @@
def render_job_index_param(self, session):
return "xargs"
+ def render_fullpageable(self, session):
+ return "fullpageable"
+
+ def get_scheduler_select(self, session):
+ raise Exception("Must implement get_scheduler_select")
+
def render_slot_job_url(self, session):
job = Identifiable("XXX")
- object = self.frame.object.get(session) # XXX eeks
+ select = self.get_scheduler_select(session)
try:
- if isinstance(object, Sysimage):
- scheduler = Scheduler.select("system='%s'" %
object.nodeName)[0]
- else:
- scheduler = Scheduler.select("pool='%s'" %
object.id)[0]
+ scheduler = Scheduler.select(select)[0]
return self.page.main.grid.pool.job.get_href(session, job, scheduler)
except:
pass
@@ -290,7 +354,7 @@
self.states = self.SlotStates(app, "slot_states")
self.add_child(self.states)
- self.activities = self.SlotActivities(app, "slot_activities")
+ self.activities = SlotActivities(app, "slot_activities")
self.add_child(self.activities)
class SlotStates(ItemSet):
@@ -305,31 +369,6 @@
def render_item_title(self, session, state):
return state
- class SlotActivities(ItemSet):
- activities = (("Idle", "clear"),
- ("Busy", "green"),
- ("Suspended", "red"),
- ("Vacating", "orange"),
- ("Killing", "blue"),
- ("Benchmarking", "yellow"),
- ("Retiring", "purple"),
- ("Unknown", "grey"))
-
- def render_item_size(self, session, activity):
- return 12
-
- def do_get_items(self, session):
- return self.activities
-
- def render_item_title(self, session, activity):
- return activity[0]
-
- def render_item_href(self, session, activity):
- params = list()
- params.append("dot=%s" % activity[0])
-
- return "poolslots.png?" + ";".join(params)
-
class SlotInfo(ItemSet):
display_names = {"jid": "jid", "job_id": "Job
ID",
"system": "System", "machine":
"Machine",
@@ -397,3 +436,29 @@
def render_item_row_class(self, session, item):
return item[0] == "jid" and "class='hidden_row'"
or ""
+
+class SlotActivities(ItemSet):
+ activities = (("Idle", "clear"),
+ ("Busy", "green"),
+ ("Suspended", "red"),
+ ("Vacating", "orange"),
+ ("Killing", "blue"),
+ ("Benchmarking", "yellow"),
+ ("Retiring", "purple"),
+ ("Unknown", "grey"))
+
+ def render_item_size(self, session, activity):
+ return 12
+
+ def do_get_items(self, session):
+ return self.activities
+
+ def render_item_title(self, session, activity):
+ return activity[0]
+
+ def render_item_href(self, session, activity):
+ params = list()
+ params.append("dot=%s" % activity[0])
+
+ return "poolslots.vis?" + ";".join(params)
+
Modified: mgmt/trunk/cumin/python/cumin/grid/slot.strings
===================================================================
--- mgmt/trunk/cumin/python/cumin/grid/slot.strings 2009-09-30 14:53:24 UTC (rev 3661)
+++ mgmt/trunk/cumin/python/cumin/grid/slot.strings 2009-09-30 14:58:46 UTC (rev 3662)
@@ -5,6 +5,9 @@
s.machine,
s.system,
s.job_id,
+ s.accounting_group,
+ s.op_sys,
+ s.arch,
j.id as jid,
c.activity,
c.state,
@@ -1077,7 +1080,7 @@
<div class="slot_map" id="{id}">
<div id="slot_glass"><!-- mouse target to prevent
selecting/dragging image --></div>
<div id="slot_png">
- <img name="{id}" src="{image_href}" border="0"
onload="vis.img_loaded(this)" onerror="vis.img_error(this)"
alt="slots"/>
+ <img name="{id}" border="0"
onload="vis.img_loaded(this)" onerror="vis.img_error(this)"
alt="slots"/>
</div>
<div id="slot_hover"><!-- red border around current slot
--></div>
<div id="slot_zooming"><p>Zooming...</p></div>
@@ -1089,25 +1092,37 @@
<div style="clear: left"><!-- --></div>
{slot_info}
<script type="text/javascript">
- var slot_current_id = "{id}";
- var show_slot_job_url = "{slot_job_url}";
- var slot_job_index = "{job_index_param}";
- var slot_map_info = {size: 0, width: 0, height: 0, count: 0, rows: 0, cols: 0};
- var slot_clip_size = {slot_clip_size};
- var original_clip_size = slot_clip_size;
- $('slot_visualization').onfullpage = function (width) { vis.notify(true,
width); };
- $('slot_visualization').onrestore = function () { vis.notify(false,
original_clip_size); };
- var oGlass = document.getElementById("slot_glass");
- if (oGlass) {
- oGlass.onmousedown = vis.downGlass;
- oGlass.onmouseup = vis.upGlass;
- oGlass.onmousemove = vis.moveGlass;
- oGlass.onmouseout = vis.outGlass;
- oGlass.ondblclick = clicks.doMouseDown;
- oGlass.onclick = clicks.doMouseDown;
- oGlass.dragging = false;
- oGlass.down_pos = null;
- }
+//<![CDATA[
+ var flashversion = swfobject.getFlashPlayerVersion();
+ if (flashversion.major < 9) {
+ var psm = document.getElementById('pngSlotMap');
+ if (psm)
+ psm.style.display = "block";
+ var theImage = document.images['{id}'];
+ if (theImage) {
+ theImage.src = "{image_href}";
+ }
+ var slot_current_id = "{id}";
+ var show_slot_job_url = "{slot_job_url}";
+ var slot_job_index = "{job_index_param}";
+ var slot_map_info = {size: 0, width: 0, height: 0, count: 0, rows: 0, cols: 0};
+ var slot_clip_size = {slot_clip_size};
+ var original_clip_size = slot_clip_size;
+ $('slot_visualization').onfullpage = function (width) { vis.notify(true,
width); };
+ $('slot_visualization').onrestore = function () { vis.notify(false,
original_clip_size); };
+ var oGlass = document.getElementById("slot_glass");
+ if (oGlass) {
+ oGlass.onmousedown = vis.downGlass;
+ oGlass.onmouseup = vis.upGlass;
+ oGlass.onmousemove = vis.moveGlass;
+ oGlass.onmouseout = vis.outGlass;
+ oGlass.ondblclick = clicks.doMouseDown;
+ oGlass.onclick = clicks.doMouseDown;
+ oGlass.dragging = false;
+ oGlass.down_pos = null;
+ }
+ }
+//]]>
</script>
[SlotInfo.css]