summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGary Bressler <garybressler@nc.rr.com>2010-04-30 14:04:11 -0400
committerGary Bressler <garybressler@nc.rr.com>2010-04-30 14:04:11 -0400
commitc21dea9f97dd31ad8a915469edc01f44345a469e (patch)
tree9a66ff34dc8c90c695572038b443bc2fd05e8cbf
parent6cfc2cc3bb104ef81e4d9e70f63ab803949f9764 (diff)
Fixes related to scrolling, along with the display of some extra information. Updated documentation.
-rw-r--r--doc/index.txt40
-rw-r--r--temp0
-rw-r--r--unit_trace/viz/__init__.py2
-rw-r--r--unit_trace/viz/canvas.py158
-rw-r--r--unit_trace/viz/format.py7
-rw-r--r--unit_trace/viz/graph.py135
-rw-r--r--unit_trace/viz/schedule.py304
-rw-r--r--unit_trace/viz/util.py75
-rw-r--r--unit_trace/viz/viewer.py246
-rw-r--r--unit_trace/viz/windows.py24
10 files changed, 621 insertions, 370 deletions
diff --git a/doc/index.txt b/doc/index.txt
index 600590b..e24eb2a 100644
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -130,6 +130,8 @@ input parameters you specify. For example, if you use `-e`
130and `-l` to specify a time range, the visualizer will 130and `-l` to specify a time range, the visualizer will
131generate a graph restricted to that time range. 131generate a graph restricted to that time range.
132 132
133#### Organization of the Visualizer ####
134
133When the visualizer starts up, you'll see the beginning of the graph which the 135When the visualizer starts up, you'll see the beginning of the graph which the
134visualizer automatically generated. We'll first discuss the axes. 136visualizer automatically generated. We'll first discuss the axes.
135The x-axis gives time (in whatever units 137The x-axis gives time (in whatever units
@@ -143,7 +145,11 @@ gives the identifier of a CPU that at one point was used by at least one task.
143 145
144The horizontal cross-section demarcated by each task name or CPU 146The horizontal cross-section demarcated by each task name or CPU
145identifier gives the chronological sequence of events in the input stream 147identifier gives the chronological sequence of events in the input stream
146for the relevant task or CPU. The event symbols are as follows: 148for the relevant task or CPU.
149
150#### Representation of Events ####
151
152The event symbols are as follows:
147 153
148<table border=1> 154<table border=1>
149<tr><th>Symbol Description</th><th>Event Type</th><th>Meaning</th></tr> 155<tr><th>Symbol Description</th><th>Event Type</th><th>Meaning</th></tr>
@@ -174,9 +180,12 @@ event occurred, but at a time not in the input stream.
174In other words, it assumes that such events are genuine. To represent this phenomenon, 180In other words, it assumes that such events are genuine. To represent this phenomenon,
175the visualizer shows the bar going "off the graph". 181the visualizer shows the bar going "off the graph".
176 182
177Interacting with the visualizer is easy. The scrollbars work in the obvious way. You can also 183#### Interacting with Events ####
178use the arrow keys to move, or use Ctrl+arrow keys to move faster. Mousing over an event gives 184
179a description of the event at the bottom. You can also click an event to 185Interacting with the visualizer is easy. Mousing over an event gives
186a description of the event at the bottom, along with information about the job under which
187the event occurred.
188You can also click an event to
180select it. Hold down Ctrl to select multiple events. You can also drag a box around multiple 189select it. Hold down Ctrl to select multiple events. You can also drag a box around multiple
181events to select them. You can even combine this with the Ctrl key to select multiple 190events to select them. You can even combine this with the Ctrl key to select multiple
182boxes of events in succession. Your selection is independent of the mode you are in -- 191boxes of events in succession. Your selection is independent of the mode you are in --
@@ -186,12 +195,33 @@ switch over to CPU mode. Right-click and you will get a context menu containing
186you selected. Selecting an item in the menu gives you detailed information about the event 195you selected. Selecting an item in the menu gives you detailed information about the event
187in its own window. 196in its own window.
188 197
198#### Moving Around ####
199
200The scrollbars work in the obvious way. You can also
201use the arrow keys to move about, or use Ctrl+arrow keys to move faster.
189If you want to see what's happening at a certain time, but don't want to bother scrolling there 202If you want to see what's happening at a certain time, but don't want to bother scrolling there
190manually, you can select `View->Move to Time` and type in the time you want to move to. 203manually, you can select `View->Jump to Time` and type in the time you want to move to. The
204default units are in milliseconds, but you can use an SI prefix (ns, us, ms, s) to specify
205another time unit.
191 206
192You can also zoom by either going to `View->Zoom In/Out`, or by holding down Ctrl and scrolling 207You can also zoom by either going to `View->Zoom In/Out`, or by holding down Ctrl and scrolling
193the mouse wheel. 208the mouse wheel.
194 209
210#### Colors ####
211
212Certain events, most notably the `Scheduled` event, are color-coded. In Task Mode, each color
213corresponds to a CPU that a job was scheduled on; conversely, in CPU Mode, each color corresponds
214to a task that a job was scheduled for. (In Task Mode, there is an extra color for a pseudo-CPU,
215CPU -1. If an event is assigned CPU -1 this simply means that there is no actual CPU that can
216logically be assigned to the event. By default, CPU -1 has a gray color.)
217
218You can change the default set of colors. To change a CPU or task color, select exactly one event
219assigned to the relevant CPU or task. Alteratively, select one of the task or CPU names listed
220in the upper-left portion of the graph. Then navigate to `Edit->Colors` and use the color chooser
221to pick the desired color.
222
223#### Exiting ####
224
195To exit the Unit-Trace visualizer, go to `File->Quit` or click the close button. 225To exit the Unit-Trace visualizer, go to `File->Quit` or click the close button.
196 226
197## Gotchas ## 227## Gotchas ##
diff --git a/temp b/temp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/temp
diff --git a/unit_trace/viz/__init__.py b/unit_trace/viz/__init__.py
index 592f697..91093c1 100644
--- a/unit_trace/viz/__init__.py
+++ b/unit_trace/viz/__init__.py
@@ -18,7 +18,7 @@ except ImportError:
18gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 18gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
19 None, (gtk.Adjustment, gtk.Adjustment)) 19 None, (gtk.Adjustment, gtk.Adjustment))
20gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 20gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
21 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) 21 None, (gobject.TYPE_PYOBJECT,))
22gobject.signal_new('update-sel-changes', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 22gobject.signal_new('update-sel-changes', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
23 None, (gobject.TYPE_PYOBJECT,)) 23 None, (gobject.TYPE_PYOBJECT,))
24gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 24gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
diff --git a/unit_trace/viz/canvas.py b/unit_trace/viz/canvas.py
index 9ac0e8b..badebe9 100644
--- a/unit_trace/viz/canvas.py
+++ b/unit_trace/viz/canvas.py
@@ -47,7 +47,7 @@ class Surface(object):
47 self.virt_y = y 47 self.virt_y = y
48 self.width = width 48 self.width = width
49 self.height = height 49 self.height = height
50 50
51 def set_scale(self, scale): 51 def set_scale(self, scale):
52 """Sets the scale factor.""" 52 """Sets the scale factor."""
53 self.scale = scale 53 self.scale = scale
@@ -58,17 +58,21 @@ class Surface(object):
58 that we should draw to. Note that these might actually be outside the 58 that we should draw to. Note that these might actually be outside the
59 bounds of the surface, 59 bounds of the surface,
60 if we want something outside the surface's ``window''.""" 60 if we want something outside the surface's ``window''."""
61 return (x - self.virt_x * self.scale, y - self.virt_y * self.scale) 61 return ((x - self.virt_x) * self.scale, (y - self.virt_y) * self.scale)
62
63 def get_real_dim(self, w, h):
64 """Translates virtual width and height dimensions to real width and height
65 dimensions."""
66 return (w * self.scale, h * self.scale)
62 67
63 def get_virt_coor(self, x, y): 68 def get_virt_coor(self, x, y):
64 """Does the inverse of the last method.""" 69 """Does the inverse of the last method."""
65 return (x + self.virt_x * self.scale, y + self.virt_y * self.scale)
66
67 def get_virt_coor_unscaled(self, x, y):
68 """Does the same, but removes the scale factor (i.e. behaves as if
69 the scale was 1.0 all along)."""
70 return (x / self.scale + self.virt_x, y / self.scale + self.virt_y) 70 return (x / self.scale + self.virt_x, y / self.scale + self.virt_y)
71 71
72 def get_virt_dim(self, w, h):
73 """Does the inverse of the last method."""
74 return (w / self.scale, h / self.scale)
75
72class SVGSurface(Surface): 76class SVGSurface(Surface):
73 def renew(self, width, height): 77 def renew(self, width, height):
74 iwidth = int(math.ceil(width)) 78 iwidth = int(math.ceil(width))
@@ -77,7 +81,7 @@ class SVGSurface(Surface):
77 self.height = height 81 self.height = height
78 self.surface = cairo.SVGSurface(self.fname, iwidth, iheight) 82 self.surface = cairo.SVGSurface(self.fname, iwidth, iheight)
79 self.ctx = cairo.Context(self.surface) 83 self.ctx = cairo.Context(self.surface)
80 84
81 def write_out(self, fname): 85 def write_out(self, fname):
82 os.execl('cp', self.fname, fname) 86 os.execl('cp', self.fname, fname)
83 87
@@ -89,7 +93,7 @@ class ImageSurface(Surface):
89 self.height = height 93 self.height = height
90 self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, iwidth, iheight) 94 self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, iwidth, iheight)
91 self.ctx = cairo.Context(self.surface) 95 self.ctx = cairo.Context(self.surface)
92 96
93 def write_out(self, fname): 97 def write_out(self, fname):
94 if self.surface is None: 98 if self.surface is None:
95 raise ValueError("Don't own surface, can't write to file") 99 raise ValueError("Don't own surface, can't write to file")
@@ -98,6 +102,7 @@ class ImageSurface(Surface):
98 102
99class Pattern(object): 103class Pattern(object):
100 DEF_STRIPE_SIZE = 10 104 DEF_STRIPE_SIZE = 10
105 COVER_EXTRA = 1
101 MAX_FADE_WIDTH = 250 106 MAX_FADE_WIDTH = 250
102 107
103 def __init__(self, color_list, stripe_size=DEF_STRIPE_SIZE): 108 def __init__(self, color_list, stripe_size=DEF_STRIPE_SIZE):
@@ -106,7 +111,7 @@ class Pattern(object):
106 111
107 def get_color_list(self): 112 def get_color_list(self):
108 return self.color_list 113 return self.color_list
109 114
110 def render_on_canvas(self, canvas, x, y, width, height, fade=False): 115 def render_on_canvas(self, canvas, x, y, width, height, fade=False):
111 fade_span = min(width, Pattern.MAX_FADE_WIDTH) 116 fade_span = min(width, Pattern.MAX_FADE_WIDTH)
112 117
@@ -118,7 +123,8 @@ class Pattern(object):
118 canvas.fill_rect(x, y, width, height, self.color_list[0]) 123 canvas.fill_rect(x, y, width, height, self.color_list[0])
119 124
120 if width > Pattern.MAX_FADE_WIDTH: 125 if width > Pattern.MAX_FADE_WIDTH:
121 canvas.fill_rect(x + Pattern.MAX_FADE_WIDTH, y, width - Pattern.MAX_FADE_WIDTH, 126 canvas.fill_rect(x + Pattern.MAX_FADE_WIDTH - Pattern.COVER_EXTRA, y,
127 width - Pattern.MAX_FADE_WIDTH + Pattern.COVER_EXTRA,
122 height, self.color_list[0]) 128 height, self.color_list[0])
123 else: 129 else:
124 n = 0 130 n = 0
@@ -126,7 +132,7 @@ class Pattern(object):
126 while y < bottom: 132 while y < bottom:
127 i = n % len(self.color_list) 133 i = n % len(self.color_list)
128 if fade: 134 if fade:
129 canvas.fill_rect_fade(x, y, fade_span, \ 135 canvas.fill_rect_fade(x, y, fade_span,
130 min(self.stripe_size, bottom - y), (1.0, 1.0, 1.0), self.color_list[i]) 136 min(self.stripe_size, bottom - y), (1.0, 1.0, 1.0), self.color_list[i])
131 else: 137 else:
132 canvas.fill_rect(x, y, width, min(self.stripe_size, bottom - y), self.color_list[i]) 138 canvas.fill_rect(x, y, width, min(self.stripe_size, bottom - y), self.color_list[i])
@@ -167,7 +173,7 @@ class Canvas(object):
167 173
168 self.surface = surface 174 self.surface = surface
169 self.def_surface = None 175 self.def_surface = None
170 176
171 self.width = int(math.ceil(width)) 177 self.width = int(math.ceil(width))
172 self.height = int(math.ceil(height)) 178 self.height = int(math.ceil(height))
173 self.item_clist = item_clist 179 self.item_clist = item_clist
@@ -182,23 +188,20 @@ class Canvas(object):
182 or drawing to a file.""" 188 or drawing to a file."""
183 if self.def_surface is not None: 189 if self.def_surface is not None:
184 raise ValueError 190 raise ValueError
185 191
186 self.def_surface = self.surface 192 self.def_surface = self.surface
187 self.surface = surface 193 self.surface = surface
188 194
189 def revert_surface(self): 195 def revert_surface(self):
190 """Goes back to the default surface; that is, the one used for 196 """Goes back to the default surface; that is, the one used for
191 drawing to the screen.""" 197 drawing to the screen."""
192 if self.def_surface is not None: 198 if self.def_surface is not None:
193 self.surface = self.def_surface 199 self.surface = self.def_surface
194 self.def_surface = None 200 self.def_surface = None
195 201
196 def set_scale(self, scale): 202 def set_scale(self, scale):
197 self.scale = scale 203 self.scale = scale
198 self.surface.set_scale(scale) 204 self.surface.set_scale(scale)
199 for layer in self.selectable_regions:
200 for event in self.selectable_regions[layer]:
201 self.selectable_regions[layer][event].set_scale(scale)
202 205
203 def scaled(self, *coors): 206 def scaled(self, *coors):
204 """Scales a series of coordinates.""" 207 """Scales a series of coordinates."""
@@ -265,7 +268,7 @@ class Canvas(object):
265 self.draw_line((x, y), (x, y + GraphFormat.MAJ_TICK_SIZE), 268 self.draw_line((x, y), (x, y + GraphFormat.MAJ_TICK_SIZE),
266 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS) 269 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
267 270
268 if (i < end_tick): 271 if i < end_tick:
269 for j in range(0, min_per_maj): 272 for j in range(0, min_per_maj):
270 self.draw_line((x, y), (x + maj_sep / min_per_maj, y), 273 self.draw_line((x, y), (x + maj_sep / min_per_maj, y),
271 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS) 274 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
@@ -276,7 +279,8 @@ class Canvas(object):
276 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS) 279 (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
277 280
278 def draw_x_axis_labels(self, x, y, start_tick, end_tick, maj_sep, min_per_maj, start=0, incr=1, show_min=False, \ 281 def draw_x_axis_labels(self, x, y, start_tick, end_tick, maj_sep, min_per_maj, start=0, incr=1, show_min=False, \
279 majfopts=GraphFormat.DEF_FOPTS_MAJ, minfopts=GraphFormat.DEF_FOPTS_MIN): 282 unit=GraphFormat.DEF_TIME_UNIT, majfopts=GraphFormat.DEF_FOPTS_MAJ,
283 minfopts=GraphFormat.DEF_FOPTS_MIN):
280 """Draws the labels for the x-axis. (x, y) should give the origin. 284 """Draws the labels for the x-axis. (x, y) should give the origin.
281 how far down you want the text. ``incr'' gives the increment per major 285 how far down you want the text. ``incr'' gives the increment per major
282 tick. ``start'' gives the value of the first tick. ``show_min'' specifies 286 tick. ``start'' gives the value of the first tick. ``show_min'' specifies
@@ -288,9 +292,9 @@ class Canvas(object):
288 minincr = incr / (min_per_maj * 1.0) 292 minincr = incr / (min_per_maj * 1.0)
289 293
290 cur = start * 1.0 294 cur = start * 1.0
291 295
292 for i in range(start_tick, end_tick + 1): 296 for i in range(start_tick, end_tick + 1):
293 text = util.format_float(cur, 2) 297 text = util.format_float(unit.nsec_to_native(cur), 2)
294 self.draw_label(text, x, y, majfopts, AlignMode.CENTER, AlignMode.TOP) 298 self.draw_label(text, x, y, majfopts, AlignMode.CENTER, AlignMode.TOP)
295 299
296 if (i < end_tick): 300 if (i < end_tick):
@@ -362,7 +366,7 @@ class Canvas(object):
362 y -= axis_height - item_size / 2.0 - item_size * item_no 366 y -= axis_height - item_size / 2.0 - item_size * item_no
363 367
364 orig_color = fopts.color 368 orig_color = fopts.color
365 369
366 if selected: 370 if selected:
367 fopts.color = GraphFormat.HIGHLIGHT_COLOR 371 fopts.color = GraphFormat.HIGHLIGHT_COLOR
368 else: 372 else:
@@ -370,19 +374,19 @@ class Canvas(object):
370 self.draw_label(item, x, y, fopts, AlignMode.RIGHT, AlignMode.CENTER) 374 self.draw_label(item, x, y, fopts, AlignMode.RIGHT, AlignMode.CENTER)
371 375
372 fopts.color = orig_color 376 fopts.color = orig_color
373 377
374 def add_sel_y_axis_item(self, x, y, axis_height, item, item_no, item_size, event, fopts=None): 378 def add_sel_y_axis_item(self, x, y, axis_height, item, item_no, item_size, event, fopts=None):
375 if fopts is None: 379 if fopts is None:
376 fopts = GraphFormat.DEF_FOPTS_ITEM 380 fopts = GraphFormat.DEF_FOPTS_ITEM
377 381
378 x -= GraphFormat.Y_AXIS_ITEM_GAP 382 x -= GraphFormat.Y_AXIS_ITEM_GAP
379 y -= axis_height - item_size / 2.0 - item_size * item_no 383 y -= axis_height - item_size / 2.0 - item_size * item_no
380 384
381 x, y, width, height, f_height = self.get_label_dim(item, x, y, fopts, AlignMode.RIGHT, AlignMode.CENTER, False) 385 x, y, width, height, f_height = self.get_label_dim(item, x, y, fopts, AlignMode.RIGHT, AlignMode.CENTER, False)
382 #(x, y) gives bottom-left, not top-left 386 #(x, y) gives bottom-left, not top-left
383 y -= height 387 y -= height
384 self.add_sel_region(SelectableRegion(x, y, width, height, event)) 388 self.add_sel_region(SelectableRegion(x, y, width, height, event))
385 389
386 def draw_top_item(self, x, y, axis_height, item, item_no, prev_items, selected, fopts=None): 390 def draw_top_item(self, x, y, axis_height, item, item_no, prev_items, selected, fopts=None):
387 """Draws the item labels at the top. ``item'' is the string to print, 391 """Draws the item labels at the top. ``item'' is the string to print,
388 while item_size gives the vertical amount of 392 while item_size gives the vertical amount of
@@ -393,22 +397,22 @@ class Canvas(object):
393 fopts = GraphFormat.DEF_FOPTS_ITEM 397 fopts = GraphFormat.DEF_FOPTS_ITEM
394 398
395 y -= axis_height + GraphFormat.TOP_ITEM_VERT_GAP 399 y -= axis_height + GraphFormat.TOP_ITEM_VERT_GAP
396 400
397 for prev_item in prev_items: 401 for prev_item in prev_items:
398 x += GraphFormat.TOP_ITEM_HORIZ_GAP + self.get_label_dim(prev_item, x, y, fopts, 402 x += GraphFormat.TOP_ITEM_HORIZ_GAP + self.get_label_dim(prev_item, x, y, fopts,
399 AlignMode.LEFT, AlignMode.TOP)[2] 403 AlignMode.LEFT, AlignMode.TOP)[2]
400 404
401 orig_color = fopts.color 405 orig_color = fopts.color
402 406
403 if selected: 407 if selected:
404 fopts.color = GraphFormat.HIGHLIGHT_COLOR 408 fopts.color = GraphFormat.HIGHLIGHT_COLOR
405 else: 409 else:
406 fopts.color = self.get_bar_pattern(item_no).get_color_list()[0] 410 fopts.color = self.get_bar_pattern(item_no).get_color_list()[0]
407 411
408 self.draw_label(item, x, y, fopts, AlignMode.LEFT, AlignMode.BOTTOM) 412 self.draw_label(item, x, y, fopts, AlignMode.LEFT, AlignMode.BOTTOM)
409 413
410 fopts.color = orig_color 414 fopts.color = orig_color
411 415
412 def add_sel_top_item(self, x, y, axis_height, item, item_no, prev_items, event, fopts=None): 416 def add_sel_top_item(self, x, y, axis_height, item, item_no, prev_items, event, fopts=None):
413 """Draws the item labels at the top. ``item'' is the string to print, 417 """Draws the item labels at the top. ``item'' is the string to print,
414 while item_size gives the vertical amount of 418 while item_size gives the vertical amount of
@@ -419,19 +423,19 @@ class Canvas(object):
419 fopts = GraphFormat.DEF_FOPTS_ITEM 423 fopts = GraphFormat.DEF_FOPTS_ITEM
420 424
421 y -= axis_height + GraphFormat.TOP_ITEM_VERT_GAP 425 y -= axis_height + GraphFormat.TOP_ITEM_VERT_GAP
422 426
423 for item in prev_items: 427 for item in prev_items:
424 x += GraphFormat.TOP_ITEM_HORIZ_GAP + self.get_label_dim(item, x, y, fopts, 428 x += GraphFormat.TOP_ITEM_HORIZ_GAP + self.get_label_dim(item, x, y, fopts,
425 AlignMode.LEFT, AlignMode.BOTTOM)[2] 429 AlignMode.LEFT, AlignMode.BOTTOM)[2]
426 430
427 x, y, width, height, f_height = self.get_label_dim(item, x, y, fopts, AlignMode.LEFT, AlignMode.BOTTOM, False) 431 x, y, width, height, f_height = self.get_label_dim(item, x, y, fopts, AlignMode.LEFT, AlignMode.BOTTOM, False)
428 #(x, y) gives bottom-left, not top-left 432 #(x, y) gives bottom-left, not top-left
429 y -= height 433 y -= height
430 self.add_sel_region(SelectableRegion(x, y, width, height, event)) 434 self.add_sel_region(SelectableRegion(x, y, width, height, event))
431 435
432 def add_sel_dummy(self, event): 436 def add_sel_dummy(self, event):
433 self.add_sel_region(SelectableRegion(None, None, None, None, event)) 437 self.add_sel_region(SelectableRegion(None, None, None, None, event))
434 438
435 def add_sel_bar(self, x, y, width, height, event): 439 def add_sel_bar(self, x, y, width, height, event):
436 self.add_sel_region(SelectableRegion(x, y, width, height, event)) 440 self.add_sel_region(SelectableRegion(x, y, width, height, event))
437 441
@@ -593,7 +597,6 @@ class Canvas(object):
593 self.selectable_regions = {} 597 self.selectable_regions = {}
594 598
595 def add_sel_region(self, region): 599 def add_sel_region(self, region):
596 region.set_scale(self.scale)
597 layer = region.get_event().get_layer() 600 layer = region.get_event().get_layer()
598 if layer not in self.selectable_regions: 601 if layer not in self.selectable_regions:
599 self.selectable_regions[layer] = {} 602 self.selectable_regions[layer] = {}
@@ -606,9 +609,7 @@ class Canvas(object):
606 return event.get_layer() in self.selectable_regions \ 609 return event.get_layer() in self.selectable_regions \
607 and event in self.selectable_regions[event.get_layer()] 610 and event in self.selectable_regions[event.get_layer()]
608 611
609 def get_intersecting_regions(self, real_x, real_y, width, height): 612 def get_intersecting_regions(self, x, y, width, height):
610 x, y = self.surface.get_virt_coor(real_x, real_y)
611
612 selected = {} 613 selected = {}
613 for layer in self.selectable_regions: 614 for layer in self.selectable_regions:
614 for event in self.selectable_regions[layer]: 615 for event in self.selectable_regions[layer]:
@@ -624,23 +625,23 @@ class Canvas(object):
624 """Overwrites the surface completely white, but technically doesn't delete anything""" 625 """Overwrites the surface completely white, but technically doesn't delete anything"""
625 # Make sure we don't scale here (we want to literally white out just this region) 626 # Make sure we don't scale here (we want to literally white out just this region)
626 627
627 x, y = self.surface.get_virt_coor_unscaled(0, 0) 628 x, y = self.surface.get_virt_coor(0, 0)
628 width, height = self.unscaled(self.surface.width, self.surface.height) 629 width, height = self.unscaled(self.surface.width, self.surface.height)
629 630
630 self.fill_rect(x, y, width, height, (1.0, 1.0, 1.0), False) 631 self.fill_rect(x, y, width, height, (1.0, 1.0, 1.0), False)
631 632
632 def get_item_color(self, n): 633 def get_item_color(self, n):
633 return self.item_clist[n] 634 return self.item_clist[n]
634 635
635 def get_bar_pattern(self, n): 636 def get_bar_pattern(self, n):
636 return self.bar_plist[n] 637 return self.bar_plist[n]
637 638
638 def set_item_color(self, n, color): 639 def set_item_color(self, n, color):
639 self.item_clist[n] = color 640 self.item_clist[n] = color
640 641
641 def set_bar_pattern(self, n, pattern): 642 def set_bar_pattern(self, n, pattern):
642 self.bar_plist[n] = pattern 643 self.bar_plist[n] = pattern
643 644
644class CairoCanvas(Canvas): 645class CairoCanvas(Canvas):
645 """This is a basic class that stores and draws on a Cairo surface, 646 """This is a basic class that stores and draws on a Cairo surface,
646 using various primitives related to drawing a real-time graph (up-arrows, 647 using various primitives related to drawing a real-time graph (up-arrows,
@@ -672,8 +673,8 @@ class CairoCanvas(Canvas):
672 def _rect_common(self, x, y, width, height, color, thickness, clip_side=None, do_snap=True): 673 def _rect_common(self, x, y, width, height, color, thickness, clip_side=None, do_snap=True):
673 EXTRA_FACTOR = 2.0 674 EXTRA_FACTOR = 2.0
674 675
675 x, y, width, height = self.scaled(x, y, width, height)
676 x, y = self.surface.get_real_coor(x, y) 676 x, y = self.surface.get_real_coor(x, y)
677 width, height = self.surface.get_real_dim(width, height)
677 max_width = self.surface.width + EXTRA_FACTOR * thickness 678 max_width = self.surface.width + EXTRA_FACTOR * thickness
678 max_height = self.surface.height + EXTRA_FACTOR * thickness 679 max_height = self.surface.height + EXTRA_FACTOR * thickness
679 680
@@ -721,8 +722,8 @@ class CairoCanvas(Canvas):
721 722
722 def fill_rect_fade(self, x, y, width, height, lcolor, rcolor, do_snap=True): 723 def fill_rect_fade(self, x, y, width, height, lcolor, rcolor, do_snap=True):
723 """Draws a rectangle somewhere, filled in with the fade.""" 724 """Draws a rectangle somewhere, filled in with the fade."""
724 x, y, width, height = self.scaled(x, y, width, height)
725 x, y = self.surface.get_real_coor(x, y) 725 x, y = self.surface.get_real_coor(x, y)
726 width, height = self.surface.get_real_dim(width, height)
726 727
727 if do_snap: 728 if do_snap:
728 linear = cairo.LinearGradient(snap(x), snap(y), \ 729 linear = cairo.LinearGradient(snap(x), snap(y), \
@@ -741,9 +742,7 @@ class CairoCanvas(Canvas):
741 742
742 def draw_line(self, p0, p1, color, thickness, do_snap=True): 743 def draw_line(self, p0, p1, color, thickness, do_snap=True):
743 """Draws a line from p0 to p1 with a certain color and thickness.""" 744 """Draws a line from p0 to p1 with a certain color and thickness."""
744 p0 = self.scaled(p0[0], p0[1])
745 p0 = self.surface.get_real_coor(p0[0], p0[1]) 745 p0 = self.surface.get_real_coor(p0[0], p0[1])
746 p1 = self.scaled(p1[0], p1[1])
747 p1 = self.surface.get_real_coor(p1[0], p1[1]) 746 p1 = self.surface.get_real_coor(p1[0], p1[1])
748 if do_snap: 747 if do_snap:
749 p0 = (snap(p0[0]), snap(p0[1])) 748 p0 = (snap(p0[0]), snap(p0[1]))
@@ -756,8 +755,7 @@ class CairoCanvas(Canvas):
756 self.surface.ctx.stroke() 755 self.surface.ctx.stroke()
757 756
758 def _polyline_common(self, coor_list, color, thickness, do_snap=True): 757 def _polyline_common(self, coor_list, color, thickness, do_snap=True):
759 scaled_coor_list = [self.scaled(coor[0], coor[1]) for coor in coor_list] 758 real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) for coor in coor_list]
760 real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) for coor in scaled_coor_list]
761 759
762 self.surface.ctx.move_to(real_coor_list[0][0], real_coor_list[0][1]) 760 self.surface.ctx.move_to(real_coor_list[0][0], real_coor_list[0][1])
763 if do_snap: 761 if do_snap:
@@ -780,7 +778,7 @@ class CairoCanvas(Canvas):
780 778
781 def _get_label_dim_common(self, text, x, y, fopts, x_bearing_factor, \ 779 def _get_label_dim_common(self, text, x, y, fopts, x_bearing_factor, \
782 f_descent_factor, width_factor, f_height_factor, 780 f_descent_factor, width_factor, f_height_factor,
783 scale, do_snap=True): 781 do_snap=True):
784 """Helper function for drawing a label with some alignment. Instead of taking in an alignment, 782 """Helper function for drawing a label with some alignment. Instead of taking in an alignment,
785 it takes in the scale factor for the font extent parameters, which give the raw data of how much to adjust 783 it takes in the scale factor for the font extent parameters, which give the raw data of how much to adjust
786 the x and y parameters. Only should be used internally.""" 784 the x and y parameters. Only should be used internally."""
@@ -793,30 +791,30 @@ class CairoCanvas(Canvas):
793 surface.renew(10, 10) 791 surface.renew(10, 10)
794 else: 792 else:
795 surface = self.surface 793 surface = self.surface
796 794
797 surface.ctx.select_font_face(fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) 795 surface.ctx.select_font_face(fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
798 surface.ctx.set_font_size(fopts.size * scale) 796 surface.ctx.set_font_size(fopts.size)
799 797
800 fe = surface.ctx.font_extents() 798 fe = surface.ctx.font_extents()
801 f_ascent, f_descent, f_height = fe[:3] 799 f_ascent, f_descent, f_height = fe[:3]
802 800
803 te = surface.ctx.text_extents(text) 801 te = surface.ctx.text_extents(text)
804 x_bearing, y_bearing, width, height = te[:4] 802 x_bearing, y_bearing, width, height = te[:4]
805 803
806 actual_x = x - x_bearing * x_bearing_factor - width * width_factor 804 actual_x = x - x_bearing * x_bearing_factor - width * width_factor
807 actual_y = y - f_descent * f_descent_factor + f_height * f_height_factor 805 actual_y = y - f_descent * f_descent_factor + f_height * f_height_factor
808 806
809 if do_snap: 807 if do_snap:
810 actual_x = snap(actual_x) 808 actual_x = snap(actual_x)
811 actual_y = snap(actual_y) 809 actual_y = snap(actual_y)
812 810
813 return actual_x, actual_y, width, height, f_height 811 return actual_x, actual_y, width, height, f_height
814 812
815 def get_label_dim(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, 813 def get_label_dim(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT,
816 valign=AlignMode.BOTTOM, do_scale=True, do_snap=True): 814 valign=AlignMode.BOTTOM, do_snap=True):
817 """Gets the dimensions of the label if it were to be drawn with the given parameters, and with the given 815 """Gets the dimensions of the label if it were to be drawn with the given parameters, and with the given
818 justification. Note: this does everything except actually draw the label""" 816 justification. Note: this does everything except actually draw the label"""
819 817
820 x_bearing_factor, f_descent_factor, width_factor, f_height_factor = 0.0, 0.0, 0.0, 0.0 818 x_bearing_factor, f_descent_factor, width_factor, f_height_factor = 0.0, 0.0, 0.0, 0.0
821 halign_factors = {AlignMode.LEFT : (0.0, 0.0), AlignMode.CENTER : (1.0, 0.5), AlignMode.RIGHT : (1.0, 1.0)} 819 halign_factors = {AlignMode.LEFT : (0.0, 0.0), AlignMode.CENTER : (1.0, 0.5), AlignMode.RIGHT : (1.0, 1.0)}
822 if halign not in halign_factors: 820 if halign not in halign_factors:
@@ -827,24 +825,22 @@ class CairoCanvas(Canvas):
827 if valign not in valign_factors: 825 if valign not in valign_factors:
828 raise ValueError('Invalid alignment value') 826 raise ValueError('Invalid alignment value')
829 f_descent_factor, f_height_factor = valign_factors[valign] 827 f_descent_factor, f_height_factor = valign_factors[valign]
830 828
831 scale = 1.0
832 if do_scale:
833 scale = self.scale
834
835 return self._get_label_dim_common(text, x, y, fopts, x_bearing_factor, 829 return self._get_label_dim_common(text, x, y, fopts, x_bearing_factor,
836 f_descent_factor, width_factor, f_height_factor, 830 f_descent_factor, width_factor, f_height_factor,
837 scale, do_snap) 831 do_snap)
838 832
839 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, do_snap=True): 833 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, do_snap=True):
840 """Draws a label with the given parameters, with the given horizontal and vertical justification.""" 834 """Draws a label with the given parameters, with the given horizontal and vertical justification."""
841 835
842 x, y = self.scaled(x, y) 836 actual_x, actual_y, width, height, f_height = self.get_label_dim(text, x, y, fopts, halign, valign, do_snap)
843 actual_x, actual_y, width, height, f_height = self.get_label_dim(text, x, y, fopts, halign, valign, True, do_snap)
844 actual_x, actual_y = self.surface.get_real_coor(actual_x, actual_y) 837 actual_x, actual_y = self.surface.get_real_coor(actual_x, actual_y)
845 838
839 self.surface.ctx.select_font_face(fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
840 self.surface.ctx.set_font_size(fopts.size * self.scale)
846 self.surface.ctx.set_source_rgb(fopts.color[0], fopts.color[1], fopts.color[2]) 841 self.surface.ctx.set_source_rgb(fopts.color[0], fopts.color[1], fopts.color[2])
847 self.surface.ctx.move_to(actual_x, actual_y) 842 self.surface.ctx.move_to(actual_x, actual_y)
843
848 self.surface.ctx.show_text(text) 844 self.surface.ctx.show_text(text)
849 845
850 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \ 846 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \
@@ -881,7 +877,7 @@ def snap(pos):
881 since the line will get distributed over two pixels. We actually apply this to all 877 since the line will get distributed over two pixels. We actually apply this to all
882 coordinates to make sure everything is aligned.""" 878 coordinates to make sure everything is aligned."""
883 return pos - 0.5 879 return pos - 0.5
884 880
885# represents a selectable region of the graph 881# represents a selectable region of the graph
886class SelectableRegion(object): 882class SelectableRegion(object):
887 def __init__(self, x, y, width, height, event): 883 def __init__(self, x, y, width, height, event):
@@ -892,7 +888,6 @@ class SelectableRegion(object):
892 self.width = width 888 self.width = width
893 self.height = height 889 self.height = height
894 self.event = event 890 self.event = event
895 self.scale = 1.0
896 891
897 def get_dimensions(self): 892 def get_dimensions(self):
898 """Gets the dimensions of the region.""" 893 """Gets the dimensions of the region."""
@@ -902,18 +897,15 @@ class SelectableRegion(object):
902 """Gets the event associated with the region.""" 897 """Gets the event associated with the region."""
903 return self.event 898 return self.event
904 899
905 def set_scale(self, scale):
906 self.scale = scale
907
908 def is_dummy(self): 900 def is_dummy(self):
909 return self.x is None or self.y is None 901 return self.x is None or self.y is None
910 902
911 def intersects(self, x, y, width, height): 903 def intersects(self, x, y, width, height):
912 if self.is_dummy(): 904 if self.is_dummy():
913 return False 905 return False
914 906
915 return x <= (self.x + self.width) * self.scale \ 907 return x <= (self.x + self.width) \
916 and x + width >= self.x * self.scale \ 908 and x + width >= self.x \
917 and y <= (self.y + self.height) * self.scale \ 909 and y <= (self.y + self.height) \
918 and y + height >= self.y * self.scale 910 and y + height >= self.y
919 911
diff --git a/unit_trace/viz/format.py b/unit_trace/viz/format.py
index 18dfd5a..13d4843 100644
--- a/unit_trace/viz/format.py
+++ b/unit_trace/viz/format.py
@@ -1,5 +1,7 @@
1"""Various formatting parameters intended to be accessible by the client.""" 1"""Various formatting parameters intended to be accessible by the client."""
2 2
3import util
4
3class FontOptions(object): 5class FontOptions(object):
4 """Class for combining assorted simple font options.""" 6 """Class for combining assorted simple font options."""
5 def __init__(self, name, size, color): 7 def __init__(self, name, size, color):
@@ -58,6 +60,8 @@ class GraphFormat(object):
58 DEF_FOPTS_ARROW = FontOptions("Times", 12, (0.0, 0.0, 0.0)) 60 DEF_FOPTS_ARROW = FontOptions("Times", 12, (0.0, 0.0, 0.0))
59 DEF_FOPTS_ARROW_SSCRIPT = FontOptions("Times", 7, (0.0, 0.0, 0.0)) 61 DEF_FOPTS_ARROW_SSCRIPT = FontOptions("Times", 7, (0.0, 0.0, 0.0))
60 62
63 DEF_TIME_UNIT = util.MilliSec()
64
61 LEFT_SIDE_PAD = 30 65 LEFT_SIDE_PAD = 30
62 WIDTH_PAD = 50 66 WIDTH_PAD = 50
63 HEIGHT_PAD = 150 67 HEIGHT_PAD = 150
@@ -85,7 +89,7 @@ class GraphFormat(object):
85 def __init__(self, time_per_maj=DEF_TIME_PER_MAJ, maj_sep=DEF_MAJ_SEP, 89 def __init__(self, time_per_maj=DEF_TIME_PER_MAJ, maj_sep=DEF_MAJ_SEP,
86 min_per_maj=DEF_MIN_PER_MAJ, y_item_size=DEF_Y_ITEM_SIZE, bar_fopts=DEF_FOPTS_BAR, 90 min_per_maj=DEF_MIN_PER_MAJ, y_item_size=DEF_Y_ITEM_SIZE, bar_fopts=DEF_FOPTS_BAR,
87 item_fopts=DEF_FOPTS_ITEM, show_min=False, majfopts=DEF_FOPTS_MAJ, 91 item_fopts=DEF_FOPTS_ITEM, show_min=False, majfopts=DEF_FOPTS_MAJ,
88 minfopts=DEF_FOPTS_MIN): 92 minfopts=DEF_FOPTS_MIN, unit=DEF_TIME_UNIT):
89 self.time_per_maj = time_per_maj 93 self.time_per_maj = time_per_maj
90 self.maj_sep = maj_sep 94 self.maj_sep = maj_sep
91 self.min_per_maj = min_per_maj 95 self.min_per_maj = min_per_maj
@@ -95,3 +99,4 @@ class GraphFormat(object):
95 self.show_min = show_min 99 self.show_min = show_min
96 self.majfopts = majfopts 100 self.majfopts = majfopts
97 self.minfopts = minfopts 101 self.minfopts = minfopts
102 self.unit = unit
diff --git a/unit_trace/viz/graph.py b/unit_trace/viz/graph.py
index fa0df99..73c4ce4 100644
--- a/unit_trace/viz/graph.py
+++ b/unit_trace/viz/graph.py
@@ -60,25 +60,25 @@ class Graph(object):
60 60
61 #if surface.ctx is None: 61 #if surface.ctx is None:
62 # surface.renew(width, height) 62 # surface.renew(width, height)
63 63
64 # item clist might end before 64 # item clist might end before
65 full_item_clist = [] 65 full_item_clist = []
66 full_bar_plist = [] 66 full_bar_plist = []
67 67
68 for i in range(0, len(self.y_item_list)): 68 for i in range(0, len(self.y_item_list)):
69 full_item_clist.append(item_clist[i % len(item_clist)]) 69 full_item_clist.append(item_clist[i % len(item_clist)])
70 70
71 for i in range(0, len(self.top_item_list)): 71 for i in range(0, len(self.top_item_list)):
72 full_bar_plist.append(bar_plist[i % (len(bar_plist) - 1)]) 72 full_bar_plist.append(bar_plist[i % (len(bar_plist) - 1)])
73 73
74 # last bar pattern is a ``special pattern'' so put it at 74 # last bar pattern is a ``special pattern'' so put it at
75 # the end (and nowhere else) 75 # the end (and nowhere else)
76 full_bar_plist.append(bar_plist[-1]) 76 full_bar_plist.append(bar_plist[-1])
77 77
78 self.canvas = CanvasType(width, height, full_item_clist, full_bar_plist, surface) 78 self.canvas = CanvasType(width, height, full_item_clist, full_bar_plist, surface)
79 79
80 def get_intersecting_regions(self, real_x, real_y, width, height): 80 def get_intersecting_regions(self, x, y, width, height):
81 return self.canvas.get_intersecting_regions(real_x, real_y, width, height) 81 return self.canvas.get_intersecting_regions(x, y, width, height)
82 82
83 def get_width(self): 83 def get_width(self):
84 return self.width 84 return self.width
@@ -103,29 +103,55 @@ class Graph(object):
103 103
104 def get_item_color(self, no): 104 def get_item_color(self, no):
105 return self.canvas.get_item_color(no) 105 return self.canvas.get_item_color(no)
106 106
107 def get_bar_pattern(self, no): 107 def get_bar_pattern(self, no):
108 return self.canvas.get_bar_pattern(no) 108 return self.canvas.get_bar_pattern(no)
109 109
110 def set_bar_pattern(self, no, pattern): 110 def set_bar_pattern(self, no, pattern):
111 self.canvas.set_bar_pattern(no, pattern) 111 self.canvas.set_bar_pattern(no, pattern)
112 112
113 def update_view(self, x, y, width, height, scale, ctx): 113 def real_to_virt(self, x, y, width, height):
114 """Updates the view's window location, scale factor, and Cairo context (i.e. where 114 """Convert real coordinates to virtual coordinates."""
115 virt_x, virt_y = self.canvas.surface.get_virt_coor(x, y)
116 virt_width, virt_height = self.canvas.surface.get_virt_dim(width, height)
117 return (virt_x, virt_y, virt_width, virt_height)
118
119 def real_to_virt_list(self, coors):
120 l = []
121 for coor in coors:
122 l.append(self.real_to_virt(coor[0], coor[1], coor[2], coor[3]))
123 return l
124
125 def virt_to_real(self, x, y, width, height):
126 real_x, real_y = self.canvas.surface.get_real_coor(x, y)
127 real_width, real_height = self.canvas.surface.get_real_dim(width, height)
128 return (real_x, real_y, real_width, real_height)
129
130 def virt_to_real_list(self, coors):
131 l = []
132 for coor in coors:
133 l.append(self.virt_to_real(coor[0], coor[1], coor[2], coor[3]))
134 return l
135
136 def update_view(self, x, y, width, height, scale):
137 """Updates the view's window location and scale factor, and Cairo context (i.e. where
115 the view currently ``is'').""" 138 the view currently ``is'')."""
116 self.canvas.surface.pan(x, y, width, height) 139 self.canvas.surface.pan(x, y, width, height)
117 self.canvas.set_scale(scale) 140 self.canvas.set_scale(scale)
141
142 def load_ctx(self, ctx):
143 """Borrows a context to draw on."""
118 self.canvas.surface.change_ctx(ctx) 144 self.canvas.surface.change_ctx(ctx)
119 145
120 def set_tmp_surface(self, surface): 146 def set_tmp_surface(self, surface):
121 """Changes to a temporary surface, instead of using the default surface (the 147 """Changes to a temporary surface, instead of using the default surface (the
122 surface that writes to the screen.""" 148 surface that writes to the screen."""
123 self.canvas.set_tmp_surface(surface) 149 self.canvas.set_tmp_surface(surface)
124 150
125 def revert_surface(self): 151 def revert_surface(self):
126 """Changes back to the default surface.""" 152 """Changes back to the default surface."""
127 self.canvas.revert_surface() 153 self.canvas.revert_surface()
128 154
129 def _recomp_min_max(self, start_time, end_time, start_item, end_item): 155 def _recomp_min_max(self, start_time, end_time, start_item, end_item):
130 if self.min_time is None or start_time < self.min_time: 156 if self.min_time is None or start_time < self.min_time:
131 self.min_time = start_time 157 self.min_time = start_time
@@ -177,15 +203,12 @@ class Graph(object):
177 def ycoor_to_item_no(self, y): 203 def ycoor_to_item_no(self, y):
178 return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size) 204 return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size)
179 205
180 def get_offset_params(self, real_x, real_y, width, height): 206 def get_offset_params(self, x, y, width, height):
181 x_start, y_start = self.canvas.surface.get_virt_coor_unscaled(real_x, real_y) 207 start_time = self.xcoor_to_time(x)
182 x_end, y_end = self.canvas.surface.get_virt_coor_unscaled(real_x + width, real_y + height) 208 end_time = self.xcoor_to_time(x + width)
183 209
184 start_time = self.xcoor_to_time(x_start) 210 start_item = self.ycoor_to_item_no(y)
185 end_time = self.xcoor_to_time(x_end) 211 end_item = 1 + self.ycoor_to_item_no(y + height)
186
187 start_item = self.ycoor_to_item_no(y_start)
188 end_item = 1 + self.ycoor_to_item_no(y_end)
189 212
190 return (start_time, end_time, start_item, end_item) 213 return (start_time, end_time, start_item, end_item)
191 214
@@ -199,7 +222,7 @@ class Graph(object):
199 self.canvas.whiteout() 222 self.canvas.whiteout()
200 start, end = sched.get_time_bounds() 223 start, end = sched.get_time_bounds()
201 self.draw_skeleton(start, end, 0, len(self.y_item_list)) 224 self.draw_skeleton(start, end, 0, len(self.y_item_list))
202 225
203 slots = {} 226 slots = {}
204 all_events = {} 227 all_events = {}
205 sched.get_time_slot_array().get_slots(slots, 228 sched.get_time_slot_array().get_slots(slots,
@@ -210,7 +233,7 @@ class Graph(object):
210 if event.get_layer() not in all_events: 233 if event.get_layer() not in all_events:
211 all_events[event.get_layer()] = {} 234 all_events[event.get_layer()] = {}
212 all_events[event.get_layer()][event] = None 235 all_events[event.get_layer()][event] = None
213 236
214 self.render_events(all_events) 237 self.render_events(all_events)
215 238
216 def get_events_to_render(self, sched, regions, selectable=False): 239 def get_events_to_render(self, sched, regions, selectable=False):
@@ -221,15 +244,15 @@ class Graph(object):
221 x, y, width, height = region 244 x, y, width, height = region
222 start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height) 245 start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height)
223 self._recomp_min_max(start_time, end_time, start_item, end_item) 246 self._recomp_min_max(start_time, end_time, start_item, end_item)
224 247
225 sched.get_time_slot_array().get_slots(slots, 248 sched.get_time_slot_array().get_slots(slots,
226 start_time, end_time, start_item, end_item, 249 start_time, end_time, start_item, end_item,
227 self.list_type) 250 self.list_type)
228 251
229 events_to_render = {} 252 events_to_render = {}
230 for layer in Canvas.LAYERS: 253 for layer in Canvas.LAYERS:
231 events_to_render[layer] = {} 254 events_to_render[layer] = {}
232 255
233 for event in sched.get_time_slot_array().get_events(slots, 256 for event in sched.get_time_slot_array().get_events(slots,
234 self.list_type, schedule.EVENT_LIST): 257 self.list_type, schedule.EVENT_LIST):
235 events_to_render[event.get_layer()][event] = None 258 events_to_render[event.get_layer()][event] = None
@@ -246,7 +269,7 @@ class Graph(object):
246 self.draw_skeleton(self.min_time, self.max_time, 269 self.draw_skeleton(self.min_time, self.max_time,
247 self.min_item, self.max_item) 270 self.min_item, self.max_item)
248 self.render_events(events, selectable) 271 self.render_events(events, selectable)
249 272
250 def render_events(self, events, selectable=False): 273 def render_events(self, events, selectable=False):
251 for layer in Canvas.LAYERS: 274 for layer in Canvas.LAYERS:
252 prev_events = {} 275 prev_events = {}
@@ -278,7 +301,7 @@ class Graph(object):
278 self.canvas.draw_grid(self.origin[0], self.origin[1], self._get_y_axis_height(), 301 self.canvas.draw_grid(self.origin[0], self.origin[1], self._get_y_axis_height(),
279 start_tick, end_tick, start_item, end_item, self.attrs.maj_sep, self.attrs.y_item_size, \ 302 start_tick, end_tick, start_item, end_item, self.attrs.maj_sep, self.attrs.y_item_size, \
280 self.attrs.min_per_maj, True) 303 self.attrs.min_per_maj, True)
281 304
282 def draw_x_axis_with_labels_at_time(self, start_time, end_time): 305 def draw_x_axis_with_labels_at_time(self, start_time, end_time):
283 start_tick = max(0, self._get_bottom_tick(start_time)) 306 start_tick = max(0, self._get_bottom_tick(start_time))
284 end_tick = min(self.num_maj - 1, self._get_top_tick(end_time)) 307 end_tick = min(self.num_maj - 1, self._get_top_tick(end_time))
@@ -288,33 +311,33 @@ class Graph(object):
288 self.canvas.draw_x_axis_labels(self.origin[0], self.origin[1], start_tick, \ 311 self.canvas.draw_x_axis_labels(self.origin[0], self.origin[1], start_tick, \
289 end_tick, self.attrs.maj_sep, self.attrs.min_per_maj, 312 end_tick, self.attrs.maj_sep, self.attrs.min_per_maj,
290 self.start_time + start_tick * self.attrs.time_per_maj, 313 self.start_time + start_tick * self.attrs.time_per_maj,
291 self.attrs.time_per_maj, False) 314 self.attrs.time_per_maj, False, self.attrs.unit)
292 315
293 def draw_y_axis(self): 316 def draw_y_axis(self):
294 self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height()) 317 self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height())
295 #self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), \ 318 #self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), \
296 # self.y_item_list, self.attrs.y_item_size) 319 # self.y_item_list, self.attrs.y_item_size)
297 320
298 def draw_y_axis_item(self, item_no, selected): 321 def draw_y_axis_item(self, item_no, selected):
299 self.canvas.draw_y_axis_item(self.origin[0], self.origin[1], self._get_y_axis_height(), 322 self.canvas.draw_y_axis_item(self.origin[0], self.origin[1], self._get_y_axis_height(),
300 self.y_item_list[item_no], item_no, self.attrs.y_item_size, 323 self.y_item_list[item_no], item_no, self.attrs.y_item_size,
301 selected) 324 selected)
302 325
303 def add_sel_y_axis_item(self, item_no, event): 326 def add_sel_y_axis_item(self, item_no, event):
304 self.canvas.add_sel_y_axis_item(self.origin[0], self.origin[1], self._get_y_axis_height(), 327 self.canvas.add_sel_y_axis_item(self.origin[0], self.origin[1], self._get_y_axis_height(),
305 self.y_item_list[item_no], item_no, self.attrs.y_item_size, 328 self.y_item_list[item_no], item_no, self.attrs.y_item_size,
306 event) 329 event)
307 330
308 def draw_top_item(self, item_no, selected): 331 def draw_top_item(self, item_no, selected):
309 self.canvas.draw_top_item(self.origin[0], self.origin[1], self._get_y_axis_height(), 332 self.canvas.draw_top_item(self.origin[0], self.origin[1], self._get_y_axis_height(),
310 self.top_item_list[item_no], item_no, self.top_item_list[0:item_no], 333 self.top_item_list[item_no], item_no, self.top_item_list[0:item_no],
311 selected) 334 selected)
312 335
313 def add_sel_top_item(self, item_no, event): 336 def add_sel_top_item(self, item_no, event):
314 self.canvas.add_sel_top_item(self.origin[0], self.origin[1], self._get_y_axis_height(), 337 self.canvas.add_sel_top_item(self.origin[0], self.origin[1], self._get_y_axis_height(),
315 self.top_item_list[item_no], item_no, self.top_item_list[0:item_no], 338 self.top_item_list[item_no], item_no, self.top_item_list[0:item_no],
316 event) 339 event)
317 340
318 def all_one_item_selected(self, selected): 341 def all_one_item_selected(self, selected):
319 """When the user selects some parts of the graph, it might be the case that all of the 342 """When the user selects some parts of the graph, it might be the case that all of the
320 parts lie under one ``item'' of the graph. In this case, certain operations are likely 343 parts lie under one ``item'' of the graph. In this case, certain operations are likely
@@ -322,19 +345,19 @@ class Graph(object):
322 a set of selected items satisfies this criterion. It also gives the item selected if 345 a set of selected items satisfies this criterion. It also gives the item selected if
323 so.""" 346 so."""
324 raise NotImplementedError 347 raise NotImplementedError
325 348
326 def draw_cpu_item(self, item_no, selected): 349 def draw_cpu_item(self, item_no, selected):
327 raise NotImplementedError 350 raise NotImplementedError
328 351
329 def add_sel_cpu_item(self, item_no, event): 352 def add_sel_cpu_item(self, item_no, event):
330 raise NotImplementedError 353 raise NotImplementedError
331 354
332 def draw_task_item(self, item_no, selected): 355 def draw_task_item(self, item_no, selected):
333 raise NotImplementedError 356 raise NotImplementedError
334 357
335 def add_sel_task_item(self, item_no, event): 358 def add_sel_task_item(self, item_no, event):
336 raise NotImplementedError 359 raise NotImplementedError
337 360
338 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 361 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
339 """Draws a suspension symbol for a dcertain task at an instant in time.""" 362 """Draws a suspension symbol for a dcertain task at an instant in time."""
340 raise NotImplementedError 363 raise NotImplementedError
@@ -404,7 +427,7 @@ class TaskGraph(Graph):
404 super(TaskGraph, self).__init__(CanvasType, surface, start_time, end_time, y_item_list, top_item_list, 427 super(TaskGraph, self).__init__(CanvasType, surface, start_time, end_time, y_item_list, top_item_list,
405 attrs, item_clist, bar_plist) 428 attrs, item_clist, bar_plist)
406 self.list_type = schedule.TimeSlotArray.TASK_LIST 429 self.list_type = schedule.TimeSlotArray.TASK_LIST
407 430
408 def all_one_item_selected(self, selected): 431 def all_one_item_selected(self, selected):
409 cpu = None 432 cpu = None
410 for layer in selected: 433 for layer in selected:
@@ -414,21 +437,21 @@ class TaskGraph(Graph):
414 else: 437 else:
415 if event.get_cpu() is None or event.get_cpu() != cpu: 438 if event.get_cpu() is None or event.get_cpu() != cpu:
416 return None 439 return None
417 440
418 return cpu 441 return cpu
419 442
420 def draw_cpu_item(self, item_no, selected): 443 def draw_cpu_item(self, item_no, selected):
421 self.draw_top_item(item_no, selected) 444 self.draw_top_item(item_no, selected)
422 445
423 def add_sel_cpu_item(self, item_no, event): 446 def add_sel_cpu_item(self, item_no, event):
424 self.add_sel_top_item(item_no, event) 447 self.add_sel_top_item(item_no, event)
425 448
426 def draw_task_item(self, item_no, selected): 449 def draw_task_item(self, item_no, selected):
427 self.draw_y_axis_item(item_no, selected) 450 self.draw_y_axis_item(item_no, selected)
428 451
429 def add_sel_task_item(self, item_no, event): 452 def add_sel_task_item(self, item_no, event):
430 self.add_sel_y_axis_item(item_no, event) 453 self.add_sel_y_axis_item(item_no, event)
431 454
432 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 455 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
433 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR 456 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
434 x = self.get_time_xpos(time) 457 x = self.get_time_xpos(time)
@@ -563,7 +586,7 @@ class CpuGraph(Graph):
563 super(CpuGraph, self).__init__(CanvasType, surface, start_time, end_time, y_item_list, top_item_list, 586 super(CpuGraph, self).__init__(CanvasType, surface, start_time, end_time, y_item_list, top_item_list,
564 attrs, item_clist, bar_plist) 587 attrs, item_clist, bar_plist)
565 self.list_type = schedule.TimeSlotArray.CPU_LIST 588 self.list_type = schedule.TimeSlotArray.CPU_LIST
566 589
567 def all_one_item_selected(self, selected): 590 def all_one_item_selected(self, selected):
568 task_no = None 591 task_no = None
569 for layer in selected: 592 for layer in selected:
@@ -573,21 +596,21 @@ class CpuGraph(Graph):
573 else: 596 else:
574 if event.get_task() is None or event.get_task().get_task_no() != task_no: 597 if event.get_task() is None or event.get_task().get_task_no() != task_no:
575 return None 598 return None
576 599
577 return task_no 600 return task_no
578 601
579 def draw_cpu_item(self, item_no, selected): 602 def draw_cpu_item(self, item_no, selected):
580 self.draw_y_axis_item(item_no, selected) 603 self.draw_y_axis_item(item_no, selected)
581 604
582 def add_sel_cpu_item(self, item_no, event): 605 def add_sel_cpu_item(self, item_no, event):
583 self.add_sel_y_axis_item(item_no, event) 606 self.add_sel_y_axis_item(item_no, event)
584 607
585 def draw_task_item(self, item_no, selected): 608 def draw_task_item(self, item_no, selected):
586 self.draw_top_item(item_no, selected) 609 self.draw_top_item(item_no, selected)
587 610
588 def add_sel_task_item(self, item_no, event): 611 def add_sel_task_item(self, item_no, event):
589 self.add_sel_top_item(item_no, event) 612 self.add_sel_top_item(item_no, event)
590 613
591 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 614 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
592 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR 615 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
593 x = self.get_time_xpos(time) 616 x = self.get_time_xpos(time)
diff --git a/unit_trace/viz/schedule.py b/unit_trace/viz/schedule.py
index 82b7ea1..ec6398d 100644
--- a/unit_trace/viz/schedule.py
+++ b/unit_trace/viz/schedule.py
@@ -22,14 +22,14 @@ class TimeSlotArray(object):
22 TASK_LIST = 0 22 TASK_LIST = 0
23 CPU_LIST = 1 23 CPU_LIST = 1
24 LIST_TYPES = (TASK_LIST, CPU_LIST) 24 LIST_TYPES = (TASK_LIST, CPU_LIST)
25 25
26 PRE_ITEM_NO = -1 26 PRE_ITEM_NO = -1
27 POST_ITEM_NO = -2 27 POST_ITEM_NO = -2
28 28
29 PRE_SLOT = 'pre' 29 PRE_SLOT = 'pre'
30 POST_SLOT = 'post' 30 POST_SLOT = 'post'
31 ALL_SLOT = 'all' 31 ALL_SLOT = 'all'
32 32
33 def __init__(self, time_per_maj=None, num_tasks=0, num_cpus=0): 33 def __init__(self, time_per_maj=None, num_tasks=0, num_cpus=0):
34 if time_per_maj is None: 34 if time_per_maj is None:
35 self.array = None 35 self.array = None
@@ -40,7 +40,7 @@ class TimeSlotArray(object):
40 self.array = {} 40 self.array = {}
41 self.min_slot = None 41 self.min_slot = None
42 self.max_slot = None 42 self.max_slot = None
43 43
44 for type in self.list_sizes: 44 for type in self.list_sizes:
45 num = self.list_sizes[type] 45 num = self.list_sizes[type]
46 self.array[type] = [] 46 self.array[type] = []
@@ -53,12 +53,12 @@ class TimeSlotArray(object):
53 53
54 def get_time_slot(self, time): 54 def get_time_slot(self, time):
55 return int(time // self.time_per_maj) 55 return int(time // self.time_per_maj)
56 56
57 def put_event_in_slot(self, list_type, no, klass, slot, event): 57 def put_event_in_slot(self, list_type, no, klass, slot, event):
58 if slot not in self.array[list_type][no][klass]: 58 if slot not in self.array[list_type][no][klass]:
59 self.array[list_type][no][klass][slot] = [] 59 self.array[list_type][no][klass][slot] = []
60 self.array[list_type][no][klass][slot].append(event) 60 self.array[list_type][no][klass][slot].append(event)
61 61
62 if self.min_slot is None or slot < self.min_slot: 62 if self.min_slot is None or slot < self.min_slot:
63 self.min_slot = slot 63 self.min_slot = slot
64 if self.max_slot is None or slot > self.max_slot: 64 if self.max_slot is None or slot > self.max_slot:
@@ -69,10 +69,10 @@ class TimeSlotArray(object):
69 cpu = event.get_cpu() 69 cpu = event.get_cpu()
70 70
71 slot = self.get_time_slot(event.get_time()) 71 slot = self.get_time_slot(event.get_time())
72 72
73 for type in item_nos: 73 for type in item_nos:
74 self.put_event_in_slot(type, item_nos[type], event.__class__, slot, event) 74 self.put_event_in_slot(type, item_nos[type], event.__class__, slot, event)
75 75
76 def fill_span_event_from_start(self, event, item_nos): 76 def fill_span_event_from_start(self, event, item_nos):
77 end_slot = None 77 end_slot = None
78 if event.corresp_end_event is None: 78 if event.corresp_end_event is None:
@@ -104,17 +104,17 @@ class TimeSlotArray(object):
104 104
105 for type in item_nos: 105 for type in item_nos:
106 self.put_event_in_slot(type, item_nos[type], event.dummy_class, slot, dummy) 106 self.put_event_in_slot(type, item_nos[type], event.dummy_class, slot, dummy)
107 107
108 def add_item_dummies(self, sched): 108 def add_item_dummies(self, sched):
109 start = sched.get_time_bounds()[0] 109 start = sched.get_time_bounds()[0]
110 110
111 for task in sched.get_tasks().values(): 111 for task in sched.get_tasks().values():
112 dummy = TaskDummy(start, task) 112 dummy = TaskDummy(start, task)
113 self.put_event_in_slot(TimeSlotArray.TASK_LIST, task.get_task_no(), dummy.__class__, 113 self.put_event_in_slot(TimeSlotArray.TASK_LIST, task.get_task_no(), dummy.__class__,
114 TimeSlotArray.PRE_SLOT, dummy) 114 TimeSlotArray.PRE_SLOT, dummy)
115 self.put_event_in_slot(TimeSlotArray.CPU_LIST, TimeSlotArray.PRE_ITEM_NO, dummy.__class__, 115 self.put_event_in_slot(TimeSlotArray.CPU_LIST, TimeSlotArray.PRE_ITEM_NO, dummy.__class__,
116 TimeSlotArray.ALL_SLOT, dummy) 116 TimeSlotArray.ALL_SLOT, dummy)
117 117
118 for i in range(0, self.list_sizes[TimeSlotArray.CPU_LIST]): 118 for i in range(0, self.list_sizes[TimeSlotArray.CPU_LIST]):
119 dummy = CPUDummy(start, sched, i) 119 dummy = CPUDummy(start, sched, i)
120 self.put_event_in_slot(TimeSlotArray.CPU_LIST, i, dummy.__class__, 120 self.put_event_in_slot(TimeSlotArray.CPU_LIST, i, dummy.__class__,
@@ -135,14 +135,14 @@ class TimeSlotArray(object):
135 raise ValueError('Litmus is not a time machine') 135 raise ValueError('Litmus is not a time machine')
136 if start_no > end_no: 136 if start_no > end_no:
137 raise ValueError('start no should be less than end no') 137 raise ValueError('start no should be less than end no')
138 138
139 if self.min_slot is None: 139 if self.min_slot is None:
140 # no events ever got added 140 # no events ever got added
141 return {} 141 return {}
142 142
143 start_slot = self.get_time_slot(start) 143 start_slot = self.get_time_slot(start)
144 end_slot = self.get_time_slot(end) + 1 144 end_slot = self.get_time_slot(end) + 1
145 145
146 item_no_list = [] 146 item_no_list = []
147 if start_no < 0: 147 if start_no < 0:
148 item_no_list.append(TimeSlotArray.PRE_ITEM_NO) 148 item_no_list.append(TimeSlotArray.PRE_ITEM_NO)
@@ -151,24 +151,24 @@ class TimeSlotArray(object):
151 start_no = max(0, start_no) 151 start_no = max(0, start_no)
152 end_no = min(self.list_sizes[list_type] - 1, end_no) 152 end_no = min(self.list_sizes[list_type] - 1, end_no)
153 item_no_list += range(start_no, end_no + 1) 153 item_no_list += range(start_no, end_no + 1)
154 154
155 edge_slots = [TimeSlotArray.ALL_SLOT] 155 edge_slots = [TimeSlotArray.ALL_SLOT]
156 if start_slot <= self.min_slot: 156 if start_slot <= self.min_slot:
157 edge_slots.append(TimeSlotArray.PRE_SLOT) 157 edge_slots.append(TimeSlotArray.PRE_SLOT)
158 if end_slot >= self.max_slot: 158 if end_slot >= self.max_slot:
159 edge_slots.append(TimeSlotArray.POST_SLOT) 159 edge_slots.append(TimeSlotArray.POST_SLOT)
160 160
161 for slot in edge_slots: 161 for slot in edge_slots:
162 slots[slot] = {} 162 slots[slot] = {}
163 for no in item_no_list: 163 for no in item_no_list:
164 slots[slot][no] = None 164 slots[slot][no] = None
165 165
166 for slot in xrange(start_slot, end_slot + 1): 166 for slot in xrange(start_slot, end_slot + 1):
167 if slot not in slots: 167 if slot not in slots:
168 slots[slot] = {} 168 slots[slot] = {}
169 for no in item_no_list: 169 for no in item_no_list:
170 slots[slot][no] = None 170 slots[slot][no] = None
171 171
172class Schedule(object): 172class Schedule(object):
173 """The total schedule (task system), consisting of a certain number of 173 """The total schedule (task system), consisting of a certain number of
174 tasks.""" 174 tasks."""
@@ -213,8 +213,8 @@ class Schedule(object):
213 self.time_slot_array = TimeSlotArray() 213 self.time_slot_array = TimeSlotArray()
214 return 214 return
215 215
216 self.time_slot_array = TimeSlotArray(self.time_per_maj, \ 216 self.time_slot_array = TimeSlotArray(self.time_per_maj,
217 len(self.task_list), self.num_cpus) 217 len(self.task_list), self.num_cpus)
218 218
219 def get_time_slot_array(self): 219 def get_time_slot_array(self):
220 return self.time_slot_array 220 return self.time_slot_array
@@ -226,6 +226,7 @@ class Schedule(object):
226 self.start = None 226 self.start = None
227 self.end = None 227 self.end = None
228 228
229 self.sort_task_nos_numeric()
229 self.set_time_params(time_per_maj) 230 self.set_time_params(time_per_maj)
230 231
231 # we scan the graph task by task, and job by job 232 # we scan the graph task by task, and job by job
@@ -250,10 +251,10 @@ class Schedule(object):
250 switch_event = switches[event] 251 switch_event = switches[event]
251 if switch_event is not None: 252 if switch_event is not None:
252 event.fill_span_event_from_start() 253 event.fill_span_event_from_start()
253 254
254 # add events that correspond to the tasks and CPUS, at the very beginning 255 # add events that correspond to the tasks and CPUS, at the very beginning
255 self.time_slot_array.add_item_dummies(self) 256 self.time_slot_array.add_item_dummies(self)
256 257
257 def add_task(self, task): 258 def add_task(self, task):
258 if task.name in self.tasks: 259 if task.name in self.tasks:
259 raise ValueError("task already in list!") 260 raise ValueError("task already in list!")
@@ -263,6 +264,17 @@ class Schedule(object):
263 task.task_no = self.cur_task_no 264 task.task_no = self.cur_task_no
264 self.cur_task_no += 1 265 self.cur_task_no += 1
265 266
267 def sort_task_nos_numeric(self):
268 # sort task numbers by the numeric value of the task names.
269 nums = []
270
271 for task_name in self.tasks:
272 nums.append((int(task_name), task_name))
273
274 nums.sort(key=lambda t: t[0])
275 for no, task in enumerate(nums):
276 self.tasks[task[1]].task_no = no
277
266 def get_tasks(self): 278 def get_tasks(self):
267 return self.tasks 279 return self.tasks
268 280
@@ -281,6 +293,11 @@ def deepcopy_selected(selected):
281 selected_copy[layer] = copy.copy(selected[layer]) 293 selected_copy[layer] = copy.copy(selected[layer])
282 return selected_copy 294 return selected_copy
283 295
296def _format_time(time, unit):
297 if time is None:
298 return '(None)'
299 return util.format_float(unit.nsec_to_native(time), Event.NUM_DEC_PLACES) \
300 + ' ' + str(unit)
284class Task(object): 301class Task(object):
285 """Represents a task, including the set of jobs that were run under 302 """Represents a task, including the set of jobs that were run under
286 this task.""" 303 this task."""
@@ -317,15 +334,75 @@ class Job(object):
317 self.job_no = job_no 334 self.job_no = job_no
318 self.events = {} 335 self.events = {}
319 self.task = None 336 self.task = None
337 self.start_run_time = None
338 self.end_run_time = None
339 self.release_time = None
340 self.deadline_time = None
320 for event in event_list: 341 for event in event_list:
321 self.add_event(event) 342 self.add_event(event)
322 343
344 def get_name(self):
345 return 'Job ' + str(self.job_no) + ' for task ' + str(self.get_task().get_task_no())
346
347 def str_long(self, unit):
348 duration = None
349 if self.start_run_time is not None and self.end_run_time is not None:
350 duration = self.end_run_time - self.start_run_time
351
352 return_str = 'Job Information\n---------------' + \
353 '\nTask Name: ' + str(self.get_task().get_name()) + \
354 '\n(Task no., Job no.): ' + str((self.get_task().get_task_no(), \
355 self.get_job_no())) + \
356 '\nStart Run Time: ' + _format_time(self.start_run_time, unit)
357
358 if duration is not None:
359 return_str += '\nDuration: ' + _format_time(duration, unit)
360
361 return_str += '\nEnd Run Time: ' + _format_time(self.end_run_time, unit) \
362 + '\nRelease Time: ' + _format_time(self.release_time, unit) \
363 + '\nDeadline Time: ' + _format_time(self.deadline_time, unit)
364
365 return return_str
366
367 def str_run_short(self, unit):
368 start = self.start_run_time
369 end = self.end_run_time
370 duration = None
371 if start is not None and end is not None:
372 duration = end - start
373
374 return_str = self.get_name() + ': START=' + _format_time(start, unit)
375
376 if duration is not None:
377 return_str += ', DUR=' + _format_time(duration, unit)
378
379 return_str += ', END=' + _format_time(end, unit)
380
381 return return_str
382
383 def str_rd_short(self, unit):
384 return self.get_name() + ': RELEASE=' + _format_time(self.release_time, unit) \
385 + ', DEADLINE=' + _format_time(self.deadline_time, unit)
386
323 def add_event(self, event): 387 def add_event(self, event):
324 if event.time not in self.events: 388 if event.time not in self.events:
325 self.events[event.time] = [] 389 self.events[event.get_time()] = []
326 self.events[event.time].append(event) 390 self.events[event.get_time()].append(event)
327 event.job = self 391 event.job = self
328 392
393 # update some statistics
394 if isinstance(event, ReleaseEvent):
395 if self.release_time is None or event.get_time() < self.release_time:
396 self.release_time = event.get_time()
397 elif isinstance(event, DeadlineEvent):
398 if self.deadline_time is None or event.get_time() > self.deadline_time:
399 self.deadline_time = event.get_time()
400 else:
401 if self.start_run_time is None or event.get_time() < self.start_run_time:
402 self.start_run_time = event.get_time()
403 if self.end_run_time is None or event.get_time() > self.end_run_time:
404 self.end_run_time = event.get_time()
405
329 def get_events(self): 406 def get_events(self):
330 return self.events 407 return self.events
331 408
@@ -348,9 +425,6 @@ class DummyEvent(object):
348 self.job = None 425 self.job = None
349 self.layer = None 426 self.layer = None
350 427
351 def __str__(self):
352 return '[Dummy Event]'
353
354 def get_time(self): 428 def get_time(self):
355 return self.time 429 return self.time
356 430
@@ -359,21 +433,21 @@ class DummyEvent(object):
359 433
360 def get_schedule(self): 434 def get_schedule(self):
361 return self.get_task().get_schedule() 435 return self.get_task().get_schedule()
362 436
363 def get_task(self): 437 def get_task(self):
364 return self.get_job().get_task() 438 return self.get_job().get_task()
365 439
366 def get_job(self): 440 def get_job(self):
367 return self.job 441 return self.job
368 442
369 def get_layer(self): 443 def get_layer(self):
370 return self.layer 444 return self.layer
371 445
372 def is_selected(self): 446 def is_selected(self):
373 """Returns whether the event has been selected by the user. (needed for rendering)""" 447 """Returns whether the event has been selected by the user. (needed for rendering)"""
374 selected = self.get_schedule().get_selected() 448 selected = self.get_schedule().get_selected()
375 return self.get_layer() in selected and self in selected[self.get_layer()] 449 return self.get_layer() in selected and self in selected[self.get_layer()]
376 450
377 def render(self, graph, layer, prev_events, selectable=False): 451 def render(self, graph, layer, prev_events, selectable=False):
378 """Method that the visualizer calls to tell the event to render itself 452 """Method that the visualizer calls to tell the event to render itself
379 Obviously only implemented by subclasses (actual event types) 453 Obviously only implemented by subclasses (actual event types)
@@ -396,17 +470,21 @@ class Event(DummyEvent):
396 def get_name(self): 470 def get_name(self):
397 raise NotImplementedError 471 raise NotImplementedError
398 472
399 def __str__(self): 473 def str_short(self, unit):
400 return self.get_name() + self._common_str() + ', TIME=' + util.format_float(self.get_time(), Event.NUM_DEC_PLACES) 474 return self.get_name() + self._common_str() + ', TIME=' + \
475 util.format_float(unit.nsec_to_native(self.get_time()), Event.NUM_DEC_PLACES) + \
476 ' ' + str(unit)
401 477
402 def str_long(self): 478 def str_long(self, unit):
403 """Prints the event as a string, in ``long'' form.""" 479 """Prints the event as a string, in ``long'' form."""
404 return 'Event Type: ' + self.get_name() + \ 480 return 'Event Information\n-----------------\n' + \
481 'Event Type: ' + self.get_name() + \
405 '\nTask Name: ' + str(self.get_job().get_task().get_name()) + \ 482 '\nTask Name: ' + str(self.get_job().get_task().get_name()) + \
406 '\n(Task no., Job no.): ' + str((self.get_job().get_task().get_task_no(), \ 483 '\n(Task no., Job no.): ' + str((self.get_job().get_task().get_task_no(), \
407 self.get_job().get_job_no())) + \ 484 self.get_job().get_job_no())) + \
408 '\nCPU: ' + str(self.get_cpu()) + \ 485 '\nCPU: ' + str(self.get_cpu()) + \
409 '\nTime: ' + str(self.get_time()) 486 '\nTime: ' + _format_time(self.get_time(), unit) + \
487 '\n\n' + self.get_job().str_long(unit)
410 488
411 def _common_str(self): 489 def _common_str(self):
412 job = self.get_job() 490 job = self.get_job()
@@ -440,7 +518,7 @@ class Event(DummyEvent):
440 TimeSlotArray.CPU_LIST : self.get_cpu() } 518 TimeSlotArray.CPU_LIST : self.get_cpu() }
441 sched.get_time_slot_array().add_event_to_time_slot(self, item_nos) 519 sched.get_time_slot_array().add_event_to_time_slot(self, item_nos)
442 self.fill_span_event_from_end() 520 self.fill_span_event_from_end()
443 521
444 def fill_span_event_from_start(self): 522 def fill_span_event_from_start(self):
445 """This method exists for events that can ``range'' over a period of time 523 """This method exists for events that can ``range'' over a period of time
446 (e.g. SwitchAway and SwitchTo). In case a start event is not paired with 524 (e.g. SwitchAway and SwitchTo). In case a start event is not paired with
@@ -448,11 +526,11 @@ class Event(DummyEvent):
448 the way to the beginning or end. Since most events occur only at a specific 526 the way to the beginning or end. Since most events occur only at a specific
449 time, this is usually a no-op.""" 527 time, this is usually a no-op."""
450 pass 528 pass
451 529
452 def fill_span_event_from_end(self): 530 def fill_span_event_from_end(self):
453 """The mirror image of the last method.""" 531 """The mirror image of the last method."""
454 pass 532 pass
455 533
456class SpanEvent(Event): 534class SpanEvent(Event):
457 def __init__(self, time, cpu, dummy_class): 535 def __init__(self, time, cpu, dummy_class):
458 super(SpanEvent, self).__init__(time, cpu) 536 super(SpanEvent, self).__init__(time, cpu)
@@ -461,54 +539,93 @@ class SpanEvent(Event):
461class SpanDummy(DummyEvent): 539class SpanDummy(DummyEvent):
462 def __init__(self): 540 def __init__(self):
463 super(SpanDummy, self).__init__(None, None) 541 super(SpanDummy, self).__init__(None, None)
464 542
465 def get_task(self): 543 def get_task(self):
466 if self.corresp_start_event is not None: 544 if self.corresp_start_event is not None:
467 return self.corresp_start_event.get_task() 545 return self.corresp_start_event.get_task()
468 if self.corresp_end_event is not None: 546 if self.corresp_end_event is not None:
469 return self.corresp_end_event.get_task() 547 return self.corresp_end_event.get_task()
470 return None 548 return None
471 549
472 def get_schedule(self): 550 def get_schedule(self):
473 if self.corresp_start_event is not None: 551 if self.corresp_start_event is not None:
474 return self.corresp_start_event.get_schedule() 552 return self.corresp_start_event.get_schedule()
475 if self.corresp_end_event is not None: 553 if self.corresp_end_event is not None:
476 return self.corresp_end_event.get_schedule() 554 return self.corresp_end_event.get_schedule()
477 return None 555 return None
478 556
479 def get_job(self): 557 def get_job(self):
480 if self.corresp_start_event is not None: 558 if self.corresp_start_event is not None:
481 return self.corresp_start_event.get_job() 559 return self.corresp_start_event.get_job()
482 if self.corresp_end_event is not None: 560 if self.corresp_end_event is not None:
483 return self.corresp_end_event.get_job() 561 return self.corresp_end_event.get_job()
484 return None 562 return None
485 563
486 def get_cpu(self): 564 def get_cpu(self):
487 if self.corresp_start_event is not None: 565 if self.corresp_start_event is not None:
488 return self.corresp_start_event.get_cpu() 566 return self.corresp_start_event.get_cpu()
489 if self.corresp_end_event is not None: 567 if self.corresp_end_event is not None:
490 return self.corresp_end_event.get_cpu() 568 return self.corresp_end_event.get_cpu()
491 return None 569 return None
492 570
493 def get_time(self): 571 def get_time(self):
494 if self.corresp_start_event is not None: 572 if self.corresp_start_event is not None:
495 return self.corresp_start_event.get_time() 573 return self.corresp_start_event.get_time()
496 if self.corresp_end_event is not None: 574 if self.corresp_end_event is not None:
497 return self.corresp_end_event.get_time() 575 return self.corresp_end_event.get_time()
498 return None 576 return None
499 577
500class StartSpanEvent(SpanEvent): 578class StartSpanEvent(SpanEvent):
579 def str_short(self, unit):
580 if self.corresp_end_event is None:
581 return super(StartSpanEvent, self).str_short(unit)
582 start = self.get_time()
583 end = self.corresp_end_event.get_time()
584 duration = end - start
585 return self.get_name() + self._common_str() + ', START=' \
586 + _format_time(start, unit) \
587 + ', DUR=' + _format_time(duration, unit) \
588 + ', END=' + _format_time(end, unit)
589
590 def str_long(self, unit):
591 if self.corresp_end_event is None:
592 return super(StartSpanEvent, self).str_long(unit)
593 else:
594 start = self.get_time()
595 end = self.corresp_end_event.get_time()
596 duration = end - start
597 return 'Event Type: ' + self.get_name() + \
598 '\nTask Name: ' + str(self.get_job().get_task().get_name()) + \
599 '\n(Task no., Job no.): ' + str((self.get_job().get_task().get_task_no(), \
600 self.get_job().get_job_no())) + \
601 '\nCPU: ' + str(self.get_cpu()) + \
602 '\nStart: ' + _format_time(start, unit) + \
603 '\nDuration: ' + _format_time(duration, unit) + \
604 '\nEnd: ' + _format_time(end, unit) + \
605 '\n\n' + self.get_job().str_long(unit)
606
501 def fill_span_event_from_start(self): 607 def fill_span_event_from_start(self):
502 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(), 608 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
503 TimeSlotArray.CPU_LIST : self.get_cpu() } 609 TimeSlotArray.CPU_LIST : self.get_cpu() }
504 self.get_schedule().get_time_slot_array().fill_span_event_from_start(self, item_nos) 610 self.get_schedule().get_time_slot_array().fill_span_event_from_start(self, item_nos)
505 611
506class EndSpanEvent(SpanEvent): 612class EndSpanEvent(SpanEvent):
613 def str_short(self, unit):
614 if self.corresp_start_event is None:
615 return super(EndSpanEvent, self).str_short(unit)
616 return self.corresp_start_event.str_short(unit)
617
618 def str_long(self, unit):
619 if self.corresp_start_event is None:
620 return super(EndSpanEvent, self).str_long(unit)
621 else:
622 return self.corresp_start_event.str_long(unit)
623
507 def fill_span_event_from_end(self): 624 def fill_span_event_from_end(self):
508 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(), 625 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
509 TimeSlotArray.CPU_LIST : self.get_cpu() } 626 TimeSlotArray.CPU_LIST : self.get_cpu() }
510 self.get_schedule().get_time_slot_array().fill_span_event_from_end(self, item_nos) 627 self.get_schedule().get_time_slot_array().fill_span_event_from_end(self, item_nos)
511 628
512class SuspendEvent(Event): 629class SuspendEvent(Event):
513 def __init__(self, time, cpu): 630 def __init__(self, time, cpu):
514 super(SuspendEvent, self).__init__(time, cpu) 631 super(SuspendEvent, self).__init__(time, cpu)
@@ -592,25 +709,6 @@ class SwitchToEvent(StartSpanEvent):
592 else: 709 else:
593 return 'Scheduled' 710 return 'Scheduled'
594 711
595 def __str__(self):
596 if self.corresp_end_event is None:
597 return super(SwitchToEvent, self).__str__()
598 return self.get_name() + self._common_str() + ', START=' \
599 + util.format_float(self.get_time(), Event.NUM_DEC_PLACES) \
600 + ', END=' + util.format_float(self.corresp_end_event.get_time(), Event.NUM_DEC_PLACES)
601
602 def str_long(self):
603 if self.corresp_end_event is None:
604 return super(SwitchToEvent, self).str_long()
605 else :
606 return 'Event Type: ' + self.get_name() + \
607 '\nTask Name: ' + str(self.get_job().get_task().get_name()) + \
608 '\n(Task no., Job no.): ' + str((self.get_job().get_task().get_task_no(), \
609 self.get_job().get_job_no())) + \
610 '\nCPU: ' + str(self.get_cpu()) + \
611 '\nStart: ' + str(self.get_time()) + \
612 '\nEnd: ' + str(self.corresp_end_event.get_time())
613
614 def scan(self, cur_cpu, switches): 712 def scan(self, cur_cpu, switches):
615 old_cur_cpu = cur_cpu[0] 713 old_cur_cpu = cur_cpu[0]
616 cur_cpu[0] = self.get_cpu() 714 cur_cpu[0] = self.get_cpu()
@@ -656,17 +754,6 @@ class SwitchAwayEvent(EndSpanEvent):
656 else: 754 else:
657 return 'Scheduled' 755 return 'Scheduled'
658 756
659 def __str__(self):
660 if self.corresp_start_event is None:
661 return super(SwitchAwayEvent, self).__str__()
662 return str(self.corresp_start_event)
663
664 def str_long(self):
665 if self.corresp_start_event is None:
666 return super(SwitchAwayEvent, self).str_long()
667
668 return self.corresp_start_event.str_long()
669
670 def scan(self, cur_cpu, switches): 757 def scan(self, cur_cpu, switches):
671 old_cur_cpu = cur_cpu[0] 758 old_cur_cpu = cur_cpu[0]
672 759
@@ -772,30 +859,11 @@ class InversionStartEvent(StartSpanEvent):
772 else: 859 else:
773 return 'Priority Inversion' 860 return 'Priority Inversion'
774 861
775 def __str__(self):
776 if self.corresp_end_event is None:
777 return super(InversionStartEvent, self).__str__()
778 return self.get_name() + self._common_str() + ', START=' \
779 + util.format_float(self.get_time(), Event.NUM_DEC_PLACES) \
780 + ', END=' + util.format_float(self.corresp_end_event.get_time(), Event.NUM_DEC_PLACES)
781
782 def str_long(self):
783 if self.corresp_end_event is None:
784 return super(InversionStartEvent, self).str_long()
785 else :
786 return 'Event Type: ' + self.get_name() + \
787 '\nTask Name: ' + str(self.get_job().get_task().get_name()) + \
788 '\n(Task no., Job no.): ' + str((self.get_job().get_task().get_task_no(), \
789 self.get_job().get_job_no())) + \
790 '\nCPU: ' + str(self.get_cpu()) + \
791 '\nStart: ' + str(self.get_time()) + \
792 '\nEnd: ' + str(self.corresp_end_event.get_time())
793
794 def fill_span_event_from_start(self): 862 def fill_span_event_from_start(self):
795 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(), 863 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
796 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO } 864 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO }
797 self.get_schedule().get_time_slot_array().fill_span_event_from_start(self, item_nos) 865 self.get_schedule().get_time_slot_array().fill_span_event_from_start(self, item_nos)
798 866
799 def scan(self, cur_cpu, switches): 867 def scan(self, cur_cpu, switches):
800 switches[InversionStartEvent] = self 868 switches[InversionStartEvent] = self
801 self.corresp_end_event = None 869 self.corresp_end_event = None
@@ -840,23 +908,11 @@ class InversionEndEvent(EndSpanEvent):
840 else: 908 else:
841 return 'Priority Inversion' 909 return 'Priority Inversion'
842 910
843 def __str__(self):
844 if self.corresp_start_event is None:
845 return super(InversionEndEvent, self).__str__()
846
847 return str(self.corresp_start_event)
848
849 def str_long(self):
850 if self.corresp_start_event is None:
851 return super(InversionEndEvent, self).str_long()
852
853 return self.corresp_start_event.str_long()
854
855 def fill_span_event_from_end(self): 911 def fill_span_event_from_end(self):
856 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(), 912 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
857 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO } 913 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO }
858 self.get_schedule().get_time_slot_array().fill_span_event_from_end(self, item_nos) 914 self.get_schedule().get_time_slot_array().fill_span_event_from_end(self, item_nos)
859 915
860 def scan(self, cur_cpu, switches): 916 def scan(self, cur_cpu, switches):
861 self.corresp_start_event = switches[InversionStartEvent] 917 self.corresp_start_event = switches[InversionStartEvent]
862 918
@@ -932,25 +988,25 @@ class TaskDummy(DummyEvent):
932 super(TaskDummy, self).__init__(time, None) 988 super(TaskDummy, self).__init__(time, None)
933 self.task = task 989 self.task = task
934 self.layer = Canvas.BOTTOM_LAYER 990 self.layer = Canvas.BOTTOM_LAYER
935 991
936 def get_name(self): 992 def get_name(self):
937 return 'Task' 993 return 'Task'
938 994
939 def __str__(self): 995 def str_short(self, unit):
940 return 'Task ' + self.task.get_name() 996 return 'Task ' + self.task.get_name()
941 997
942 def str_long(self): 998 def str_long(self, unit):
943 return 'Task ' + self.task.get_name() 999 return 'Task ' + self.task.get_name()
944 1000
945 def get_task(self): 1001 def get_task(self):
946 return self.task 1002 return self.task
947 1003
948 def render(self, graph, layer, prev_events, selectable=False): 1004 def render(self, graph, layer, prev_events, selectable=False):
949 if selectable: 1005 if selectable:
950 graph.add_sel_task_item(self.task.get_task_no(), self) 1006 graph.add_sel_task_item(self.task.get_task_no(), self)
951 else: 1007 else:
952 graph.draw_task_item(self.task.get_task_no(), self.is_selected()) 1008 graph.draw_task_item(self.task.get_task_no(), self.is_selected())
953 1009
954class CPUDummy(DummyEvent): 1010class CPUDummy(DummyEvent):
955 """A dummy event that represents a certain CPU. Used for rendering the CPU's ``name'' 1011 """A dummy event that represents a certain CPU. Used for rendering the CPU's ``name''
956 (i.e. ``CPU #n'')""" 1012 (i.e. ``CPU #n'')"""
@@ -958,29 +1014,29 @@ class CPUDummy(DummyEvent):
958 super(CPUDummy, self).__init__(time, cpu) 1014 super(CPUDummy, self).__init__(time, cpu)
959 self.sched = sched 1015 self.sched = sched
960 self.layer = Canvas.BOTTOM_LAYER 1016 self.layer = Canvas.BOTTOM_LAYER
961 1017
962 def get_name(self): 1018 def get_name(self):
963 return 'CPU' 1019 return 'CPU'
964 1020
965 def __str__(self): 1021 def str_short(self, unit):
966 return 'CPU #' + str(self.get_cpu()) 1022 return 'CPU #' + str(self.get_cpu())
967 1023
968 def str_long(self): 1024 def str_long(self, unit):
969 return 'CPU #' + str(self.get_cpu()) 1025 return 'CPU #' + str(self.get_cpu())
970 1026
971 def get_task(self): 1027 def get_task(self):
972 return None 1028 return None
973 1029
974 def get_schedule(self): 1030 def get_schedule(self):
975 # we don't have a job, so get the schedule from the schedule passed in 1031 # we don't have a job, so get the schedule from the schedule passed in
976 return self.sched 1032 return self.sched
977 1033
978 def render(self, graph, layer, prev_events, selectable=False): 1034 def render(self, graph, layer, prev_events, selectable=False):
979 if selectable: 1035 if selectable:
980 graph.add_sel_cpu_item(self.cpu, self) 1036 graph.add_sel_cpu_item(self.cpu, self)
981 else: 1037 else:
982 graph.draw_cpu_item(self.cpu, self.is_selected()) 1038 graph.draw_cpu_item(self.cpu, self.is_selected())
983 1039
984EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, 1040EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None,
985 SwitchAwayEvent : None, SwitchToEvent : None, ReleaseEvent : None, 1041 SwitchAwayEvent : None, SwitchToEvent : None, ReleaseEvent : None,
986 DeadlineEvent : None, IsRunningDummy : None, 1042 DeadlineEvent : None, IsRunningDummy : None,
diff --git a/unit_trace/viz/util.py b/unit_trace/viz/util.py
index 3111f39..430063b 100644
--- a/unit_trace/viz/util.py
+++ b/unit_trace/viz/util.py
@@ -1,9 +1,78 @@
1#!/usr/bin/python
2
3"""Miscellanious utility functions that don't fit anywhere.""" 1"""Miscellanious utility functions that don't fit anywhere."""
4 2
5def format_float(num, numplaces): 3def format_float(num, numplaces):
4 if num is None:
5 return '(None)'
6
6 if abs(round(num, numplaces) - round(num, 0)) == 0.0: 7 if abs(round(num, numplaces) - round(num, 0)) == 0.0:
7 return '%.0f' % float(num) 8 return '%.0f' % float(num)
8 else: 9 else:
9 return ('%.' + numplaces + 'f') % round(float(num), numplaces) 10 return ('%.' + str(numplaces) + 'f') % round(float(num), numplaces)
11
12class TimeUnit(object):
13 """Class that represents a certain time unit."""
14 def nsec_to_native(self, time):
15 raise NotImplementedError
16
17 def native_to_nsec(self, time):
18 raise NotImplementedError
19
20class NanoSec(TimeUnit):
21 def __str__(self):
22 return 'ns'
23
24 def nsec_to_native(self, time):
25 return time
26
27 def native_to_nsec(self, time):
28 return time
29
30class MicroSec(TimeUnit):
31 def __str__(self):
32 return 'us'
33
34 def nsec_to_native(self, time):
35 if time is None: return time
36 return time / 1000.0
37
38 def native_to_nsec(self, time):
39 if time is None: return time
40 return time * 1000.0
41
42class MilliSec(TimeUnit):
43 def __str__(self):
44 return 'ms'
45
46 def nsec_to_native(self, time):
47 if time is None: return time
48 return time / 1000000.0
49
50 def native_to_nsec(self, time):
51 if time is None: return time
52 return time * 1000000.0
53
54class Sec(TimeUnit):
55 def __str__(self):
56 return 's'
57
58 def nsec_to_native(self, time):
59 if time is None: return time
60 return time / 1000000000.0
61
62 def native_to_nsec(self, time):
63 if time is None: return time
64 return time * 1000000000.0
65
66def parse_unit(expr):
67 expr = expr.strip().lower()
68
69 if expr == 'ns' or expr == 'nsec' or expr == 'nsecs' or expr == 'nanosec' or expr == 'nanosecs':
70 return NanoSec()
71 elif expr == 'us' or expr == 'usec' or expr == 'usecs' or expr == 'microsec' or expr == 'microsecs':
72 return MicroSec()
73 elif expr == 'ms' or expr == 'msec' or expr == 'msecs' or expr == 'millisec' or expr == 'millisecs':
74 return MilliSec()
75 elif expr == 's' or expr == 'sec' or expr == 'secs' or expr == 'second' or expr == 'seconds':
76 return Sec()
77 else:
78 raise ValueError
diff --git a/unit_trace/viz/viewer.py b/unit_trace/viz/viewer.py
index 4f7a511..bb1385c 100644
--- a/unit_trace/viz/viewer.py
+++ b/unit_trace/viz/viewer.py
@@ -30,7 +30,7 @@ class GraphContextMenu(gtk.Menu):
30 else: 30 else:
31 for layer in selected: 31 for layer in selected:
32 for event in selected[layer]: 32 for event in selected[layer]:
33 string = str(event) 33 string = event.str_short(info_win.get_unit())
34 if len(string) > GraphContextMenu.MAX_STR_LEN - 3: 34 if len(string) > GraphContextMenu.MAX_STR_LEN - 3:
35 string = string[:GraphContextMenu.MAX_STR_LEN - 3] + '...' 35 string = string[:GraphContextMenu.MAX_STR_LEN - 3] + '...'
36 item = gtk.MenuItem(string) 36 item = gtk.MenuItem(string)
@@ -68,19 +68,23 @@ class GraphArea(gtk.DrawingArea):
68 self.set_set_scroll_adjustments_signal('set-scroll-adjustments') 68 self.set_set_scroll_adjustments_signal('set-scroll-adjustments')
69 69
70 self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | 70 self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK |
71 gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.EXPOSURE_MASK) 71 gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.EXPOSURE_MASK | gtk.gdk.LEAVE_NOTIFY_MASK)
72 72
73 self.band_rect = None 73 self.band_rect = None
74 self.ctrl_clicked = False 74 self.ctrl_clicked = False
75 self.cursor_pos = None
75 self.last_selected = {} 76 self.last_selected = {}
76 self.dirtied_regions = [] 77 self.dirtied_regions = []
77 78
79 self.get_graph().update_view(self.cur_x, self.cur_y, self.width, self.height, self.scale)
80
78 self.connect('expose-event', self.expose) 81 self.connect('expose-event', self.expose)
79 self.connect('size-allocate', self.size_allocate) 82 self.connect('size-allocate', self.size_allocate)
80 self.connect('set-scroll-adjustments', self.set_scroll_adjustments) 83 self.connect('set-scroll-adjustments', self.set_scroll_adjustments)
81 self.connect('button-press-event', self.button_press) 84 self.connect('button-press-event', self.button_press)
82 self.connect('button-release-event', self.button_release) 85 self.connect('button-release-event', self.button_release)
83 self.connect('motion-notify-event', self.motion_notify) 86 self.connect('motion-notify-event', self.motion_notify)
87 self.connect('leave-notify-event', self.leave_notify)
84 88
85 def output_to_file(self, filename, ftype): 89 def output_to_file(self, filename, ftype):
86 """Outputs the entire graph to a file.""" 90 """Outputs the entire graph to a file."""
@@ -92,17 +96,17 @@ class GraphArea(gtk.DrawingArea):
92 surface = SVGSurface() 96 surface = SVGSurface()
93 else: 97 else:
94 raise ValueError 98 raise ValueError
95 99
96 surface.renew(graph.get_width(), graph.get_height()) 100 surface.renew(graph.get_width(), graph.get_height())
97 graph.set_tmp_surface(surface) 101 graph.set_tmp_surface(surface)
98 graph.render_all(self.renderer.get_schedule()) 102 graph.render_all(self.renderer.get_schedule())
99 surface.write_out(filename) 103 surface.write_out(filename)
100 graph.revert_surface() 104 graph.revert_surface()
101 105
102 def expose(self, widget, expose_event, data=None): 106 def expose(self, widget, expose_event, data=None):
103 ctx = widget.window.cairo_create() 107 ctx = widget.window.cairo_create()
104 graph = self.renderer.get_graph() 108 graph = self.get_graph()
105 graph.update_view(self.cur_x, self.cur_y, self.width, self.height, self.scale, ctx) 109 graph.load_ctx(ctx)
106 110
107 # If X caused the expose event, we need to update the entire area, not just the 111 # If X caused the expose event, we need to update the entire area, not just the
108 # changes we might have made. An expose event caused by X needs to take priority 112 # changes we might have made. An expose event caused by X needs to take priority
@@ -113,11 +117,13 @@ class GraphArea(gtk.DrawingArea):
113 self.dirtied_regions = [(expose_event.area.x, expose_event.area.y, 117 self.dirtied_regions = [(expose_event.area.x, expose_event.area.y,
114 expose_event.area.width, expose_event.area.height)] 118 expose_event.area.width, expose_event.area.height)]
115 119
116 graph.render_surface(self.renderer.get_schedule(), self.dirtied_regions) 120 virt_dirtied_regions = graph.real_to_virt_list(self.dirtied_regions)
121 graph.render_surface(self.renderer.get_schedule(), virt_dirtied_regions)
117 122
118 # render dragging band rectangle, if there is one 123 # render dragging band rectangle, if there is one
119 if self.band_rect is not None: 124 if self.band_rect is not None:
120 x, y, width, height = self.band_rect 125 x, y, width, height = graph.virt_to_real(self.band_rect[0], self.band_rect[1],
126 self.band_rect[2], self.band_rect[3])
121 thickness = GraphFormat.BAND_THICKNESS 127 thickness = GraphFormat.BAND_THICKNESS
122 color = GraphFormat.BAND_COLOR 128 color = GraphFormat.BAND_COLOR
123 129
@@ -136,21 +142,23 @@ class GraphArea(gtk.DrawingArea):
136 142
137 def get_schedule(self): 143 def get_schedule(self):
138 return self.renderer.get_schedule() 144 return self.renderer.get_schedule()
139 145
140 def zoom_in(self): 146 def zoom_in(self):
141 scale = self.scale + GraphArea.ZOOM_INCR 147 scale = self.scale + GraphArea.ZOOM_INCR
142 if scale > GraphArea.MAX_ZOOM_IN: 148 if scale > GraphArea.MAX_ZOOM_IN:
143 scale = GraphArea.MAX_ZOOM_IN 149 scale = GraphArea.MAX_ZOOM_IN
144 self.set_scale(scale) 150 self.set_scale(scale)
145 self.config_scrollbars(self.cur_x, self.cur_y, self.scale) 151 self.config_scrollbars(self.cur_x, self.cur_y, self.scale)
146 152 self._pos_update()
153
147 def zoom_out(self): 154 def zoom_out(self):
148 scale = self.scale - GraphArea.ZOOM_INCR 155 scale = self.scale - GraphArea.ZOOM_INCR
149 if scale < GraphArea.MIN_ZOOM_OUT: 156 if scale < GraphArea.MIN_ZOOM_OUT:
150 scale = GraphArea.MIN_ZOOM_OUT 157 scale = GraphArea.MIN_ZOOM_OUT
151 self.set_scale(scale) 158 self.set_scale(scale)
152 self.config_scrollbars(self.cur_x, self.cur_y, self.scale) 159 self.config_scrollbars(self.cur_x, self.cur_y, self.scale)
153 160 self._pos_update()
161
154 def set_scale(self, scale): 162 def set_scale(self, scale):
155 if scale == self.scale: 163 if scale == self.scale:
156 return 164 return
@@ -165,7 +173,7 @@ class GraphArea(gtk.DrawingArea):
165 value = max(value, self.horizontal.get_lower()) 173 value = max(value, self.horizontal.get_lower())
166 value = min(value, self.horizontal.get_upper() - self.horizontal.get_page_size()) 174 value = min(value, self.horizontal.get_upper() - self.horizontal.get_page_size())
167 self.horizontal.set_value(value) 175 self.horizontal.set_value(value)
168 176
169 def set_vvalue(self, value): 177 def set_vvalue(self, value):
170 if self.vertical is None: 178 if self.vertical is None:
171 return 179 return
@@ -173,7 +181,7 @@ class GraphArea(gtk.DrawingArea):
173 value = max(value, self.vertical.get_lower()) 181 value = max(value, self.vertical.get_lower())
174 value = min(value, self.vertical.get_upper() - self.vertical.get_page_size()) 182 value = min(value, self.vertical.get_upper() - self.vertical.get_page_size())
175 self.vertical.set_value(value) 183 self.vertical.set_value(value)
176 184
177 def set_scroll_adjustments(self, widget, horizontal, vertical, data=None): 185 def set_scroll_adjustments(self, widget, horizontal, vertical, data=None):
178 graph = self.renderer.get_graph() 186 graph = self.renderer.get_graph()
179 width = graph.get_width() 187 width = graph.get_width()
@@ -192,20 +200,27 @@ class GraphArea(gtk.DrawingArea):
192 self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) 200 self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width())
193 self.cur_x = max(adjustment.value, 0.0) 201 self.cur_x = max(adjustment.value, 0.0)
194 202
195 self.renderer.get_graph().render_surface(self.renderer.get_schedule(), [(0, 0, self.width, self.height)], True) 203 self.get_graph().render_surface(self.renderer.get_schedule(),
204 [self.get_graph().real_to_virt(0, 0, self.width, self.height)], True)
196 self._dirty(0, 0, self.width, self.height) 205 self._dirty(0, 0, self.width, self.height)
197 206
207 self._pos_update()
208
198 def vertical_value_changed(self, adjustment): 209 def vertical_value_changed(self, adjustment):
199 self.cur_y = min(adjustment.value, self.renderer.get_graph().get_height()) 210 self.cur_y = min(adjustment.value, self.renderer.get_graph().get_height())
200 self.cur_y = max(adjustment.value, 0.0) 211 self.cur_y = max(adjustment.value, 0.0)
201 212
202 self.renderer.get_graph().render_surface(self.renderer.get_schedule(), [(0, 0, self.width, self.height)], True) 213 self.get_graph().render_surface(self.renderer.get_schedule(),
214 [self.get_graph().real_to_virt(0, 0, self.width, self.height)], True)
203 self._dirty(0, 0, self.width, self.height) 215 self._dirty(0, 0, self.width, self.height)
204 216
217 self._pos_update()
218
205 def size_allocate(self, widget, allocation): 219 def size_allocate(self, widget, allocation):
206 self.width = allocation.width 220 self.width = allocation.width
207 self.height = allocation.height 221 self.height = allocation.height
208 self.config_scrollbars(self.cur_x, self.cur_y, self.scale) 222 self.config_scrollbars(self.cur_x, self.cur_y, self.scale)
223 self._pos_update()
209 224
210 def config_scrollbars(self, hvalue, vvalue, scale): 225 def config_scrollbars(self, hvalue, vvalue, scale):
211 graph = self.renderer.get_graph() 226 graph = self.renderer.get_graph()
@@ -224,11 +239,11 @@ class GraphArea(gtk.DrawingArea):
224 graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR / scale, 239 graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR / scale,
225 self.height / scale) 240 self.height / scale)
226 self.set_vvalue(vvalue) 241 self.set_vvalue(vvalue)
227 242
228 def refresh_all(self): 243 def refresh_all(self):
229 """Refreshes the entire visible screen.""" 244 """Refreshes the entire visible screen."""
230 self._dirty(0, 0, self.width, self.height) 245 self._dirty(0, 0, self.width, self.height)
231 246
232 def refresh_events(self, sender, new, old, replace): 247 def refresh_events(self, sender, new, old, replace):
233 """Even if the selected areas change on one graph, they change 248 """Even if the selected areas change on one graph, they change
234 everywhere, and different events might be in different regions on 249 everywhere, and different events might be in different regions on
@@ -240,7 +255,7 @@ class GraphArea(gtk.DrawingArea):
240 refreshes the drawing of the graph that requested the change.""" 255 refreshes the drawing of the graph that requested the change."""
241 if self is not sender: 256 if self is not sender:
242 self.renderer.get_graph().render_events(new, True) 257 self.renderer.get_graph().render_events(new, True)
243 258
244 self._tag_events(new) 259 self._tag_events(new)
245 260
246 if self is sender: 261 if self is sender:
@@ -252,7 +267,7 @@ class GraphArea(gtk.DrawingArea):
252 else: 267 else:
253 self.renderer.get_schedule().remove_selected(old) 268 self.renderer.get_schedule().remove_selected(old)
254 self.renderer.get_schedule().add_selected(new) 269 self.renderer.get_schedule().add_selected(new)
255 270
256 self.emit('update-sel-changes', self.renderer.get_schedule().get_selected()) 271 self.emit('update-sel-changes', self.renderer.get_schedule().get_selected())
257 272
258 def _find_max_layer(self, regions): 273 def _find_max_layer(self, regions):
@@ -267,11 +282,9 @@ class GraphArea(gtk.DrawingArea):
267 for layer in events: 282 for layer in events:
268 for event in events[layer]: 283 for event in events[layer]:
269 if not events[layer][event][self].is_dummy(): 284 if not events[layer][event][self].is_dummy():
270 x, y, width, height = events[layer][event][self].get_dimensions() 285 dim = events[layer][event][self].get_dimensions()
271 self._dirty_inflate((x - self.cur_x) * self.scale, 286 x, y, width, height = self.get_graph().virt_to_real(dim[0], dim[1], dim[2], dim[3])
272 (y - self.cur_y) * self.scale, 287 self._dirty_inflate(x, y, width, height,
273 width * self.scale,
274 height * self.scale,
275 GraphFormat.BORDER_THICKNESS * self.scale) 288 GraphFormat.BORDER_THICKNESS * self.scale)
276 289
277 def _tag_events(self, selected): 290 def _tag_events(self, selected):
@@ -303,15 +316,27 @@ class GraphArea(gtk.DrawingArea):
303 if event not in coll[event.get_layer()]: 316 if event not in coll[event.get_layer()]:
304 coll[event.get_layer()][event] = {} 317 coll[event.get_layer()][event] = {}
305 318
319 def leave_notify(self, widget, leave_event):
320 self.cursor_pos = None
321 self.emit('update-event-description', None)
322
306 def motion_notify(self, widget, motion_event, data=None): 323 def motion_notify(self, widget, motion_event, data=None):
307 msg = None 324 self.cursor_pos = (motion_event.x, motion_event.y)
325 self._pos_update()
326
327 def _pos_update(self):
328 self.get_graph().update_view(self.cur_x, self.cur_y, self.width, self.height, self.scale)
329
330 if self.cursor_pos is None:
331 return
308 332
309 graph = self.renderer.get_graph() 333 graph = self.renderer.get_graph()
310 334
311 graph.render_surface(self.renderer.get_schedule(), [(motion_event.x, motion_event.y, 335 graph.render_surface(self.renderer.get_schedule(),
312 0, 0)], True) 336 [graph.real_to_virt(self.cursor_pos[0], self.cursor_pos[1], 0, 0)], True)
313 337
314 just_selected = graph.get_intersecting_regions(motion_event.x, motion_event.y, 0, 0) 338 cur_x_v, cur_y_v, w, h = graph.real_to_virt(self.cursor_pos[0], self.cursor_pos[1], 0, 0)
339 just_selected = graph.get_intersecting_regions(cur_x_v, cur_y_v, 0, 0)
315 was_selected = self.renderer.get_schedule().get_selected() 340 was_selected = self.renderer.get_schedule().get_selected()
316 if not just_selected: 341 if not just_selected:
317 msg = '' 342 msg = ''
@@ -323,10 +348,11 @@ class GraphArea(gtk.DrawingArea):
323 the_event = event 348 the_event = event
324 break 349 break
325 350
326 msg = str(the_event) 351 msg = the_event.str_short(graph.get_attrs().unit)
327 352
328 self.emit('update-event-description', the_event, msg) 353 self.emit('update-event-description', the_event)
329 354
355 virt_x, virt_y, w, h = graph.real_to_virt(self.cursor_pos[0], self.cursor_pos[1], 0, 0)
330 if self.band_rect is not None: 356 if self.band_rect is not None:
331 remove_selected = {} 357 remove_selected = {}
332 add_selected = {} 358 add_selected = {}
@@ -334,14 +360,12 @@ class GraphArea(gtk.DrawingArea):
334 # dragging a rectangle 360 # dragging a rectangle
335 x = self.band_rect[0] 361 x = self.band_rect[0]
336 y = self.band_rect[1] 362 y = self.band_rect[1]
337 width = motion_event.x - self.band_rect[0] 363 width = virt_x - self.band_rect[0]
338 height = motion_event.y - self.band_rect[1] 364 height = virt_y - self.band_rect[1]
339 old_x, old_y, old_width, old_height = self.band_rect 365 old_x, old_y, old_width, old_height = self.band_rect
340 366
341 x_p, y_p, width_p, height_p = self._positivify(x, y, width, height) 367 x_p, y_p, width_p, height_p = self._positivify_int(x, y, width, height)
342 old_x_p, old_y_p, old_width_p, old_height_p = self._positivify(old_x, old_y, old_width, old_height) 368 old_x_p, old_y_p, old_width_p, old_height_p = self._positivify_int(old_x, old_y, old_width, old_height)
343 x_p, y_p, width_p, height_p = int(x_p), int(y_p), int(width_p), int(height_p)
344 old_x_p, old_y_p, old_width_p, old_height_p = int(old_x_p), int(old_y_p), int(old_width_p), int(old_height_p)
345 369
346 new_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(x_p, y_p, width_p, height_p)) 370 new_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(x_p, y_p, width_p, height_p))
347 old_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(old_x_p, old_y_p, old_width_p, old_height_p)) 371 old_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(old_x_p, old_y_p, old_width_p, old_height_p))
@@ -354,7 +378,7 @@ class GraphArea(gtk.DrawingArea):
354 dirty_list = [] 378 dirty_list = []
355 for rect in remove_reg.get_rectangles(): 379 for rect in remove_reg.get_rectangles():
356 dirty_list.append((rect.x, rect.y, rect.width, rect.height)) 380 dirty_list.append((rect.x, rect.y, rect.width, rect.height))
357 graph.render_surface(self.renderer.get_schedule(), dirty_list, True) 381 graph.render_surface(self.renderer.get_schedule(), graph.real_to_virt_list(dirty_list), True)
358 for rect in dirty_list: 382 for rect in dirty_list:
359 rx, ry, rwidth, rheight = rect 383 rx, ry, rwidth, rheight = rect
360 i_regs = graph.get_intersecting_regions(rx, ry, rwidth, rheight) 384 i_regs = graph.get_intersecting_regions(rx, ry, rwidth, rheight)
@@ -370,6 +394,7 @@ class GraphArea(gtk.DrawingArea):
370 for rect in add_reg.get_rectangles(): 394 for rect in add_reg.get_rectangles():
371 dirty_list.append((rect.x, rect.y, rect.width, rect.height)) 395 dirty_list.append((rect.x, rect.y, rect.width, rect.height))
372 graph.render_surface(self.renderer.get_schedule(), dirty_list, True) 396 graph.render_surface(self.renderer.get_schedule(), dirty_list, True)
397
373 for rect in dirty_list: 398 for rect in dirty_list:
374 rx, ry, rwidth, rheight = rect 399 rx, ry, rwidth, rheight = rect
375 i_regs = graph.get_intersecting_regions(rx, ry, rwidth, rheight) 400 i_regs = graph.get_intersecting_regions(rx, ry, rwidth, rheight)
@@ -380,8 +405,10 @@ class GraphArea(gtk.DrawingArea):
380 self.band_rect = x, y, width, height 405 self.band_rect = x, y, width, height
381 self.emit('request-refresh-events', self, add_selected, remove_selected, False) 406 self.emit('request-refresh-events', self, add_selected, remove_selected, False)
382 407
383 self._dirty_rect_border(old_x, old_y, old_width, old_height, GraphFormat.BAND_THICKNESS) 408 x_r, y_r, width_r, height_r = graph.virt_to_real(x, y, width, height)
384 self._dirty_rect_border(x, y, width, height, GraphFormat.BAND_THICKNESS) 409 old_x_r, old_y_r, old_width_r, old_height_r = graph.virt_to_real(old_x, old_y, old_width, old_height)
410 self._dirty_rect_border(old_x_r, old_y_r, old_width_r, old_height_r, GraphFormat.BAND_THICKNESS * self.scale)
411 self._dirty_rect_border(x_r, y_r, width_r, height_r, GraphFormat.BAND_THICKNESS * self.scale)
385 412
386 def button_press(self, widget, button_event, data=None): 413 def button_press(self, widget, button_event, data=None):
387 graph = self.renderer.get_graph() 414 graph = self.renderer.get_graph()
@@ -391,10 +418,11 @@ class GraphArea(gtk.DrawingArea):
391 if button_event.button == 1: 418 if button_event.button == 1:
392 self.left_button_start_coor = (button_event.x, button_event.y) 419 self.left_button_start_coor = (button_event.x, button_event.y)
393 graph.render_surface(self.renderer.get_schedule(), \ 420 graph.render_surface(self.renderer.get_schedule(), \
394 [(button_event.x, button_event.y, 0, 0)], True) 421 [graph.real_to_virt(button_event.x, button_event.y, 0, 0)], True)
395 422
396 just_selected = graph.get_intersecting_regions(button_event.x, button_event.y, 0, 0) 423 b_x_r, b_y_r, w, h = graph.real_to_virt(button_event.x, button_event.y, 0, 0)
397 424 just_selected = graph.get_intersecting_regions(b_x_r, b_y_r, 0, 0)
425
398 max_layer = self._find_max_layer(just_selected) 426 max_layer = self._find_max_layer(just_selected)
399 427
400 was_selected = self.renderer.get_schedule().get_selected() 428 was_selected = self.renderer.get_schedule().get_selected()
@@ -418,7 +446,7 @@ class GraphArea(gtk.DrawingArea):
418 and event in was_selected[max_layer]): 446 and event in was_selected[max_layer]):
419 self._select_event(new_now_selected, event) 447 self._select_event(new_now_selected, event)
420 break # only pick one event when just clicking 448 break # only pick one event when just clicking
421 449
422 self.emit('request-refresh-events', self, new_now_selected, was_selected, True) 450 self.emit('request-refresh-events', self, new_now_selected, was_selected, True)
423 else: 451 else:
424 remove_selected = {} 452 remove_selected = {}
@@ -435,7 +463,7 @@ class GraphArea(gtk.DrawingArea):
435 self.emit('request-refresh-events', self, add_selected, remove_selected, False) 463 self.emit('request-refresh-events', self, add_selected, remove_selected, False)
436 464
437 if self.band_rect is None: 465 if self.band_rect is None:
438 self.band_rect = (button_event.x, button_event.y, 0, 0) 466 self.band_rect = (b_x_r, b_y_r, 0, 0)
439 467
440 elif button_event.button == 3: 468 elif button_event.button == 3:
441 self._release_band() 469 self._release_band()
@@ -456,7 +484,8 @@ class GraphArea(gtk.DrawingArea):
456 def _release_band(self): 484 def _release_band(self):
457 if self.band_rect is not None: 485 if self.band_rect is not None:
458 x, y, width, height = self.band_rect 486 x, y, width, height = self.band_rect
459 self._dirty_rect_border(x, y, width, height, GraphFormat.BAND_THICKNESS) 487 x_r, y_r, width_r, height_r = self.get_graph().virt_to_real(x, y, width, height)
488 self._dirty_rect_border(x_r, y_r, width_r, height_r, GraphFormat.BAND_THICKNESS * self.scale)
460 self.band_rect = None 489 self.band_rect = None
461 490
462 def _dirty(self, x, y, width, height): 491 def _dirty(self, x, y, width, height):
@@ -498,6 +527,10 @@ class GraphArea(gtk.DrawingArea):
498 527
499 return x, y, width, height 528 return x, y, width, height
500 529
530 def _positivify_int(self, x, y, width, height):
531 p = self._positivify(x, y, width, height)
532 return (int(p[0]), int(p[1]), int(p[2]), int(p[3]))
533
501class GraphWindow(gtk.ScrolledWindow): 534class GraphWindow(gtk.ScrolledWindow):
502 def __init__(self, renderer): 535 def __init__(self, renderer):
503 super(GraphWindow, self).__init__(None, None) 536 super(GraphWindow, self).__init__(None, None)
@@ -596,7 +629,7 @@ class MainWindow(gtk.Window):
596 629
597 def __init__(self): 630 def __init__(self):
598 super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) 631 super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL)
599 632
600 self.add_events(gtk.gdk.BUTTON_PRESS_MASK) 633 self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
601 634
602 self.connect('delete_event', self.delete_event) 635 self.connect('delete_event', self.delete_event)
@@ -608,11 +641,11 @@ class MainWindow(gtk.Window):
608 641
609 agr = gtk.AccelGroup() 642 agr = gtk.AccelGroup()
610 self.add_accel_group(agr) 643 self.add_accel_group(agr)
611 644
612 # list of file items that are clickable if and only if at 645 # list of file items that are clickable if and only if at
613 # least one graph is viewable 646 # least one graph is viewable
614 self.sensitive_menu_items = [] 647 self.sensitive_menu_items = []
615 648
616 save_item = gtk.ImageMenuItem('_Save Graph to File') 649 save_item = gtk.ImageMenuItem('_Save Graph to File')
617 img = gtk.Image() 650 img = gtk.Image()
618 img.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_MENU) 651 img.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_MENU)
@@ -623,7 +656,7 @@ class MainWindow(gtk.Window):
623 save_item.connect('activate', self.save_item_activate) 656 save_item.connect('activate', self.save_item_activate)
624 save_item.set_sensitive(False) 657 save_item.set_sensitive(False)
625 self.sensitive_menu_items.append(save_item) 658 self.sensitive_menu_items.append(save_item)
626 659
627 quit_item = gtk.ImageMenuItem(gtk.STOCK_QUIT, agr) 660 quit_item = gtk.ImageMenuItem(gtk.STOCK_QUIT, agr)
628 key, mod = gtk.accelerator_parse('<Ctrl>Q') 661 key, mod = gtk.accelerator_parse('<Ctrl>Q')
629 quit_item.add_accelerator('activate', agr, key, mod, 662 quit_item.add_accelerator('activate', agr, key, mod,
@@ -636,8 +669,8 @@ class MainWindow(gtk.Window):
636 file_item = gtk.MenuItem('_File', True) 669 file_item = gtk.MenuItem('_File', True)
637 file_item.set_submenu(file_menu) 670 file_item.set_submenu(file_menu)
638 671
639 move_item = gtk.ImageMenuItem('_Move to Time') 672 move_item = gtk.ImageMenuItem('_Jump to Time')
640 key, mod = gtk.accelerator_parse('<Ctrl>M') 673 key, mod = gtk.accelerator_parse('<Ctrl>J')
641 img = gtk.Image() 674 img = gtk.Image()
642 img.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU) 675 img.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
643 move_item.set_image(img) 676 move_item.set_image(img)
@@ -645,7 +678,7 @@ class MainWindow(gtk.Window):
645 gtk.ACCEL_VISIBLE) 678 gtk.ACCEL_VISIBLE)
646 move_item.set_sensitive(False) 679 move_item.set_sensitive(False)
647 self.sensitive_menu_items.append(move_item) 680 self.sensitive_menu_items.append(move_item)
648 681
649 move_item.connect('activate', self.move_to_time_activate) 682 move_item.connect('activate', self.move_to_time_activate)
650 683
651 self.colors_item = gtk.ImageMenuItem('Co_lors') 684 self.colors_item = gtk.ImageMenuItem('Co_lors')
@@ -656,14 +689,14 @@ class MainWindow(gtk.Window):
656 self.colors_item.add_accelerator('activate', agr, key, mod, 689 self.colors_item.add_accelerator('activate', agr, key, mod,
657 gtk.ACCEL_VISIBLE) 690 gtk.ACCEL_VISIBLE)
658 self.colors_item.set_sensitive(False) 691 self.colors_item.set_sensitive(False)
659 692
660 self.colors_item.connect('activate', self.colors_activate) 693 self.colors_item.connect('activate', self.colors_activate)
661 694
662 edit_menu.append(self.colors_item) 695 edit_menu.append(self.colors_item)
663 696
664 edit_item = gtk.MenuItem('_Edit', True) 697 edit_item = gtk.MenuItem('_Edit', True)
665 edit_item.set_submenu(edit_menu) 698 edit_item.set_submenu(edit_menu)
666 699
667 zoom_in_item = gtk.ImageMenuItem(gtk.STOCK_ZOOM_IN, agr) 700 zoom_in_item = gtk.ImageMenuItem(gtk.STOCK_ZOOM_IN, agr)
668 key, mod = gtk.accelerator_parse('<Ctrl>plus') 701 key, mod = gtk.accelerator_parse('<Ctrl>plus')
669 zoom_in_item.add_accelerator('activate', agr, key, mod, 702 zoom_in_item.add_accelerator('activate', agr, key, mod,
@@ -701,20 +734,23 @@ class MainWindow(gtk.Window):
701 self.notebook.last_page = -1 734 self.notebook.last_page = -1
702 self.notebook.connect('switch-page', self.switch_page) 735 self.notebook.connect('switch-page', self.switch_page)
703 736
704 self.desc_label = gtk.Label('') 737 self.event_label = gtk.Label('')
705 self.desc_label.set_alignment(0.0, 0.0) 738 self.job_run_label = gtk.Label('')
739 self.job_rd_label = gtk.Label('')
706 740
707 self.vbox.pack_start(menu_bar, False, False, 0) 741 self.vbox.pack_start(menu_bar, False, False, 0)
708 self.vbox.pack_start(self.notebook, True, True, 0) 742 self.vbox.pack_start(self.notebook, True, True, 0)
709 self.vbox.pack_start(self.desc_label, False, False, 0) 743 self.vbox.pack_start(self.event_label, False, False, 0)
710 744 self.vbox.pack_start(self.job_run_label, False, False, 0)
745 self.vbox.pack_start(self.job_rd_label, False, False, 0)
711 self.add(self.vbox) 746 self.add(self.vbox)
712 747
713 self.info_win = InfoWindow() 748 self.info_win = InfoWindow()
749 self.text_input_dialog = TextInputDialog('Move to Time', 'how is babby formed?', self)
714 self.color_dialog = gtk.ColorSelectionDialog('Choose Item Color') 750 self.color_dialog = gtk.ColorSelectionDialog('Choose Item Color')
715 751
716 self.cur_item_no = None 752 self.cur_item_no = None
717 753
718 self.set_size_request(MainWindow.WINDOW_WIDTH_REQ, MainWindow.WINDOW_HEIGHT_REQ) 754 self.set_size_request(MainWindow.WINDOW_WIDTH_REQ, MainWindow.WINDOW_HEIGHT_REQ)
719 755
720 self.set_title('Unit-Trace Visualizer') 756 self.set_title('Unit-Trace Visualizer')
@@ -736,7 +772,7 @@ class MainWindow(gtk.Window):
736 self.connect_widgets(gwindow) 772 self.connect_widgets(gwindow)
737 gwindow.show() 773 gwindow.show()
738 self.notebook.append_page(gwindow, gtk.Label(title)) 774 self.notebook.append_page(gwindow, gtk.Label(title))
739 775
740 if self.notebook.get_n_pages() > 0: 776 if self.notebook.get_n_pages() > 0:
741 self.notebook.get_nth_page(0).grab_focus() 777 self.notebook.get_nth_page(0).grab_focus()
742 778
@@ -750,14 +786,14 @@ class MainWindow(gtk.Window):
750 def update_sel_changes(self, widget, selected): 786 def update_sel_changes(self, widget, selected):
751 """Modify the GUI appropriately to reflect a change in selection.""" 787 """Modify the GUI appropriately to reflect a change in selection."""
752 self.selected = selected 788 self.selected = selected
753 789
754 self.cur_item_no = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area() \ 790 self.cur_item_no = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area() \
755 .get_graph().all_one_item_selected(selected) 791 .get_graph().all_one_item_selected(selected)
756 if self.cur_item_no is not None: 792 if self.cur_item_no is not None:
757 self.colors_item.set_sensitive(True) 793 self.colors_item.set_sensitive(True)
758 else: 794 else:
759 self.colors_item.set_sensitive(False) 795 self.colors_item.set_sensitive(False)
760 796
761 def switch_page(self, widget, page, page_num): 797 def switch_page(self, widget, page, page_num):
762 if self.notebook.get_nth_page(self.notebook.last_page) is not None: 798 if self.notebook.get_nth_page(self.notebook.last_page) is not None:
763 old_value = self.notebook.get_nth_page(self.notebook.last_page).get_hadjustment().get_value() 799 old_value = self.notebook.get_nth_page(self.notebook.last_page).get_hadjustment().get_value()
@@ -765,11 +801,28 @@ class MainWindow(gtk.Window):
765 new_ofs = self.notebook.get_nth_page(page_num).get_graph_area().get_graph().get_origin()[0] 801 new_ofs = self.notebook.get_nth_page(page_num).get_graph_area().get_graph().get_origin()[0]
766 new_value = old_value - old_ofs + new_ofs 802 new_value = old_value - old_ofs + new_ofs
767 self.notebook.get_nth_page(page_num).get_hadjustment().set_value(new_value) 803 self.notebook.get_nth_page(page_num).get_hadjustment().set_value(new_value)
804 unit = self.notebook.get_nth_page(page_num).get_graph_area().get_graph().get_attrs().unit
805 self.info_win.set_unit(unit)
768 806
769 self.notebook.last_page = page_num 807 self.notebook.last_page = page_num
770 808
771 def update_event_description(self, widget, event, msg): 809 def update_event_description(self, widget, event):
772 self.desc_label.set_text(msg) 810 if event is None:
811 self.event_label.set_text('')
812 self.job_run_label.set_text('')
813 self.job_rd_label.set_text('')
814 return
815
816 unit = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area() \
817 .get_graph().get_attrs().unit
818 self.event_label.set_text(event.str_short(unit))
819 job = event.get_job()
820 if job is None:
821 self.job_run_label.set_text('')
822 self.job_rd_label.set_text('')
823 else:
824 self.job_run_label.set_text(job.str_run_short(unit))
825 self.job_rd_label.set_text(job.str_rd_short(unit))
773 826
774 def request_context_menu(self, widget, gdk_event, selected): 827 def request_context_menu(self, widget, gdk_event, selected):
775 button = 0 828 button = 0
@@ -789,23 +842,34 @@ class MainWindow(gtk.Window):
789 if self.notebook.get_nth_page(i).get_graph_area() is sender: 842 if self.notebook.get_nth_page(i).get_graph_area() is sender:
790 self.notebook.get_nth_page(i).get_graph_area().refresh_events(sender, old, new, replace) 843 self.notebook.get_nth_page(i).get_graph_area().refresh_events(sender, old, new, replace)
791 break 844 break
792 845
793 def move_to_time_activate(self, widget): 846 def move_to_time_activate(self, widget):
794 dialog = TextInputDialog('Move to Time', 'What time to move to?', self) 847 unit = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area() \
848 .get_graph().get_attrs().unit
849
850 self.text_input_dialog.set_prompt('What time to move to (default ' + str(unit) + ') ?')
795 851
796 err = True 852 err = True
797 while err: 853 while err:
798 ret = dialog.run() 854 ret = self.text_input_dialog.run()
799 855
800 if ret == gtk.RESPONSE_ACCEPT: 856 if ret == gtk.RESPONSE_ACCEPT:
801 err, time = None, None 857 err, time = None, None
802 try: 858 try:
803 time = float(dialog.get_input()) 859 input_tokens = self.text_input_dialog.get_input().split()
804 start, end = self.notebook.get_nth_page(0).get_graph_area().get_schedule().get_time_bounds() 860 if len(input_tokens) > 2:
861 raise ValueError
862
863 if len(input_tokens) > 1:
864 unit = util.parse_unit(input_tokens[1])
865
866 time = unit.native_to_nsec(float(input_tokens[0]))
867 start, end = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area() \
868 .get_schedule().get_time_bounds()
805 if time < start or time > end: 869 if time < start or time > end:
806 err = 'Time out of range!' 870 err = 'Time out of range!'
807 except ValueError: 871 except ValueError:
808 err = 'Must input a number!' 872 err = 'Must input a number (with an optional unit)!'
809 873
810 if not err: 874 if not err:
811 for i in xrange(0, self.notebook.get_n_pages()): 875 for i in xrange(0, self.notebook.get_n_pages()):
@@ -820,12 +884,12 @@ class MainWindow(gtk.Window):
820 err) 884 err)
821 err_dialog.set_title('Input Error') 885 err_dialog.set_title('Input Error')
822 err_dialog.run() 886 err_dialog.run()
823 err_dialog.destroy() 887 err_dialog.hide()
824 888
825 else: 889 else:
826 break 890 break
827 891
828 dialog.destroy() 892 self.text_input_dialog.hide()
829 893
830 def zoom_in_item_activate(self, widget): 894 def zoom_in_item_activate(self, widget):
831 for i in range(0, self.notebook.get_n_pages()): 895 for i in range(0, self.notebook.get_n_pages()):
@@ -838,13 +902,13 @@ class MainWindow(gtk.Window):
838 def colors_activate(self, widget): 902 def colors_activate(self, widget):
839 garea = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area() 903 garea = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area()
840 graph = garea.get_renderer().get_graph() 904 graph = garea.get_renderer().get_graph()
841 905
842 906
843 pattern = graph.get_bar_pattern(self.cur_item_no) 907 pattern = graph.get_bar_pattern(self.cur_item_no)
844 908
845 if len(pattern.get_color_list()) > 1: 909 if len(pattern.get_color_list()) > 1:
846 print 'striped patterns not yet supported' 910 print 'striped patterns not yet supported'
847 911
848 r, g, b = pattern.get_color_list()[0] 912 r, g, b = pattern.get_color_list()[0]
849 colorsel = self.color_dialog.get_color_selection() 913 colorsel = self.color_dialog.get_color_selection()
850 cur_color = gtk.gdk.Color(int(r * 65535), int(g * 65535), int(b * 65535)) 914 cur_color = gtk.gdk.Color(int(r * 65535), int(g * 65535), int(b * 65535))
@@ -857,34 +921,34 @@ class MainWindow(gtk.Window):
857 r, g, b = color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0 921 r, g, b = color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0
858 graph.set_bar_pattern(self.cur_item_no, Pattern([(r, g, b)])) 922 graph.set_bar_pattern(self.cur_item_no, Pattern([(r, g, b)]))
859 garea.refresh_all() 923 garea.refresh_all()
860 924
861 self.color_dialog.hide() 925 self.color_dialog.hide()
862 926
863 def save_item_activate(self, widget): 927 def save_item_activate(self, widget):
864 ACCEPTED_TYPES = ('png', 'svg') 928 ACCEPTED_TYPES = ('png', 'svg')
865 929
866 dialog = gtk.FileChooserDialog('Save to Graphics File', self, 930 dialog = gtk.FileChooserDialog('Save to Graphics File', self,
867 gtk.FILE_CHOOSER_ACTION_SAVE, 931 gtk.FILE_CHOOSER_ACTION_SAVE,
868 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 932 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
869 gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) 933 gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
870 934
871 png_filter = gtk.FileFilter() 935 png_filter = gtk.FileFilter()
872 png_filter.add_mime_type('image/png') 936 png_filter.add_mime_type('image/png')
873 png_filter.set_name('PNG image (*.png)') 937 png_filter.set_name('PNG image (*.png)')
874 dialog.add_filter(png_filter) 938 dialog.add_filter(png_filter)
875 939
876 svg_filter = gtk.FileFilter() 940 svg_filter = gtk.FileFilter()
877 svg_filter.add_mime_type('image/svg+xml') 941 svg_filter.add_mime_type('image/svg+xml')
878 svg_filter.set_name('SVG vector image (*.svg)') 942 svg_filter.set_name('SVG vector image (*.svg)')
879 dialog.add_filter(svg_filter) 943 dialog.add_filter(svg_filter)
880 944
881 filename = None 945 filename = None
882 ftype = None 946 ftype = None
883 while True: 947 while True:
884 if dialog.run() == gtk.RESPONSE_ACCEPT: 948 if dialog.run() == gtk.RESPONSE_ACCEPT:
885 filename = dialog.get_filename() 949 filename = dialog.get_filename()
886 # parse filename for extension 950 # parse filename for extension
887 951
888 parts = filename.split('.') 952 parts = filename.split('.')
889 ftype = None 953 ftype = None
890 if len(parts) == 1: 954 if len(parts) == 1:
@@ -910,7 +974,7 @@ class MainWindow(gtk.Window):
910 filename = None 974 filename = None
911 ftype = None 975 ftype = None
912 break 976 break
913 977
914 if filename is not None: 978 if filename is not None:
915 cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) 979 cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
916 dialog.get_window().set_cursor(cursor) 980 dialog.get_window().set_cursor(cursor)
@@ -919,9 +983,9 @@ class MainWindow(gtk.Window):
919 dialog.get_window().set_cursor(None) 983 dialog.get_window().set_cursor(None)
920 dialog.destroy() 984 dialog.destroy()
921 gobject.idle_add(idle_callback, dialog) # needed to get the cursor to display 985 gobject.idle_add(idle_callback, dialog) # needed to get the cursor to display
922 else: 986 else:
923 dialog.destroy() 987 dialog.destroy()
924 988
925 def quit_item_activate(self, widget): 989 def quit_item_activate(self, widget):
926 self.destroy() 990 self.destroy()
927 991
diff --git a/unit_trace/viz/windows.py b/unit_trace/viz/windows.py
index 4e5af5c..cd75398 100644
--- a/unit_trace/viz/windows.py
+++ b/unit_trace/viz/windows.py
@@ -8,20 +8,20 @@ class TextInputDialog(gtk.Dialog):
8 WINDOW_WIDTH_REQ = 250 8 WINDOW_WIDTH_REQ = 250
9 WINDOW_HEIGHT_REQ = 100 9 WINDOW_HEIGHT_REQ = 100
10 10
11 def __init__(self, title, label, parent_window=None): 11 def __init__(self, title, prompt, parent_window=None):
12 super(TextInputDialog, self).__init__(title, parent_window, 12 super(TextInputDialog, self).__init__(title, parent_window,
13 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 13 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
14 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 14 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
15 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) 15 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
16 label_widget = gtk.Label(label) 16 self.label_widget = gtk.Label(prompt)
17 label_widget.set_alignment(0.0, 0.0) 17 self.label_widget.set_alignment(0.0, 0.0)
18 self.text_input = gtk.Entry() 18 self.text_input = gtk.Entry()
19 label_widget.show() 19 self.label_widget.show()
20 self.text_input.show() 20 self.text_input.show()
21 21
22 vbox = self.get_content_area() 22 vbox = self.get_content_area()
23 23
24 vbox.pack_start(label_widget, False, False, 0) 24 vbox.pack_start(self.label_widget, False, False, 0)
25 vbox.pack_start(self.text_input, False, False, 0) 25 vbox.pack_start(self.text_input, False, False, 0)
26 vbox.show() 26 vbox.show()
27 27
@@ -29,6 +29,9 @@ class TextInputDialog(gtk.Dialog):
29 29
30 self.set_size_request(TextInputDialog.WINDOW_WIDTH_REQ, TextInputDialog.WINDOW_HEIGHT_REQ) 30 self.set_size_request(TextInputDialog.WINDOW_WIDTH_REQ, TextInputDialog.WINDOW_HEIGHT_REQ)
31 31
32 def set_prompt(self, text):
33 self.label_widget.set_text(text)
34
32 def get_input(self): 35 def get_input(self):
33 return self.text_input.get_text() 36 return self.text_input.get_text()
34 37
@@ -57,9 +60,18 @@ class InfoWindow(gtk.Window):
57 60
58 self.add(self.vbox) 61 self.add(self.vbox)
59 62
63 self.unit = None
64
60 self.set_default_size(InfoWindow.WINDOW_WIDTH_REQ, InfoWindow.WINDOW_HEIGHT_REQ) 65 self.set_default_size(InfoWindow.WINDOW_WIDTH_REQ, InfoWindow.WINDOW_HEIGHT_REQ)
61 self.set_title('Event Details') 66 self.set_title('Event Details')
62 67
63 def set_event(self, event): 68 def set_event(self, event):
64 self.text_view.get_buffer().set_text(event.str_long()) 69 self.text_view.get_buffer().set_text(event.str_long(self.unit))
65 self.frm.set_label('Details for ' + event.get_name()) 70 self.frm.set_label('Details for ' + event.get_name())
71
72 def set_unit(self, unit):
73 self.unit = unit
74
75 def get_unit(self):
76 return self.unit
77