summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGary Bressler <garybressler@nc.rr.com>2010-03-15 13:40:58 -0400
committerGary Bressler <garybressler@nc.rr.com>2010-03-15 13:40:58 -0400
commit7203974deea94b11b3a0a99619f3c24091b157ed (patch)
tree1ac2297c45a887b736ba3b79d4402576348bf241
parent1f6656c3b8f8e72e3be4ad3e357e748b7d6e0603 (diff)
parent452023b74dfc6332c1ec548b15a0ed79e0a51b32 (diff)
Merge branch 'wip-gary' of ssh://cvs.cs.unc.edu/cvs/proj/litmus/repo/unit-trace into wip-gary
Conflicts: README reader/sample_script.py visualizer.py
-rw-r--r--README105
-rw-r--r--gedf_test.py~164
-rwxr-xr-xreader/sample_script.py41
-rw-r--r--unit_trace/viz/__init__.py8
-rw-r--r--unit_trace/viz/convert.py2
-rw-r--r--unit_trace/viz/draw.py142
-rw-r--r--unit_trace/viz/format.py2
-rw-r--r--unit_trace/viz/schedule.py226
-rw-r--r--unit_trace/viz/trace_reader.py32
-rw-r--r--unit_trace/viz/viewer.py315
-rwxr-xr-xvisualizer.py38
11 files changed, 890 insertions, 185 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..4c1b772
--- /dev/null
+++ b/README
@@ -0,0 +1,105 @@
1See the LITMUS Wiki page for a general explanation of this tool.
2
3unit_trace consists of two modules and a core. The ``core'' is basically
4a bunch of code, implemented as Python iterators, which converts the
5raw trace data into a sequence of record objects, implemented in
6Python. The modules are:
7
81) A simple module that outputs the contents of each record to
9stdout. This module, along with most of the core, can be found in the
10reader/ directory. There is a sample script -- look at
11sample_script.py in the reader/ directory (it's pretty
12self-explanatory). Note that Mac is the one who coded most of the
13this, though I can probably try to answer any questions about it since
14I've had to go in there from time to time.
15
162) The visualizer. Now, the GUI as it stands is very basic -- it's
17basically just a shell for the core visualizer component. How to open
18a file is obvious -- but note that you can open several files at a
19time (since more often than not a trace consists of more than one
20file, typically one for each CPU).
21
22Most of the code for this is in the viz/ directory, but to run it, the
23file you want to execute is visualizer.py (in the main directory).
24
25A few notes on how to use the GUI:
26
27-- How to scroll is pretty obvious, though I still need to implement
28 keypresses (very trivial, but when making a GUI component from
29 scratch it always seems like there are a million little things that
30 you need to do :)
31
32-- You can view either by task or by CPU; click the tabs at the top.
33
34-- Mousing over the items (not the axes, though, since those are
35 pretty self-explanatory) gives you information about the item that
36 you moused over, displayed at the bottom.
37
38-- You can select items. You can click them individually, one at a
39 time, or you can drag or ctrl-click to select multiple.
40
41-- What you have selected is independent of what mode (task or CPU)
42 you are operating in. So if you are curious, say, when a certain
43 job is running compared to other jobs on the same CPU, you can
44 click a job in task mode and then switch to CPU mode, and it will
45 remain selected.
46
47-- Right-click to get a menu of all the items you have selected (in
48 the future this menu will be clickable, so that you can get the
49 information about an item in its own window).
50
51-- It is a bit laggy when lots of stuff is on the screen at once. This
52 should be fairly easy to optimize, if I have correctly identified
53 the problem, but it's not a huge issue (it's not _that_ slow).
54
55But wait, there's more:
56
57-- As of now unit-trace has no way to determine the algorithm that was
58 used on the trace you're loading. This is important since certain
59 sections of code work only with G-EDF in particular. The point of
60 having this special code is either to filter out bogus data or to
61 generate extra information about the schedule (e.g. priority
62 inversions). Of course, you can leave these extra steps out and
63 it will still work, but you might get extra ``bogus'' information
64 generated by the tracer or you might not get all the information
65 you want.
66
67-- To add or remove these extra steps, take a look at visualizer.py
68 and sample_script.py. You will see some code like this:
69
70 stream = reader.trace_reader.trace_reader(file_list)
71 #stream = reader.sanitizer.sanitizer(stream)
72 #stream = reader.gedf_test.gedf_test(stream)
73
74 Uncommenting those lines will run the extra steps in the pipeline.
75 The sanitizer filters out some bogus data (stuff like ``pid 0''),
76 but so far it's only been coded for a select number of traces.
77 gedf_test generates extra information about G-EDF schedules
78 (the traces that are named st-g?-?.bin). If you try to run
79 gedf_test on anything else, it will most likely fail.
80
81-- What traces are you going to use? Well, you will probably want to
82 use your own, but there are some samples you can try in the traces/
83 directory (a couple of them do give error messages, however).
84
85How to install:
86
87You should type
88
89git clone -b wip-gary ssh://cvs.cs.unc.edu/cvs/proj/litmus/repo/unit-trace.git
90
91to check out the repository. Note that you shouldn't check out the
92master branch, as it's pretty outdated. It's all Python so far, so no
93compiling or anything like that is necessary.
94
95Requirements:
96
97You're going to need Python 2.5 to run this. You'll also need to
98install the pycairo and pygtk libraries. If anyone has questions about
99how to do this or what these are, ask me.
100
101Miscellanies:
102
103Of course, let me know if you find any bugs (I'm sure there are
104plenty, though, since this is fairly alpha software), if you're
105unable to run it, or if you have any questions. \ No newline at end of file
diff --git a/gedf_test.py~ b/gedf_test.py~
new file mode 100644
index 0000000..2381dad
--- /dev/null
+++ b/gedf_test.py~
@@ -0,0 +1,164 @@
1###############################################################################
2# Description
3###############################################################################
4
5# G-EDF Test
6
7###############################################################################
8# Imports
9###############################################################################
10#test
11
12import copy
13
14
15###############################################################################
16# Public Functions
17###############################################################################
18
19def gedf_test(stream):
20
21 # Two lists to model the system: tasks occupying a CPU and tasks eligible
22 # to do so. Also, m = the number of CPUs.
23 eligible = []
24 on_cpu = []
25 m = None
26
27 # Time of the last record we saw. Only run the G-EDF test when the time
28 # is updated.
29 last_time = None
30
31 for record in stream:
32 if record.record_type != "event":
33 if record.record_type == "meta" and record.type_name == "num_cpus":
34 m = record.num_cpus
35 continue
36
37 # Check for inversion starts and ends and yield them.
38 # Only to the check when time has moved forward.
39 # (It is common to have records with simultaneous timestamps.)
40 if last_time is not None and last_time != record.when:
41 errors = _gedf_check(eligible,on_cpu,record.when,m)
42 for error in errors:
43 yield error
44
45 # Add a newly-released Job to the eligible queue
46 if record.type_name == 'release':
47 eligible.append(Job(record))
48
49 # Move a Job from the eligible queue to on_cpu
50 elif record.type_name == 'switch_to':
51 pos = _find_job(record,eligible)
52 job = eligible[pos]
53 del eligible[pos]
54 on_cpu.append(job)
55
56 # Mark a Job as completed.
57 # The only time a Job completes when it is not on a
58 # CPU is when it is the last job of the task.
59 elif record.type_name == 'completion':
60 pos = _find_job(record,on_cpu)
61 if pos is not None:
62 on_cpu[pos].is_complete = True
63 else:
64 pos = _find_job(record,eligible)
65 del eligible[pos]
66
67 # A job is switched away from a CPU. If it has
68 # been marked as complete, remove it from the model.
69 elif record.type_name == 'switch_away':
70 pos = _find_job(record,on_cpu)
71 job = on_cpu[pos]
72 del on_cpu[pos]
73 if job.is_complete == False:
74 eligible.append(job)
75
76 last_time = record.when
77 yield record
78
79###############################################################################
80# Private Functions
81###############################################################################
82
83# Internal representation of a Job
84class Job(object):
85 def __init__(self, record):
86 self.pid = record.pid
87 self.job = record.job
88 self.deadline = record.deadline
89 self.is_complete = False
90 self.inversion_start = None
91 self.inversion_end = None
92 def __str__(self):
93 return "(%d.%d:%d)" % (self.pid,self.job,self.deadline)
94
95# G-EDF errors: the start or end of an inversion
96class Error(object):
97 def __init__(self, job, eligible, on_cpu):
98 self.job = copy.copy(job)
99 self.eligible = copy.copy(eligible)
100 self.on_cpu = copy.copy(on_cpu)
101 self.record_type = 'error'
102 if job.inversion_end is None:
103 self.type_name = 'inversion_start'
104 else:
105 self.type_name = 'inversion_end'
106
107# Returns the position of a Job in a list, or None
108def _find_job(record,list):
109 for i in range(0,len(list)):
110 if list[i].pid == record.pid and list[i].job == record.job:
111 return i
112 return None
113
114# Return records for any inversion_starts and inversion_ends
115def _gedf_check(eligible,on_cpu,when,m):
116
117 # List of error records to be returned
118 errors = []
119
120 # List of all jobs that are not complete
121 all = []
122 for x in on_cpu:
123 if x.is_complete is not True:
124 all.append(x)
125 all += eligible
126
127 # Sort by on_cpu and then by deadline. sort() is guaranteed to be stable.
128 # Thus, this gives us jobs ordered by deadline with preference to those
129 # actually running.
130 all.sort(key=lambda x: 0 if (x in on_cpu) else 1)
131 all.sort(key=lambda x: x.deadline)
132
133 # Check those that actually should be running
134 for x in range(0,min(m,len(all))):
135 job = all[x]
136
137 # It's not running and an inversion_start has not been recorded
138 if job not in on_cpu and job.inversion_start is None:
139 job.inversion_start = when
140 errors.append(Error(job, eligible, on_cpu))
141
142 # It is running and an inversion_start exists (i.e. it it still
143 # marked as being inverted)
144 elif job in on_cpu and job.inversion_start is not None:
145 job.inversion_end = when
146 errors.append(Error(job, eligible, on_cpu))
147 job.inversion_start = None
148 job.inversion_end = None
149
150 # Check those that actually should not be running
151 for x in range(m,len(all)):
152 job = all[x]
153
154 # It actually is running. We don't care.
155
156 # It isn't running, but an inversion_start exists (i.e. it is still
157 # marked as being inverted)
158 if job not in on_cpu and job.inversion_start is not None:
159 job.inversion_end = when
160 errors.append(Error(job, eligible, on_cpu))
161 job.inversion_start = None
162 job.inversion_end = None
163
164 return errors
diff --git a/reader/sample_script.py b/reader/sample_script.py
new file mode 100755
index 0000000..676cfac
--- /dev/null
+++ b/reader/sample_script.py
@@ -0,0 +1,41 @@
1#!/usr/bin/python
2
3# This is a sample script for using the tool. I would recommend copying
4# this and modifying it to suit your needs for a particular test. Make
5# sure you redirect the output to a file (e.g. ./sample_script.py > output).
6
7# Import the modules we need. You should not need to know about
8# their internals.
9import trace_reader
10import sanitizer
11import gedf_test
12import stats
13import stdout_printer
14
15# Specify your trace files
16g6 = [
17'../traces/st-g6-0.bin',
18'../traces/st-g6-1.bin',
19'../traces/st-g6-2.bin',
20'../traces/st-g6-3.bin',
21]
22
23# Here is an example of a custom filter function.
24# It will remove from the error stream all inversion_end records indicating
25# an inversion of less than 4000000 time units. Thus, you can grep through
26# the output looking 'Inversion end' and find only errors for particularly
27# long inversions. This is commented out in the pipeline (below) since you
28# probably don't want it in general.
29def my_filter(record):
30 if record.record_type == 'error' and record.type_name == 'inversion_end':
31 if record.job.inversion_end - record.job.inversion_start < 4000000:
32 return False
33 return True
34
35# Pipeline
36stream = trace_reader.trace_reader(g6) # Read events from traces
37stream = sanitizer.sanitizer(stream) # Remove garbage events
38stream = gedf_test.gedf_test(stream) # Produce G-EDF error records
39stream = stats.stats(stream) # Produce a statistics record
40#stream = filter(my_filter, stream) # Filter some records before printing
41stdout_printer.stdout_printer(stream) # Print records to stdout
diff --git a/unit_trace/viz/__init__.py b/unit_trace/viz/__init__.py
index 5f8d000..ab141e1 100644
--- a/unit_trace/viz/__init__.py
+++ b/unit_trace/viz/__init__.py
@@ -6,6 +6,10 @@ import gtk
6import convert 6import convert
7 7
8gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 8gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
9 None, (gtk.Adjustment, gtk.Adjustment)) 9 None, (gtk.Adjustment, gtk.Adjustment))
10gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 10gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
11 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) 11 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
12gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
13 None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT))
14gobject.signal_new('request-renderer-change', viewer.MainWindow, gobject.SIGNAL_RUN_FIRST,
15 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
diff --git a/unit_trace/viz/convert.py b/unit_trace/viz/convert.py
index deb50c8..f37dd27 100644
--- a/unit_trace/viz/convert.py
+++ b/unit_trace/viz/convert.py
@@ -79,7 +79,7 @@ def convert_trace_to_schedule(stream):
79 } 79 }
80 80
81 actions[record.type_name]() 81 actions[record.type_name]()
82 82
83 return sched 83 return sched
84 84
85def _pid_to_task_name(pid): 85def _pid_to_task_name(pid):
diff --git a/unit_trace/viz/draw.py b/unit_trace/viz/draw.py
index c3ab756..c30ffe7 100644
--- a/unit_trace/viz/draw.py
+++ b/unit_trace/viz/draw.py
@@ -84,31 +84,40 @@ class ImageSurface(Surface):
84 84
85class Pattern(object): 85class Pattern(object):
86 DEF_STRIPE_SIZE = 10 86 DEF_STRIPE_SIZE = 10
87 MAX_FADE_WIDTH = 250
87 88
88 def __init__(self, color_list, stripe_size=DEF_STRIPE_SIZE): 89 def __init__(self, color_list, stripe_size=DEF_STRIPE_SIZE):
89 self.color_list = color_list 90 self.color_list = color_list
90 self.stripe_size = stripe_size 91 self.stripe_size = stripe_size
91 92
92 def render_on_canvas(self, canvas, x, y, width, height, fade=False): 93 def render_on_canvas(self, canvas, x, y, width, height, fade=False):
94 fade_span = min(width, Pattern.MAX_FADE_WIDTH)
95
93 if len(self.color_list) == 1: 96 if len(self.color_list) == 1:
94 if fade: 97 if fade:
95 canvas.fill_rect_fade(x, y, width, height, (1.0, 1.0, 1.0), \ 98 canvas.fill_rect_fade(x, y, fade_span, height, (1.0, 1.0, 1.0), \
96 self.color_list[0]) 99 self.color_list[0])
97 else: 100 else:
98 canvas.fill_rect(x, y, width, height, self.color_list[0]) 101 canvas.fill_rect(x, y, width, height, self.color_list[0])
99 102
103 if width > Pattern.MAX_FADE_WIDTH:
104 canvas.fill_rect(x + Pattern.MAX_FADE_WIDTH, y, width - Pattern.MAX_FADE_WIDTH,
105 height, self.color_list[0])
100 else: 106 else:
101 n = 0 107 n = 0
102 bottom = y + height 108 bottom = y + height
103 while y < bottom: 109 while y < bottom:
104 linear = cairo.LinearGradient(x, y, x + width, math.min(y + self.stripe_size, bottom))
105 i = n % len(self.color_list) 110 i = n % len(self.color_list)
106 if fade: 111 if fade:
107 canvas.fill_rect_fade(x, y, width, min(self.stripe_size, bottom - y), (1.0, 1.0, 1.0), \ 112 canvas.fill_rect_fade(x, y, fade_span, \
108 self.color_list[i]) 113 min(self.stripe_size, bottom - y), (1.0, 1.0, 1.0), self.color_list[i])
109 else: 114 else:
110 canvas.fill_rect(x, y, width, min(self.stripe_size, bottom - y), self.color_list[i]) 115 canvas.fill_rect(x, y, width, min(self.stripe_size, bottom - y), self.color_list[i])
111 116
117 if width > Pattern.MAX_FADE_WIDTH:
118 canvas.fill_rect(x + Pattern.MAX_FADE_WIDTH, y, width - Pattern.MAX_FADE_WIDTH,
119 min(self.stripe_size, bottom - y), self.color_list[i])
120
112 y += self.stripe_size 121 y += self.stripe_size
113 n += 1 122 n += 1
114 123
@@ -129,6 +138,8 @@ class Canvas(object):
129 138
130 LAYERS = (BOTTOM_LAYER, MIDDLE_LAYER, TOP_LAYER) 139 LAYERS = (BOTTOM_LAYER, MIDDLE_LAYER, TOP_LAYER)
131 140
141 NULL_PATTERN = -1
142
132 SQRT3 = math.sqrt(3.0) 143 SQRT3 = math.sqrt(3.0)
133 144
134 def __init__(self, width, height, item_clist, bar_plist, surface): 145 def __init__(self, width, height, item_clist, bar_plist, surface):
@@ -470,28 +481,32 @@ class Canvas(object):
470 def add_sel_resume_triangle(self, x, y, height, event): 481 def add_sel_resume_triangle(self, x, y, height, event):
471 self.add_sel_region(SelectableRegion(x - height / 2.0, y, height / 2.0, height, event)) 482 self.add_sel_region(SelectableRegion(x - height / 2.0, y, height / 2.0, height, event))
472 483
473 def clear_selectable_regions(self): 484 def clear_selectable_regions(self, real_x, real_y, width, height):
474 self.selectable_regions = {} 485 x = real_x + self.surface.virt_x
486 y = real_y + self.surface.virt_y
487 for event in self.selectable_regions.keys():
488 if self.selectable_regions[event].intersects(x, y, width, height):
489 del self.selectable_regions[event]
475 490
476 def add_sel_region(self, region): 491 def add_sel_region(self, region):
477 self.selectable_regions[region.get_event()] = region 492 self.selectable_regions[region.get_event()] = region
478 493
479 def get_selected_regions(self, real_x, real_y): 494 def get_selected_regions(self, real_x, real_y, width, height):
480 x = real_x + self.surface.virt_x 495 x = real_x + self.surface.virt_x
481 y = real_y + self.surface.virt_y 496 y = real_y + self.surface.virt_y
482 497
483 selected = {} 498 selected = {}
484 for event in self.selectable_regions: 499 for event in self.selectable_regions:
485 region = self.selectable_regions[event] 500 region = self.selectable_regions[event]
486 if region.contains(x, y): 501 if region.intersects(x, y, width, height):
487 selected[event] = region 502 selected[event] = region
488 503
489 return selected 504 return selected
490 505
491 def whiteout(self): 506 def whiteout(self, real_x, real_y, width, height):
492 """Overwrites the surface completely white, but technically doesn't delete anything""" 507 """Overwrites the surface completely white, but technically doesn't delete anything"""
493 self.fill_rect(self.surface.virt_x, self.surface.virt_y, self.surface.width, 508 self.fill_rect(self.surface.virt_x + real_x, self.surface.virt_y + real_y, width,
494 self.surface.height, (1.0, 1.0, 1.0)) 509 height, (1.0, 1.0, 1.0))
495 510
496 def get_item_color(self, n): 511 def get_item_color(self, n):
497 """Gets the nth color in the item color list, which are the colors used to draw the items 512 """Gets the nth color in the item color list, which are the colors used to draw the items
@@ -505,7 +520,9 @@ class Canvas(object):
505 fill in the bars. Note that there are conceptually infinitely 520 fill in the bars. Note that there are conceptually infinitely
506 many patterns because the patterns repeat -- that is, we just mod out by the size of the pattern 521 many patterns because the patterns repeat -- that is, we just mod out by the size of the pattern
507 list when indexing.""" 522 list when indexing."""
508 return self.bar_plist[n % len(self.bar_plist)] 523 if n < 0:
524 return self.bar_plist[-1]
525 return self.bar_plist[n % (len(self.bar_plist) - 1)]
509 526
510class CairoCanvas(Canvas): 527class CairoCanvas(Canvas):
511 """This is a basic class that stores and draws on a Cairo surface, 528 """This is a basic class that stores and draws on a Cairo surface,
@@ -679,8 +696,8 @@ class SelectableRegion(object):
679 def get_event(self): 696 def get_event(self):
680 return self.event 697 return self.event
681 698
682 def contains(self, x, y): 699 def intersects(self, x, y, width, height):
683 return self.x <= x <= self.x + self.width and self.y <= y <= self.y + self.height 700 return x <= self.x + self.width and x + width >= self.x and y <= self.y + self.height and y + height >= self.y
684 701
685class Graph(object): 702class Graph(object):
686 DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), Pattern([(0.9, 0.7, 0.0)]), 703 DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), Pattern([(0.9, 0.7, 0.0)]),
@@ -691,9 +708,15 @@ class Graph(object):
691 708
692 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, attrs=GraphFormat(), 709 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, attrs=GraphFormat(),
693 item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST): 710 item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST):
711 # deal with possibly blank schedules
712 if start_time is None:
713 start_time = 0
714 if end_time is None:
715 end_time = 0
716
694 if start_time > end_time: 717 if start_time > end_time:
695 raise ValueError("Litmus is not a time machine") 718 raise ValueError("Litmus is not a time machine")
696 719
697 self.attrs = attrs 720 self.attrs = attrs
698 self.start_time = start_time 721 self.start_time = start_time
699 self.end_time = end_time 722 self.end_time = end_time
@@ -720,6 +743,7 @@ class Graph(object):
720 extra_width = cur_width 743 extra_width = cur_width
721 744
722 width += extra_width 745 width += extra_width
746
723 self.origin = (extra_width + GraphFormat.WIDTH_PAD / 2.0, height - GraphFormat.HEIGHT_PAD / 2.0) 747 self.origin = (extra_width + GraphFormat.WIDTH_PAD / 2.0, height - GraphFormat.HEIGHT_PAD / 2.0)
724 748
725 self.width = width 749 self.width = width
@@ -730,8 +754,8 @@ class Graph(object):
730 754
731 self.canvas = CanvasType(width, height, item_clist, bar_plist, surface) 755 self.canvas = CanvasType(width, height, item_clist, bar_plist, surface)
732 756
733 def get_selected_regions(self, real_x, real_y): 757 def get_selected_regions(self, real_x, real_y, width, height):
734 return self.canvas.get_selected_regions(real_x, real_y) 758 return self.canvas.get_selected_regions(real_x, real_y, width, height)
735 759
736 def get_width(self): 760 def get_width(self):
737 return self.width 761 return self.width
@@ -739,6 +763,9 @@ class Graph(object):
739 def get_height(self): 763 def get_height(self):
740 return self.height 764 return self.height
741 765
766 def get_origin(self):
767 return self.origin
768
742 def get_attrs(self): 769 def get_attrs(self):
743 return self.attrs 770 return self.attrs
744 771
@@ -788,12 +815,12 @@ class Graph(object):
788 def ycoor_to_item_no(self, y): 815 def ycoor_to_item_no(self, y):
789 return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size) 816 return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size)
790 817
791 def get_offset_params(self): 818 def get_offset_params(self, real_x, real_y, width, height):
792 start_time = self.xcoor_to_time(self.canvas.surface.virt_x) 819 start_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x)
793 end_time = self.xcoor_to_time(self.canvas.surface.virt_x + self.canvas.surface.width) 820 end_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x + width)
794 821
795 start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y) 822 start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y)
796 end_item = 1 + self.ycoor_to_item_no(self.canvas.surface.virt_y + self.canvas.surface.height) 823 end_item = 1 + self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y + height)
797 824
798 return (start_time, end_time, start_item, end_item) 825 return (start_time, end_time, start_item, end_item)
799 826
@@ -802,7 +829,7 @@ class Graph(object):
802 self.draw_x_axis_with_labels_at_time(start_time, end_time) 829 self.draw_x_axis_with_labels_at_time(start_time, end_time)
803 self.draw_y_axis_with_labels() 830 self.draw_y_axis_with_labels()
804 831
805 def render_surface(self, sched, list_type): 832 def render_surface(self, sched, real_x, real_y, width, height, selectable=False):
806 raise NotImplementedError 833 raise NotImplementedError
807 834
808 def render_all(self, schedule): 835 def render_all(self, schedule):
@@ -919,21 +946,24 @@ class Graph(object):
919 raise NotImplementedError 946 raise NotImplementedError
920 947
921class TaskGraph(Graph): 948class TaskGraph(Graph):
922 def render_surface(self, sched): 949 def render_surface(self, sched, real_x, real_y, width, height, selectable=False):
923 self.canvas.whiteout() 950 if not selectable:
924 self.canvas.clear_selectable_regions() 951 self.canvas.whiteout(real_x, real_y, width, height)
952 else:
953 self.canvas.clear_selectable_regions(real_x, real_y, width, height)
925 954
926 start_time, end_time, start_item, end_item = self.get_offset_params() 955 start_time, end_time, start_item, end_item = self.get_offset_params(real_x, real_y, width, height)
927 956
928 self.draw_skeleton(start_time, end_time, start_item, end_item) 957 if not selectable:
958 self.draw_skeleton(start_time, end_time, start_item, end_item)
929 959
930 for layer in Canvas.LAYERS: 960 for layer in Canvas.LAYERS:
931 prev_events = {} 961 prev_events = {}
932 for event in sched.get_time_slot_array().iter_over_period( 962 for event in sched.get_time_slot_array().iter_over_period(
933 start_time, end_time, start_item, end_item, 963 start_time, end_time, start_item, end_item,
934 schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST): 964 schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST):
935 event.render(self, layer, prev_events) 965 event.render(self, layer, prev_events, selectable)
936 966
937 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 967 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
938 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR 968 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
939 x = self._get_time_xpos(time) 969 x = self._get_time_xpos(time)
@@ -1046,7 +1076,7 @@ class TaskGraph(Graph):
1046 width = self._get_bar_width(start_time, end_time) 1076 width = self._get_bar_width(start_time, end_time)
1047 height = self._get_mini_bar_height() 1077 height = self._get_mini_bar_height()
1048 1078
1049 self.canvas.draw_mini_bar(x, y, width, height, cpu_no, selected) 1079 self.canvas.draw_mini_bar(x, y, width, height, Canvas.NULL_PATTERN, selected)
1050 1080
1051 if job_no is not None: 1081 if job_no is not None:
1052 x += GraphFormat.MINI_BAR_LABEL_OFS 1082 x += GraphFormat.MINI_BAR_LABEL_OFS
@@ -1063,35 +1093,42 @@ class TaskGraph(Graph):
1063 self.canvas.add_sel_mini_bar(x, y, width, height, event) 1093 self.canvas.add_sel_mini_bar(x, y, width, height, event)
1064 1094
1065class CpuGraph(Graph): 1095class CpuGraph(Graph):
1066 def render_surface(self, sched): 1096 def render_surface(self, sched, real_x, real_y, width, height, selectable=False):
1067 self.canvas.whiteout() 1097 if not selectable:
1068 self.canvas.clear_selectable_regions() 1098 self.canvas.whiteout(real_x, real_y, width, height)
1099 else:
1100 self.canvas.clear_selectable_regions(real_x, real_y, width, height)
1069 1101
1070 start_time, end_time, start_item, end_item = self.get_offset_params() 1102 start_time, end_time, start_item, end_item = self.get_offset_params(real_x, real_y, width, height)
1071 1103
1072 self.draw_skeleton(start_time, end_time, start_item, end_item) 1104 if not selectable:
1105 self.draw_skeleton(start_time, end_time, start_item, end_item)
1073 1106
1074 event_list = dict(schedule.EVENT_LIST) 1107 event_list = dict(schedule.EVENT_LIST)
1075 1108
1076 del event_list[schedule.ReleaseEvent] 1109 bottom_events = [schedule.ReleaseEvent, schedule.DeadlineEvent, schedule.InversionStartEvent,
1077 del event_list[schedule.DeadlineEvent] 1110 schedule.InversionEndEvent, schedule.InversionDummy]
1111
1112 for event in bottom_events:
1113 del event_list[event]
1078 1114
1079 for layer in Canvas.LAYERS: 1115 for layer in Canvas.LAYERS:
1080 prev_events = {} 1116 prev_events = {}
1081 for event in sched.get_time_slot_array().iter_over_period( 1117 for event in sched.get_time_slot_array().iter_over_period(
1082 start_time, end_time, start_item, end_item, 1118 start_time, end_time, start_item, end_item,
1083 schedule.TimeSlotArray.CPU_LIST, schedule.EVENT_LIST): 1119 schedule.TimeSlotArray.CPU_LIST, schedule.EVENT_LIST):
1084 event.render(self, layer, prev_events) 1120 event.render(self, layer, prev_events, selectable)
1085 1121
1086 if end_item >= len(self.y_item_list): 1122 if end_item >= len(self.y_item_list):
1087 # we are far down enough that we should render the releases and deadlines 1123 # we are far down enough that we should render the releases and deadlines and inversions,
1124 # which appear near the x-axis
1088 for layer in Canvas.LAYERS: 1125 for layer in Canvas.LAYERS:
1089 prev_events = {} 1126 prev_events = {}
1090 for event in sched.get_time_slot_array().iter_over_period( 1127 for event in sched.get_time_slot_array().iter_over_period(
1091 start_time, end_time, start_item, end_item, 1128 start_time, end_time, 0, sched.get_num_cpus(),
1092 schedule.TimeSlotArray.CPU_LIST, 1129 schedule.TimeSlotArray.CPU_LIST,
1093 (schedule.ReleaseEvent, schedule.DeadlineEvent)): 1130 bottom_events):
1094 event.render(self, layer, prev_events) 1131 event.render(self, layer, prev_events, selectable)
1095 1132
1096 def render(self, schedule, start_time=None, end_time=None): 1133 def render(self, schedule, start_time=None, end_time=None):
1097 if end_time < start_time: 1134 if end_time < start_time:
@@ -1145,9 +1182,10 @@ class CpuGraph(Graph):
1145 self.canvas.draw_completion_marker(x, y, height, selected) 1182 self.canvas.draw_completion_marker(x, y, height, selected)
1146 1183
1147 def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): 1184 def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event):
1148 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR 1185 height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR
1186
1149 x = self._get_time_xpos(time) 1187 x = self._get_time_xpos(time)
1150 y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 1188 y = self._get_item_ypos(cpu_no) + self._get_bar_height() - height
1151 1189
1152 self.canvas.add_sel_completion_marker(x, y, height, event) 1190 self.canvas.add_sel_completion_marker(x, y, height, event)
1153 1191
@@ -1169,7 +1207,7 @@ class CpuGraph(Graph):
1169 AlignMode.CENTER, AlignMode.BOTTOM) 1207 AlignMode.CENTER, AlignMode.BOTTOM)
1170 1208
1171 def add_sel_release_arrow_at_time(self, time, task_no, event): 1209 def add_sel_release_arrow_at_time(self, time, task_no, event):
1172 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR 1210 height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR
1173 1211
1174 x = self._get_time_xpos(time) 1212 x = self._get_time_xpos(time)
1175 y = self.origin[1] - height 1213 y = self.origin[1] - height
@@ -1194,7 +1232,7 @@ class CpuGraph(Graph):
1194 AlignMode.CENTER, AlignMode.BOTTOM) 1232 AlignMode.CENTER, AlignMode.BOTTOM)
1195 1233
1196 def add_sel_deadline_arrow_at_time(self, time, task_no, event): 1234 def add_sel_deadline_arrow_at_time(self, time, task_no, event):
1197 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR 1235 height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR
1198 1236
1199 x = self._get_time_xpos(time) 1237 x = self._get_time_xpos(time)
1200 y = self.origin[1] - height 1238 y = self.origin[1] - height
@@ -1226,18 +1264,18 @@ class CpuGraph(Graph):
1226 width = self._get_bar_width(start_time, end_time) 1264 width = self._get_bar_width(start_time, end_time)
1227 height = self._get_bar_height() 1265 height = self._get_bar_height()
1228 1266
1229 self.canvas.add_sel_region(SelectableRegion(x, y, width, height, event)) 1267 self.canvas.add_sel_bar(x, y, width, height, event)
1230 1268
1231 def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False): 1269 def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False):
1232 if start_time > end_time: 1270 if start_time > end_time:
1233 raise ValueError("Litmus is not a time machine") 1271 raise ValueError("Litmus is not a time machine")
1234 1272
1235 x = self._get_time_xpos(start_time) 1273 x = self._get_time_xpos(start_time)
1236 y = self._get_item_ypos(cpu_no) - self._get_mini_bar_ofs() 1274 y = self._get_item_ypos(len(self.y_item_list))
1237 width = self._get_bar_width(start_time, end_time) 1275 width = self._get_bar_width(start_time, end_time)
1238 height = self._get_mini_bar_height() 1276 height = self._get_mini_bar_height()
1239 1277
1240 self.canvas.draw_mini_bar(x, y, width, height, cpu_no, selected) 1278 self.canvas.draw_mini_bar(x, y, width, height, task_no, selected)
1241 1279
1242 if job_no is not None: 1280 if job_no is not None:
1243 x += GraphFormat.MINI_BAR_LABEL_OFS 1281 x += GraphFormat.MINI_BAR_LABEL_OFS
@@ -1247,8 +1285,8 @@ class CpuGraph(Graph):
1247 1285
1248 def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): 1286 def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event):
1249 x = self._get_time_xpos(start_time) 1287 x = self._get_time_xpos(start_time)
1250 y = self._get_item_ypos(cpu_no) - self._get_mini_bar_ofs() 1288 y = self._get_item_ypos(len(self.y_item_list))
1251 width = self._get_bar_width(start_time, end_time) 1289 width = self._get_bar_width(start_time, end_time)
1252 height = self._get_mini_bar_height() 1290 height = self._get_mini_bar_height()
1253 1291
1254 self.canvas.add_sel_mini_bar(x, y, width, height, cpu_no, selected) 1292 self.canvas.add_sel_mini_bar(x, y, width, height, event)
diff --git a/unit_trace/viz/format.py b/unit_trace/viz/format.py
index fed39f0..6469467 100644
--- a/unit_trace/viz/format.py
+++ b/unit_trace/viz/format.py
@@ -23,7 +23,7 @@ class GraphFormat(object):
23 probably don't care about most of them anyway).""" 23 probably don't care about most of them anyway)."""
24 24
25 GRID_COLOR = (0.7, 0.7, 0.7) 25 GRID_COLOR = (0.7, 0.7, 0.7)
26 HIGHLIGHT_COLOR = (0.8, 0.0, 0.0) 26 HIGHLIGHT_COLOR = (0.85, 0.0, 0.0)
27 BORDER_COLOR = (0.0, 0.0, 0.0) 27 BORDER_COLOR = (0.0, 0.0, 0.0)
28 LITE_BORDER_COLOR = (0.4, 0.4, 0.4) 28 LITE_BORDER_COLOR = (0.4, 0.4, 0.4)
29 29
diff --git a/unit_trace/viz/schedule.py b/unit_trace/viz/schedule.py
index f842c8d..ae7284b 100644
--- a/unit_trace/viz/schedule.py
+++ b/unit_trace/viz/schedule.py
@@ -8,6 +8,10 @@ graphic."""
8from draw import * 8from draw import *
9import util 9import util
10 10
11import copy
12
13EVENT_LIST = None
14
11class TimeSlotArray(object): 15class TimeSlotArray(object):
12 """Represents another way of organizing the events. This structure organizes events by 16 """Represents another way of organizing the events. This structure organizes events by
13 the (approximate) time at which they occur. Events that occur at approximately the same 17 the (approximate) time at which they occur. Events that occur at approximately the same
@@ -17,55 +21,78 @@ class TimeSlotArray(object):
17 TASK_LIST = 0 21 TASK_LIST = 0
18 CPU_LIST = 1 22 CPU_LIST = 1
19 23
20 def __init__(self, start, end, time_per_maj, num_tasks, num_cpus): 24 def __init__(self, start=None, end=None, time_per_maj=1, num_tasks=0, num_cpus=0):
25 if start is None or end is None:
26 self.array = None
27 return
28
21 self.start = start 29 self.start = start
22 self.end = end 30 self.end = end
23 self.time_per_maj = time_per_maj 31 self.time_per_maj = time_per_maj
24 self.list_sizes = { TimeSlotArray.TASK_LIST : num_tasks, TimeSlotArray.CPU_LIST : num_cpus } 32 self.list_sizes = { TimeSlotArray.TASK_LIST : num_tasks, TimeSlotArray.CPU_LIST : num_cpus }
25 self.array = [{TimeSlotArray.TASK_LIST : [{} for i in range(0, num_tasks)], \ 33 self.array = {}
26 TimeSlotArray.CPU_LIST : [{} for i in range (0, num_cpus)]} \ 34
27 for i in range(0, (end - start) // self.time_per_maj + 1)] 35 for type in self.list_sizes:
36 num = self.list_sizes[type]
37 self.array[type] = []
38 for j in range(0, num):
39 # for each slot in the array, we need a list of all events under this type
40 # (for example, a list of all events that occur in this time slot, indexed
41 # by task).
42 for i in range(0, num):
43 self.array[type].append(dict(zip(EVENT_LIST, \
44 [{} for j in range(0, len(EVENT_LIST))])))
28 45
29 def get_time_slot(self, time): 46 def get_time_slot(self, time):
30 return int((time - self.start) // self.time_per_maj) 47 return int((time - self.start) // self.time_per_maj)
31 48
49 def _put_event_in_slot(self, list_type, no, klass, slot, event):
50 if slot not in self.array[list_type][no][klass]:
51 self.array[list_type][no][klass][slot] = []
52 self.array[list_type][no][klass][slot].append(event)
53
32 def add_event_to_time_slot(self, event): 54 def add_event_to_time_slot(self, event):
33 task_no = event.get_job().get_task().get_task_no() 55 task_no = event.get_job().get_task().get_task_no()
34 cpu = event.get_cpu() 56 cpu = event.get_cpu()
35 time_slot = self.get_time_slot(event.get_time()) 57 time_slot = self.get_time_slot(event.get_time())
36 58
37 self.array[time_slot][TimeSlotArray.TASK_LIST][task_no][event.__class__] = event 59 self._put_event_in_slot(TimeSlotArray.TASK_LIST, task_no, event.__class__, time_slot, event)
38 self.array[time_slot][TimeSlotArray.CPU_LIST][cpu][event.__class__] = event 60 self._put_event_in_slot(TimeSlotArray.CPU_LIST, cpu, event.__class__, time_slot, event)
39 61
40 span_events = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy} 62 span_events = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy}
41 63
42 for span_event in span_events: 64 for span_event in span_events:
43 if isinstance(event, span_event) and not event.is_erroneous(): 65 if isinstance(event, span_event) and event.corresp_start_event is not None:
44 start_slot = self.get_time_slot(event.corresp_start_event.get_time()) 66 start_slot = self.get_time_slot(event.corresp_start_event.get_time())
45 end_slot = self.get_time_slot(event.get_time()) 67 end_slot = time_slot
46 for slot in range(start_slot + 1, end_slot): 68 for slot in range(start_slot + 1, end_slot):
47 dummy = span_events[span_event](task_no, cpu) 69 dummy = span_events[span_event](task_no, cpu)
48 dummy.corresp_start_event = event.corresp_start_event 70 dummy.corresp_start_event = event.corresp_start_event
49 self.array[slot][TimeSlotArray.TASK_LIST][task_no][dummy.__class__] = dummy 71
50 self.array[slot][TimeSlotArray.CPU_LIST][cpu][dummy.__class__] = dummy 72 self._put_event_in_slot(TimeSlotArray.TASK_LIST, task_no, dummy.__class__, slot, dummy)
73 self._put_event_in_slot(TimeSlotArray.CPU_LIST, cpu, dummy.__class__, slot, dummy)
51 74
52 def iter_over_period(self, start, end, start_no, end_no, list_type, event_types): 75 def iter_over_period(self, start, end, start_no, end_no, list_type, event_types):
76 if self.array is None:
77 return # empty schedule
78
53 if start > end: 79 if start > end:
54 raise ValueError('Litmus is not a time machine') 80 raise ValueError('Litmus is not a time machine')
55 if start_no > end_no: 81 if start_no > end_no:
56 raise ValueError('start no should be less than end no') 82 raise ValueError('start no should be less than end no')
57 83
58 start_slot = max(0, self.get_time_slot(start)) 84 start_slot = self.get_time_slot(start)
59 end_slot = min(len(self.array), self.get_time_slot(end) + 2) 85 end_slot = self.get_time_slot(end) + 2
60 86
61 start_no = max(0, start_no) 87 start_no = max(0, start_no)
62 end_no = min(self.list_sizes[list_type] - 1, end_no) 88 end_no = min(self.list_sizes[list_type] - 1, end_no)
63 89
64 for slot in range(start_slot, end_slot): 90 for no in range(start_no, end_no + 1):
65 for no in range(start_no, end_no + 1): 91 for type in event_types:
66 for type in event_types: 92 for slot in range(start_slot, end_slot):
67 if type in self.array[slot][list_type][no]: 93 if slot in self.array[list_type][no][type]:
68 yield self.array[slot][list_type][no][type] 94 for event in self.array[list_type][no][type][slot]:
95 yield event
69 96
70class Schedule(object): 97class Schedule(object):
71 """The total schedule (task system), consisting of a certain number of 98 """The total schedule (task system), consisting of a certain number of
@@ -75,36 +102,47 @@ class Schedule(object):
75 self.name = name 102 self.name = name
76 self.tasks = {} 103 self.tasks = {}
77 self.task_list = [] 104 self.task_list = []
105 self.selected = {}
78 self.time_slot_array = None 106 self.time_slot_array = None
79 self.cur_task_no = 0 107 self.cur_task_no = 0
80 self.num_cpus = num_cpus 108 self.num_cpus = num_cpus
81 for task in task_list: 109 for task in task_list:
82 self.add_task(task) 110 self.add_task(task)
83 111
84 def set_time_params(self, time_per_maj=None): 112 def get_selected(self):
85 if self.get_task_list() is None: 113 return self.selected
86 return (0, 0)
87 114
115 def set_selected(self, new_selected):
116 for event in self.selected:
117 event.selected = False
118 for event in new_selected:
119 event.selected = True
120 self.selected = new_selected
121
122 def get_selected(self):
123 return copy.copy(self.selected)
124
125 def set_time_params(self, time_per_maj=None, max_num_slots=None):
88 def find_extreme_time_sched(sched, cmp): 126 def find_extreme_time_sched(sched, cmp):
89 def find_extreme_time_task(task, cmp): 127 def find_extreme_time_task(task, cmp):
90 def find_extreme_time_job(job, cmp): 128 def find_extreme_time_job(job, cmp):
91 extreme_time = None 129 extreme_time = None
92 for time in job.get_events(): 130 for time in job.get_events():
93 if extreme_time is None or cmp(time, extreme_time) < 0: 131 if (extreme_time is None) or cmp(time, extreme_time) < 0:
94 extreme_time = time 132 extreme_time = time
95 return extreme_time 133 return extreme_time
96 134
97 extreme_time = None 135 extreme_time = None
98 for job_no in task.get_jobs(): 136 for job_no in task.get_jobs():
99 time = find_extreme_time_job(task.get_jobs()[job_no], cmp) 137 time = find_extreme_time_job(task.get_jobs()[job_no], cmp)
100 if time is not None and (extreme_time is None or cmp(time, extreme_time) < 0): 138 if (time is not None) and ((extreme_time is None) or cmp(time, extreme_time) < 0):
101 extreme_time = time 139 extreme_time = time
102 return extreme_time 140 return extreme_time
103 141
104 extreme_time = None 142 extreme_time = None
105 for task in sched.get_task_list(): 143 for task in sched.get_task_list():
106 time = find_extreme_time_task(task, cmp) 144 time = find_extreme_time_task(task, cmp)
107 if time is not None and (extreme_time is None or cmp(time, extreme_time) < 0): 145 if (time is not None) and ((extreme_time is None) or cmp(time, extreme_time) < 0):
108 extreme_time = time 146 extreme_time = time
109 147
110 return extreme_time 148 return extreme_time
@@ -129,20 +167,32 @@ class Schedule(object):
129 167
130 self.start = find_extreme_time_sched(self, earliest_cmp) 168 self.start = find_extreme_time_sched(self, earliest_cmp)
131 self.end = find_extreme_time_sched(self, latest_cmp) 169 self.end = find_extreme_time_sched(self, latest_cmp)
132 self.time_per_maj = time_per_maj 170 if self.start is None or self.end is None:
171 self.time_slot_array = TimeSlotArray()
172 return
173
174 min_time_per_maj = (self.end - self.start) * 1.0 / max_num_slots
175 if max_num_slots is not None and time_per_maj is not None:
176 if time_per_maj < min_time_per_maj:
177 self.time_per_maj = min_time_per_maj
178 else:
179 self.time_per_maj = time_per_maj
180 else:
181 self.time_per_maj = time_per_maj
133 self.time_slot_array = None 182 self.time_slot_array = None
134 if self.time_per_maj is not None: 183 if self.time_per_maj is not None:
135 self.time_slot_array = TimeSlotArray(self.start, self.end, time_per_maj, \ 184 self.time_slot_array = TimeSlotArray(self.start, self.end, self.time_per_maj, \
136 len(self.task_list), self.num_cpus) 185 len(self.task_list), self.num_cpus)
137 186
187
138 def get_time_slot_array(self): 188 def get_time_slot_array(self):
139 return self.time_slot_array 189 return self.time_slot_array
140 190
141 def get_time_bounds(self): 191 def get_time_bounds(self):
142 return (self.start, self.end) 192 return (self.start, self.end)
143 193
144 def scan(self, time_per_maj): 194 def scan(self, time_per_maj, max_num_slots):
145 self.set_time_params(time_per_maj) 195 self.set_time_params(time_per_maj, max_num_slots)
146 196
147 # we scan the graph task by task, and job by job 197 # we scan the graph task by task, and job by job
148 switches = {} 198 switches = {}
@@ -261,9 +311,13 @@ class DummyEvent(object):
261 def get_layer(self): 311 def get_layer(self):
262 return self.layer 312 return self.layer
263 313
264 def render(self, graph, layer, prev_events): 314 def render(self, graph, layer, prev_events, selectable=False):
265 """Method that the visualizer calls to tell the event to render itself 315 """Method that the visualizer calls to tell the event to render itself
266 Obviously only implemented by subclasses (actual event types)""" 316 Obviously only implemented by subclasses (actual event types)
317
318 ``Rendering'' can mean either actually drawing the event or just
319 adding it as a selectable region. This is controlled by the
320 ``selectable'' parameter"""
267 raise NotImplementdError 321 raise NotImplementdError
268 322
269class Event(DummyEvent): 323class Event(DummyEvent):
@@ -295,10 +349,6 @@ class Event(DummyEvent):
295 def is_selected(self): 349 def is_selected(self):
296 """Returns whether the event has been selected by the user. (needed for rendering)""" 350 """Returns whether the event has been selected by the user. (needed for rendering)"""
297 return self.selected 351 return self.selected
298
299 def set_selected(self, sel):
300 """Sets the event's state to selected."""
301 self.selected = sel
302 352
303 def scan(self, cur_cpu, switches): 353 def scan(self, cur_cpu, switches):
304 """Part of the procedure that walks through all the events and sets 354 """Part of the procedure that walks through all the events and sets
@@ -328,13 +378,16 @@ class SuspendEvent(Event):
328 print "suspending on a CPU different from the CPU we are on!" 378 print "suspending on a CPU different from the CPU we are on!"
329 super(SuspendEvent, self).scan(cur_cpu, switches) 379 super(SuspendEvent, self).scan(cur_cpu, switches)
330 380
331 def render(self, graph, layer, prev_events): 381 def render(self, graph, layer, prev_events, selectable=False):
332 if layer == self.layer: 382 if layer == self.layer:
333 prev_events[self] = None 383 prev_events[self] = None
334 graph.draw_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 384 if selectable:
335 self.get_cpu(), self.is_selected()) 385 graph.add_sel_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
336 graph.add_sel_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
337 self.get_cpu(), self) 386 self.get_cpu(), self)
387 else:
388 graph.draw_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
389 self.get_cpu(), self.is_selected())
390
338 391
339class ResumeEvent(Event): 392class ResumeEvent(Event):
340 def __init__(self, time, cpu): 393 def __init__(self, time, cpu):
@@ -350,13 +403,16 @@ class ResumeEvent(Event):
350 print "Resuming when currently scheduled on a CPU, but on a different CPU from the current CPU!" 403 print "Resuming when currently scheduled on a CPU, but on a different CPU from the current CPU!"
351 super(ResumeEvent, self).scan(cur_cpu, switches) 404 super(ResumeEvent, self).scan(cur_cpu, switches)
352 405
353 def render(self, graph, layer, prev_events): 406 def render(self, graph, layer, prev_events, selectable=False):
354 if layer == self.layer: 407 if layer == self.layer:
355 prev_events[self] = None 408 prev_events[self] = None
356 graph.draw_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 409 if selectable:
357 self.get_cpu(), self.is_selected()) 410 graph.add_sel_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
358 graph.add_sel_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
359 self.get_cpu(), self) 411 self.get_cpu(), self)
412 else:
413 graph.draw_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
414 self.get_cpu(), self.is_selected())
415
360 416
361class CompleteEvent(Event): 417class CompleteEvent(Event):
362 def __init__(self, time, cpu): 418 def __init__(self, time, cpu):
@@ -369,14 +425,15 @@ class CompleteEvent(Event):
369 def scan(self, cur_cpu, switches): 425 def scan(self, cur_cpu, switches):
370 super(CompleteEvent, self).scan(cur_cpu, switches) 426 super(CompleteEvent, self).scan(cur_cpu, switches)
371 427
372 def render(self, graph, layer, prev_events): 428 def render(self, graph, layer, prev_events, selectable=False):
373 if layer == Canvas.TOP_LAYER: 429 if layer == Canvas.TOP_LAYER:
374 prev_events[self] = None 430 prev_events[self] = None
375 graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 431 if selectable:
376 self.get_cpu(), self.is_selected()) 432 graph.add_sel_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
377 graph.add_sel_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
378 self.get_cpu(), self) 433 self.get_cpu(), self)
379 434 else:
435 graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
436 self.get_cpu(), self.is_selected())
380 437
381class SwitchAwayEvent(Event): 438class SwitchAwayEvent(Event):
382 def __init__(self, time, cpu): 439 def __init__(self, time, cpu):
@@ -412,10 +469,10 @@ class SwitchAwayEvent(Event):
412 469
413 super(SwitchAwayEvent, self).scan(cur_cpu, switches) 470 super(SwitchAwayEvent, self).scan(cur_cpu, switches)
414 471
415 def render(self, graph, layer, prev_events): 472 def render(self, graph, layer, prev_events, selectable=False):
416 if self.corresp_start_event is None or self.corresp_start_event in prev_events: 473 if self.corresp_start_event is None or self.corresp_start_event in prev_events:
417 return # erroneous switch away or already rendered 474 return # erroneous switch away or already rendered
418 self.corresp_start_event.render(graph, layer, prev_events) 475 self.corresp_start_event.render(graph, layer, prev_events, selectable)
419 476
420class SwitchToEvent(Event): 477class SwitchToEvent(Event):
421 def __init__(self, time, cpu): 478 def __init__(self, time, cpu):
@@ -442,17 +499,19 @@ class SwitchToEvent(Event):
442 499
443 super(SwitchToEvent, self).scan(cur_cpu, switches) 500 super(SwitchToEvent, self).scan(cur_cpu, switches)
444 501
445 def render(self, graph, layer, prev_events): 502 def render(self, graph, layer, prev_events, selectable=False):
446 if self.is_erroneous(): 503 if self.corresp_end_event is None:
447 return # erroneous switch to 504 return # fatally erroneous switch to
448 if layer == Canvas.BOTTOM_LAYER: 505 if layer == Canvas.BOTTOM_LAYER:
449 prev_events[self] = None 506 prev_events[self] = None
450 cpu = self.get_cpu() 507 cpu = self.get_cpu()
451 task_no = self.get_job().get_task().get_task_no() 508 task_no = self.get_job().get_task().get_task_no()
452 graph.draw_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), 509 if selectable:
453 task_no, cpu, self.get_job().get_job_no(), self.is_selected()) 510 graph.add_sel_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
454 graph.add_sel_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
455 task_no, cpu, self) 511 task_no, cpu, self)
512 else:
513 graph.draw_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
514 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
456 515
457class ReleaseEvent(Event): 516class ReleaseEvent(Event):
458 def __init__(self, time, cpu): 517 def __init__(self, time, cpu):
@@ -465,13 +524,16 @@ class ReleaseEvent(Event):
465 def scan(self, cur_cpu, switches): 524 def scan(self, cur_cpu, switches):
466 super(ReleaseEvent, self).scan(cur_cpu, switches) 525 super(ReleaseEvent, self).scan(cur_cpu, switches)
467 526
468 def render(self, graph, layer, prev_events): 527 def render(self, graph, layer, prev_events, selectable=False):
469 prev_events[self] = None 528 prev_events[self] = None
470 if layer == Canvas.TOP_LAYER: 529 if layer == Canvas.TOP_LAYER:
471 graph.draw_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 530 if selectable:
531 graph.add_sel_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
532 self)
533 else:
534 graph.draw_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
472 self.get_job().get_job_no(), self.is_selected()) 535 self.get_job().get_job_no(), self.is_selected())
473 graph.add_sel_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 536
474 self)
475 537
476class DeadlineEvent(Event): 538class DeadlineEvent(Event):
477 def __init__(self, time, cpu): 539 def __init__(self, time, cpu):
@@ -484,13 +546,16 @@ class DeadlineEvent(Event):
484 def scan(self, cur_cpu, switches): 546 def scan(self, cur_cpu, switches):
485 super(DeadlineEvent, self).scan(cur_cpu, switches) 547 super(DeadlineEvent, self).scan(cur_cpu, switches)
486 548
487 def render(self, graph, layer, prev_events): 549 def render(self, graph, layer, prev_events, selectable=False):
488 prev_events[self] = None 550 prev_events[self] = None
489 if layer == Canvas.TOP_LAYER: 551 if layer == Canvas.TOP_LAYER:
490 graph.draw_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 552 if selectable:
491 self.get_job().get_job_no(), self.is_selected()) 553 graph.add_sel_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
492 graph.add_sel_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
493 self) 554 self)
555 else:
556 graph.draw_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
557 self.get_job().get_job_no(), self.is_selected())
558
494 559
495class InversionStartEvent(ErrorEvent): 560class InversionStartEvent(ErrorEvent):
496 def __init__(self, time): 561 def __init__(self, time):
@@ -512,15 +577,18 @@ class InversionStartEvent(ErrorEvent):
512 # the corresp_end_event should already be set 577 # the corresp_end_event should already be set
513 super(InversionStartEvent, self).scan(cur_cpu, switches) 578 super(InversionStartEvent, self).scan(cur_cpu, switches)
514 579
515 def render(self, graph, layer, prev_events): 580 def render(self, graph, layer, prev_events, selectable=False):
516 if layer == Canvas.BOTTOM_LAYER: 581 if layer == Canvas.BOTTOM_LAYER:
517 prev_events[self] = None 582 prev_events[self] = None
518 cpu = self.get_cpu() 583 cpu = self.get_cpu()
519 task_no = self.get_job().get_task().get_task_no() 584 task_no = self.get_job().get_task().get_task_no()
520 graph.draw_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), 585 if selectable:
521 task_no, cpu, self.get_job().get_job_no(), self.is_selected()) 586 graph.add_sel_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
522 graph.add_sel_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
523 task_no, cpu, self) 587 task_no, cpu, self)
588 else:
589 graph.draw_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
590 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
591
524 592
525class InversionEndEvent(ErrorEvent): 593class InversionEndEvent(ErrorEvent):
526 def __init__(self, time): 594 def __init__(self, time):
@@ -549,23 +617,25 @@ class InversionEndEvent(ErrorEvent):
549 617
550 super(InversionEndEvent, self).scan(cur_cpu, switches) 618 super(InversionEndEvent, self).scan(cur_cpu, switches)
551 619
552 def render(self, graph, layer, prev_events): 620 def render(self, graph, layer, prev_events, selectable=False):
553 if self.corresp_start_event is None or self.corresp_start_event in prev_events: 621 if self.corresp_start_event is None or self.corresp_start_event in prev_events:
554 return # erroneous inversion end or already rendered 622 return # erroneous inversion end or already rendered
555 self.corresp_start_event.render(graph, layer, prev_events) 623 self.corresp_start_event.render(graph, layer, prev_events, selectable)
556 624
557class InversionDummy(DummyEvent): 625class InversionDummy(DummyEvent):
558 def render(self, graph, layer, prev_events): 626 def render(self, graph, layer, prev_events, selectable=False):
559 if self.corresp_start_event in prev_events: 627 if self.corresp_start_event in prev_events:
560 return # we have already been rendered 628 return # we have already been rendered
561 self.corresp_start_event.render(graph, layer, prev_events) 629 self.corresp_start_event.render(graph, layer, prev_events, selectable)
562 630
563class IsRunningDummy(DummyEvent): 631class IsRunningDummy(DummyEvent):
564 def render(self, graph, layer, prev_events): 632 def render(self, graph, layer, prev_events, selectable=False):
565 if self.corresp_start_event in prev_events: 633 if self.corresp_start_event in prev_events:
566 return # we have already been rendered 634 return # we have already been rendered
567 self.corresp_start_event.render(graph, layer, prev_events) 635 self.corresp_start_event.render(graph, layer, prev_events, selectable)
568 636
569EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, SwitchAwayEvent : None, 637EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None,
570 SwitchToEvent : None, ReleaseEvent : None, DeadlineEvent : None, IsRunningDummy : None, 638 SwitchAwayEvent : None, SwitchToEvent : None, ReleaseEvent : None,
571 InversionStartEvent : None, InversionEndEvent : None, InversionDummy : None} 639 DeadlineEvent : None, IsRunningDummy : None,
640 InversionStartEvent : None, InversionEndEvent : None,
641 InversionDummy : None}
diff --git a/unit_trace/viz/trace_reader.py b/unit_trace/viz/trace_reader.py
index a4ff964..831a06e 100644
--- a/unit_trace/viz/trace_reader.py
+++ b/unit_trace/viz/trace_reader.py
@@ -27,7 +27,12 @@
27 27
28import struct 28import struct
29 29
30 30###############################################################################
31# Class definitions
32###############################################################################
33class InvalidRecordError(Exception):
34 pass
35
31############################################################################### 36###############################################################################
32# Public functions 37# Public functions
33############################################################################### 38###############################################################################
@@ -49,15 +54,19 @@ def trace_reader(files):
49 for file in files: 54 for file in files:
50 file_iter = _get_file_iter(file) 55 file_iter = _get_file_iter(file)
51 file_iters.append(file_iter) 56 file_iters.append(file_iter)
52 file_iter_buff.append([file_iter.next()]) 57 file_iter_buff.append([])
53 58
54 # We keep 100 records in each buffer and then keep the buffer sorted 59 # We keep 100 records in each buffer and then keep the buffer sorted
55 # This is because records may have been recorded slightly out of order 60 # This is because records may have been recorded slightly out of order
56 # This cannot guarantee records are produced in order, but it makes it 61 # This cannot guarantee records are produced in order, but it makes it
57 # overwhelmingly probably. 62 # overwhelmingly probably.
58 for x in range(0,len(file_iter_buff)): 63 for x in range(0,len(file_iter_buff)):
59 for y in range(0,100): 64 for y in range(0,100):
60 file_iter_buff[x].append(file_iters[x].next()) 65 try:
66 file_iter_buff[x].append(file_iters[x].next())
67 except StopIteration:
68 break
69
61 for x in range(0,len(file_iter_buff)): 70 for x in range(0,len(file_iter_buff)):
62 file_iter_buff[x] = sorted(file_iter_buff[x],key=lambda rec: rec.when) 71 file_iter_buff[x] = sorted(file_iter_buff[x],key=lambda rec: rec.when)
63 72
@@ -96,7 +105,7 @@ def trace_reader(files):
96 105
97 # Check for monotonically increasing time 106 # Check for monotonically increasing time
98 if last_time is not None and earliest.when < last_time: 107 if last_time is not None and earliest.when < last_time:
99 exit("FATAL ERROR: trace_reader.py: out-of-order record produced") 108 raise InvalidRecordError("out-of-order record produced")
100 else: 109 else:
101 last_time = earliest.when 110 last_time = earliest.when
102 111
@@ -112,10 +121,12 @@ def _get_file_iter(file):
112 f = open(file,'rb') 121 f = open(file,'rb')
113 while True: 122 while True:
114 data = f.read(RECORD_HEAD_SIZE) 123 data = f.read(RECORD_HEAD_SIZE)
124 if data == '':
125 break
115 try: 126 try:
116 type_num = struct.unpack_from('b',data)[0] 127 type_num = struct.unpack_from('b',data)[0]
117 except struct.error: 128 except struct.error:
118 break #We read to the end of the file 129 raise InvalidRecordError("Invalid record detected, stopping.")
119 type = _get_type(type_num) 130 type = _get_type(type_num)
120 try: 131 try:
121 values = struct.unpack_from(StHeader.format + 132 values = struct.unpack_from(StHeader.format +
@@ -123,8 +134,7 @@ def _get_file_iter(file):
123 record_dict = dict(zip(type.keys,values)) 134 record_dict = dict(zip(type.keys,values))
124 except struct.error: 135 except struct.error:
125 f.close() 136 f.close()
126 print "Invalid record detected, stopping." 137 raise InvalidRecordError("Invalid record detected, stopping.")
127 exit()
128 138
129 # Convert the record_dict into an object 139 # Convert the record_dict into an object
130 record = _dict2obj(record_dict) 140 record = _dict2obj(record_dict)
@@ -139,7 +149,7 @@ def _get_file_iter(file):
139 # If there is no timestamp, set the time to 0 149 # If there is no timestamp, set the time to 0
140 if 'when' not in record.__dict__.keys(): 150 if 'when' not in record.__dict__.keys():
141 record.when = 0 151 record.when = 0
142 152
143 yield record 153 yield record
144 154
145# Convert a dict into an object 155# Convert a dict into an object
@@ -235,6 +245,8 @@ def _get_type(type_num):
235 types = [None,StNameData,StParamData,StReleaseData,StAssignedData, 245 types = [None,StNameData,StParamData,StReleaseData,StAssignedData,
236 StSwitchToData,StSwitchAwayData,StCompletionData,StBlockData, 246 StSwitchToData,StSwitchAwayData,StCompletionData,StBlockData,
237 StResumeData,StSysReleaseData] 247 StResumeData,StSysReleaseData]
248 if type_num >= len(types) or type_num < 0:
249 raise InvalidRecordError("Invalid record detected, stopping.")
238 return types[type_num] 250 return types[type_num]
239 251
240# Return the type name, given the type_num (this is simply a convenience to 252# Return the type name, given the type_num (this is simply a convenience to
@@ -242,4 +254,6 @@ def _get_type(type_num):
242def _get_type_name(type_num): 254def _get_type_name(type_num):
243 type_names = [None,"name","params","release","assign","switch_to", 255 type_names = [None,"name","params","release","assign","switch_to",
244 "switch_away","completion","block","resume","sys_release"] 256 "switch_away","completion","block","resume","sys_release"]
257 if type_num >= len(type_names) or type_num < 0:
258 raise InvalidRecordError("Invalid record detected, stopping.")
245 return type_names[type_num] 259 return type_names[type_num]
diff --git a/unit_trace/viz/viewer.py b/unit_trace/viz/viewer.py
index a695473..ee5c230 100644
--- a/unit_trace/viz/viewer.py
+++ b/unit_trace/viz/viewer.py
@@ -3,20 +3,36 @@
3"""GUI stuff.""" 3"""GUI stuff."""
4 4
5from schedule import * 5from schedule import *
6
6from renderer import * 7from renderer import *
7 8
8import pygtk 9import pygtk
9import gtk 10import gtk
10import gobject 11import gobject
12
13class GraphContextMenu(gtk.Menu):
14 MAX_STR_LEN = 80
11 15
16 def __init__(self, selected):
17 super(GraphContextMenu, self).__init__()
18
19 for event in selected:
20 string = str(event)
21 if len(string) > GraphContextMenu.MAX_STR_LEN - 3:
22 string = string[:GraphContextMenu.MAX_STR_LEN - 3] + '...'
23 item = gtk.MenuItem(string)
24 self.append(item)
25 item.show()
26
12class GraphArea(gtk.DrawingArea): 27class GraphArea(gtk.DrawingArea):
13 DAREA_WIDTH_REQ = 500 28 HORIZ_PAGE_SCROLL_FACTOR = 10.8
14 DAREA_HEIGHT_REQ = 300
15 HORIZ_PAGE_SCROLL_FACTOR = 4.8
16 HORIZ_STEP_SCROLL_FACTOR = 0.8 29 HORIZ_STEP_SCROLL_FACTOR = 0.8
17 VERT_PAGE_SCROLL_FACTOR = 3.0 30 VERT_PAGE_SCROLL_FACTOR = 3.0
18 VERT_STEP_SCROLL_FACTOR = 0.5 31 VERT_STEP_SCROLL_FACTOR = 0.5
19 32
33 SELECT_THICKNESS = 1.5
34 SELECT_COLOR = (0.85, 0.0, 0.0)
35
20 def __init__(self, renderer): 36 def __init__(self, renderer):
21 super(GraphArea, self).__init__() 37 super(GraphArea, self).__init__()
22 38
@@ -27,26 +43,45 @@ class GraphArea(gtk.DrawingArea):
27 self.width = 0 43 self.width = 0
28 self.height = 0 44 self.height = 0
29 45
30 self.now_selected = []
31
32 self.set_set_scroll_adjustments_signal('set-scroll-adjustments') 46 self.set_set_scroll_adjustments_signal('set-scroll-adjustments')
33 47
34 self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK) 48 self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK |
49 gtk.gdk.BUTTON_RELEASE_MASK)
50
51 self.left_button_start_coor = None
52 self.select_rect = None
53 self.ctrl_clicked = False
35 54
36 self.connect('expose-event', self.expose) 55 self.connect('expose-event', self.expose)
37 self.connect('size-allocate', self.size_allocate) 56 self.connect('size-allocate', self.size_allocate)
38 self.connect('set-scroll-adjustments', self.set_scroll_adjustments) 57 self.connect('set-scroll-adjustments', self.set_scroll_adjustments)
39 self.connect('button-press-event', self.button_press) 58 self.connect('button-press-event', self.button_press)
59 self.connect('button-release-event', self.button_release)
40 self.connect('motion-notify-event', self.motion_notify) 60 self.connect('motion-notify-event', self.motion_notify)
41 61
42 self.set_size_request(GraphArea.DAREA_WIDTH_REQ, GraphArea.DAREA_HEIGHT_REQ)
43
44 def expose(self, widget, event, data=None): 62 def expose(self, widget, event, data=None):
45 ctx = widget.window.cairo_create() 63 ctx = widget.window.cairo_create()
46 graph = self.renderer.get_graph() 64 graph = self.renderer.get_graph()
47 graph.update_view(self.cur_x, self.cur_y, self.width, self.height, ctx) 65 graph.update_view(self.cur_x, self.cur_y, self.width, self.height, ctx)
48 graph.render_surface(self.renderer.get_schedule()) 66 graph.render_surface(self.renderer.get_schedule(), 0, 0, self.width, self.height)
49 67
68 # render dragging rectangle, if there is one
69 if self.select_rect is not None:
70 x, y, width, height = self.select_rect
71 thickness = GraphArea.SELECT_THICKNESS
72 color = GraphArea.SELECT_COLOR
73
74 ctx.rectangle(x, y, width, height)
75 ctx.set_line_width(thickness)
76 ctx.set_source_rgb(color[0], color[1], color[2])
77 ctx.stroke()
78
79 def get_renderer(self):
80 return self.renderer
81
82 def get_graph(self):
83 return self.renderer.get_graph()
84
50 def set_scroll_adjustments(self, widget, horizontal, vertical, data=None): 85 def set_scroll_adjustments(self, widget, horizontal, vertical, data=None):
51 graph = self.renderer.get_graph() 86 graph = self.renderer.get_graph()
52 width = graph.get_width() 87 width = graph.get_width()
@@ -55,9 +90,11 @@ class GraphArea(gtk.DrawingArea):
55 self.horizontal = horizontal 90 self.horizontal = horizontal
56 self.vertical = vertical 91 self.vertical = vertical
57 self.config_scrollbars(self.cur_x, self.cur_y) 92 self.config_scrollbars(self.cur_x, self.cur_y)
58 93
59 self.horizontal.connect('value-changed', self.horizontal_value_changed) 94 if self.horizontal is not None:
60 self.vertical.connect('value-changed', self.vertical_value_changed) 95 self.horizontal.connect('value-changed', self.horizontal_value_changed)
96 if self.vertical is not None:
97 self.vertical.connect('value-changed', self.vertical_value_changed)
61 98
62 def horizontal_value_changed(self, adjustment): 99 def horizontal_value_changed(self, adjustment):
63 self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) 100 self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width())
@@ -85,7 +122,7 @@ class GraphArea(gtk.DrawingArea):
85 122
86 if self.horizontal is not None: 123 if self.horizontal is not None:
87 self.horizontal.set_all(hvalue, 0.0, width, graph.get_attrs().maj_sep * GraphArea.HORIZ_STEP_SCROLL_FACTOR, 124 self.horizontal.set_all(hvalue, 0.0, width, graph.get_attrs().maj_sep * GraphArea.HORIZ_STEP_SCROLL_FACTOR,
88 graph.get_attrs().maj_sep * GraphArea.HORIZ_PAGE_SCROLL_FACTOR, self.width) 125 graph.get_attrs().maj_sep * GraphArea.HORIZ_PAGE_SCROLL_FACTOR, self.width)
89 if self.vertical is not None: 126 if self.vertical is not None:
90 self.vertical.set_all(vvalue, 0.0, height, graph.get_attrs().y_item_size * GraphArea.VERT_STEP_SCROLL_FACTOR, 127 self.vertical.set_all(vvalue, 0.0, height, graph.get_attrs().y_item_size * GraphArea.VERT_STEP_SCROLL_FACTOR,
91 graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR, self.height) 128 graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR, self.height)
@@ -101,7 +138,9 @@ class GraphArea(gtk.DrawingArea):
101 msg = None 138 msg = None
102 139
103 graph = self.renderer.get_graph() 140 graph = self.renderer.get_graph()
104 just_selected = graph.get_selected_regions(motion_event.x, motion_event.y) 141
142 graph.render_surface(self.renderer.get_schedule(), motion_event.x, motion_event.y, 0, 0, True)
143 just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0)
105 if not just_selected: 144 if not just_selected:
106 msg = '' 145 msg = ''
107 the_event = None 146 the_event = None
@@ -116,78 +155,270 @@ class GraphArea(gtk.DrawingArea):
116 msg = str(the_event) 155 msg = str(the_event)
117 156
118 self.emit('update-event-description', the_event, msg) 157 self.emit('update-event-description', the_event, msg)
158
159 if self.left_button_start_coor is not None:
160 selected = {}
161 if self.ctrl_clicked:
162 selected = dict(self.last_selected)
163
164 # dragging a rectangle
165 x = min(self.left_button_start_coor[0], motion_event.x)
166 y = min(self.left_button_start_coor[1], motion_event.y)
167 width = abs(self.left_button_start_coor[0] - motion_event.x)
168 height = abs(self.left_button_start_coor[1] - motion_event.y)
169
170 graph.render_surface(self.renderer.get_schedule(), x, y, width, height, True)
171 selected.update(graph.get_selected_regions(x, y, width, height))
172 self.renderer.get_schedule().set_selected(selected)
173
174 self.select_rect = (x, y, width, height)
119 175
176 self._dirty()
177
120 def button_press(self, widget, button_event, data=None): 178 def button_press(self, widget, button_event, data=None):
121 graph = self.renderer.get_graph() 179 graph = self.renderer.get_graph()
122 180
181 self.ctrl_clicked = button_event.state & gtk.gdk.CONTROL_MASK
182
123 if button_event.button == 1: 183 if button_event.button == 1:
124 just_selected = graph.get_selected_regions(button_event.x, button_event.y) 184 self.left_button_start_coor = (button_event.x, button_event.y)
185 graph.render_surface(self.renderer.get_schedule(), \
186 button_event.x, button_event.y, 0, 0, True)
187
188 just_selected = graph.get_selected_regions(button_event.x, button_event.y, 0, 0)
125 189
126 max_layer = self._find_max_layer(just_selected) 190 max_layer = self._find_max_layer(just_selected)
127 191
192 new_now_selected = None
193 if self.ctrl_clicked:
194 new_now_selected = self.renderer.get_schedule().get_selected()
195 else:
196 new_now_selected = {}
197
128 # only select those events which were in the top layer (it's 198 # only select those events which were in the top layer (it's
129 # not intuitive to click something and then have something 199 # not intuitive to click something and then have something
130 # below it get selected). Also, clicking something that 200 # below it get selected). Also, clicking something that
131 # is selected deselects it 201 # is selected deselects it, if it's the only thing selected
132 new_now_selected = {}
133 for event in just_selected: 202 for event in just_selected:
134 if event.get_layer() == max_layer: 203 if event.get_layer() == max_layer:
135 if not event.is_selected(): 204 val = True
205 now_selected = self.renderer.get_schedule().get_selected()
206 if (len(now_selected) == 1 or self.ctrl_clicked) and event in now_selected:
207 val = not event.is_selected
208 if val:
136 new_now_selected[event] = None 209 new_now_selected[event] = None
137 event.set_selected(not event.is_selected()) 210 elif event in new_now_selected:
138 break 211 del new_now_selected[event]
212 break # only pick one event when just clicking
139 213
140 for event in self.now_selected: 214 self.renderer.get_schedule().set_selected(new_now_selected)
141 if event not in new_now_selected: 215 self.last_selected = dict(new_now_selected)
142 event.set_selected(False)
143
144 self.now_selected = new_now_selected
145 216
146 rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) 217 self._dirty()
147 self.window.invalidate_rect(rect, True) 218 elif button_event.button == 3:
148 219 self._release_band()
220 self.emit('request-context-menu', button_event, self.renderer.get_schedule().get_selected())
221
222 def button_release(self, widget, button_event, data=None):
223 self.ctrl_clicked = False
224 self.last_selected = dict(self.renderer.get_schedule().get_selected())
225
226 if button_event.button == 1:
227 self._release_band()
228
229 def _release_band(self):
230 old_select_rect = self.select_rect
231 self.left_button_start_coor = None
232 self.select_rect = None
233 if old_select_rect is not None:
234 self._dirty()
235
236 def _dirty(self, x=None, y=None, width=None, height=None):
237 if x is None:
238 x = 0
239 if y is None:
240 y = 0
241 if width is None:
242 width = self.width
243 if height is None:
244 height = self.height
245 rect = gtk.gdk.Rectangle(x, y, width, height)
246 self.window.invalidate_rect(rect, True)
247
149class GraphWindow(gtk.ScrolledWindow): 248class GraphWindow(gtk.ScrolledWindow):
150 def __init__(self, renderer): 249 def __init__(self, renderer):
151 super(GraphWindow, self).__init__(None, None) 250 super(GraphWindow, self).__init__(None, None)
152 251
252 self.add_events(gtk.gdk.KEY_PRESS_MASK)
253
254 self.ctr = 0
255 self.connect('key-press-event', self.key_press)
256
153 self.garea = GraphArea(renderer) 257 self.garea = GraphArea(renderer)
154 self.add(self.garea) 258 self.add(self.garea)
155 self.garea.show() 259 self.garea.show()
156 260
261 def key_press(self, widget, key_event):
262 hadj = self.get_hadjustment()
263 vadj = self.get_vadjustment()
264 if hadj is None or vadj is None:
265 return
266
267 hupper = hadj.get_upper()
268 hlower = hadj.get_lower()
269 hpincr = hadj.get_page_increment()
270 hsincr = hadj.get_step_increment()
271 hpsize = hadj.get_page_size()
272 hval = hadj.get_value()
273 vupper = vadj.get_upper()
274 vlower = vadj.get_lower()
275 vval = vadj.get_value()
276 vpincr = vadj.get_page_increment()
277 vsincr = vadj.get_step_increment()
278 vpsize = vadj.get_page_size()
279
280 ctrl_clicked = key_event.state & gtk.gdk.CONTROL_MASK
281
282 adj_tuple = {'up' : (vadj, -vsincr, 0, vval, max),
283 'ctrl-up' : (vadj, -vpincr, 0, vval, max),
284 'down' : (vadj, vsincr, vupper - vpsize, vval, min),
285 'ctrl-down' : (vadj, vpincr, vupper - vpsize, vval, min),
286 'left' : (hadj, -hsincr, 0, hval, max),
287 'ctrl-left' : (hadj, -hpincr, 0, hval, max),
288 'right' : (hadj, hsincr, hupper - hpsize, hval, min),
289 'ctrl-right' : (hadj, hpincr, hupper - hpsize, hval, min)}
290
291 keystr = None
292 keymap = {gtk.keysyms.Up : 'up', gtk.keysyms.Down : 'down',
293 gtk.keysyms.Left : 'left', gtk.keysyms.Right : 'right'}
294 if key_event.keyval in keymap:
295 keystr = keymap[key_event.keyval]
296
297 if ctrl_clicked:
298 keystr = 'ctrl-' + keystr
299
300 if keystr is not None:
301 adj, inc, lim, val, extr = adj_tuple[keystr]
302 adj.set_value(extr(val + inc, lim))
303
304 return True
305
157 def get_graph_area(self): 306 def get_graph_area(self):
158 return self.garea 307 return self.garea
159 308
160class MainWindow(object): 309class MainWindow(gtk.Window):
161 def __init__(self, renderer): 310 WINDOW_WIDTH_REQ = 500
162 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 311 WINDOW_HEIGHT_REQ = 300
312
313 def __init__(self, request_renderer_change):
314 super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL)
163 315
164 self.window.connect('delete_event', self.delete_event) 316 self.connect('delete_event', self.delete_event)
165 self.window.connect('destroy', self.destroy) 317 self.connect('destroy', self.die)
318
319 self.connect('request-renderer-change', request_renderer_change)
320
321 self.file_menu = gtk.Menu()
322 self.open_item = gtk.MenuItem('_Open', True)
323 self.open_item.connect('activate', self.open_item_activate)
324 self.file_menu.append(self.open_item)
325 self.open_item.show()
326
327 self.quit_item = gtk.MenuItem('_Quit', True)
328 self.quit_item.connect('activate', self.quit_item_activate)
329 self.quit_item.show()
330 self.file_menu.append(self.quit_item)
331
332 self.file_item = gtk.MenuItem('_File', True)
333 self.file_item.set_submenu(self.file_menu)
334 self.file_item.show()
335
336 self.menu_bar = gtk.MenuBar()
337 self.menu_bar.append(self.file_item)
338
339 self.menu_bar.show()
166 340
167 self.vbox = gtk.VBox(False, 0) 341 self.vbox = gtk.VBox(False, 0)
168 342
169 self.gwindow = GraphWindow(renderer) 343 self.notebook = gtk.Notebook()
170 self.gwindow.get_graph_area().connect('update-event-description', 344
171 self.update_event_description) 345 self.notebook.last_page = -1
172 self.gwindow.show() 346 self.notebook.connect('switch-page', self.switch_page)
347
348 self.notebook.show()
173 349
174 self.desc_label = gtk.Label('') 350 self.desc_label = gtk.Label('')
175 self.desc_label.set_justify(gtk.JUSTIFY_LEFT) 351 self.desc_label.set_justify(gtk.JUSTIFY_LEFT)
176 self.desc_label.show() 352 self.desc_label.show()
177 353
178 self.vbox.pack_start(self.gwindow, True, True, 0) 354 self.vbox.pack_start(self.menu_bar, False, False, 0)
355 self.vbox.pack_start(self.notebook, True, True, 0)
179 self.vbox.pack_start(self.desc_label, False, False, 0) 356 self.vbox.pack_start(self.desc_label, False, False, 0)
180 self.vbox.show() 357 self.vbox.show()
181 358
182 self.window.add(self.vbox) 359 self.add(self.vbox)
183 self.window.show() 360
184 361 self.set_size_request(MainWindow.WINDOW_WIDTH_REQ, MainWindow.WINDOW_HEIGHT_REQ)
362
363 self.show()
364
365 def connect_widgets(self, garea):
366 garea.connect('update-event-description', self.update_event_description)
367 garea.connect('request-context-menu', self.request_context_menu)
368
369 def set_renderers(self, renderers):
370 for i in range(0, self.notebook.get_n_pages()):
371 self.notebook.remove_page(0)
372 for title in renderers:
373 gwindow = GraphWindow(renderers[title])
374 self.connect_widgets(gwindow.get_graph_area())
375 gwindow.show()
376 self.notebook.append_page(gwindow, gtk.Label(title))
377
378 def switch_page(self, widget, page, page_num):
379 if self.notebook.get_nth_page(self.notebook.last_page) is not None:
380 hadj = self.notebook.get_nth_page(self.notebook.last_page).get_hadjustment()
381 old_value = hadj.get_value()
382 old_ofs = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area().get_graph().get_origin()[0]
383 new_ofs = self.notebook.get_nth_page(page_num).get_graph_area().get_graph().get_origin()[0]
384 new_value = old_value - old_ofs + new_ofs
385 hadj.set_value(new_value)
386
387 self.notebook.last_page = page_num
388
185 def update_event_description(self, widget, event, msg): 389 def update_event_description(self, widget, event, msg):
186 self.desc_label.set_text(msg) 390 self.desc_label.set_text(msg)
187 391
392 def request_context_menu(self, widget, gdk_event, selected):
393 button = 0
394 if hasattr(gdk_event, 'button'):
395 button = gdk_event.button
396 time = gdk_event.time
397
398 menu = GraphContextMenu(selected)
399 menu.popup(None, None, None, button, time)
400
401 def open_item_activate(self, widget):
402 dialog = gtk.FileChooserDialog('Open File', self,
403 gtk.FILE_CHOOSER_ACTION_OPEN,
404 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
405 gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
406 dialog.set_select_multiple(True)
407
408 if dialog.run() == gtk.RESPONSE_ACCEPT:
409 filenames = dialog.get_filenames()
410 dialog.destroy()
411 self.emit('request-renderer-change', filenames, None)
412 else:
413 dialog.destroy()
414
415
416 def quit_item_activate(self, widget):
417 self.destroy()
418
188 def delete_event(self, widget, event, data=None): 419 def delete_event(self, widget, event, data=None):
189 return False 420 return False
190 421
191 def destroy(self, widget, data=None): 422 def die(self, widget, data=None):
192 gtk.main_quit() 423 gtk.main_quit()
193 424
diff --git a/visualizer.py b/visualizer.py
new file mode 100755
index 0000000..43d74a8
--- /dev/null
+++ b/visualizer.py
@@ -0,0 +1,38 @@
1#!/usr/bin/python
2
3"""Runs the visualizer."""
4
5import convert
6import reader
7import viz
8
9import gtk
10
11TIME_PER_MAJ = 10000000
12MAX_NUM_SLOTS = 10000
13
14def request_renderer_change(widget, file_list, params):
15 try:
16 stream = reader.trace_reader.trace_reader(file_list)
17 #stream = reader.sanitizer.sanitizer(stream)
18 #stream = reader.gedf_test.gedf_test(stream)
19 sched = convert.convert_trace_to_schedule(stream)
20 except reader.trace_reader.InvalidRecordError, e:
21 dialog = gtk.MessageDialog(widget, gtk.DIALOG_DESTROY_WITH_PARENT,
22 gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, str(e))
23 dialog.run()
24 dialog.destroy()
25 return
26
27 sched.scan(TIME_PER_MAJ, MAX_NUM_SLOTS)
28
29 task_renderer = viz.renderer.Renderer(sched)
30 task_renderer.prepare_task_graph(attrs=viz.format.GraphFormat(time_per_maj=TIME_PER_MAJ))
31 cpu_renderer = viz.renderer.Renderer(sched)
32 cpu_renderer.prepare_cpu_graph(attrs=viz.format.GraphFormat(time_per_maj=TIME_PER_MAJ))
33 widget.set_renderers({'Tasks' : task_renderer, 'CPUs' : cpu_renderer})
34
35if __name__ == '__main__':
36 window = viz.viewer.MainWindow(request_renderer_change)
37 gtk.main()
38