diff options
author | Gary Bressler <garybressler@nc.rr.com> | 2010-03-09 13:33:54 -0500 |
---|---|---|
committer | Gary Bressler <garybressler@nc.rr.com> | 2010-03-09 13:33:54 -0500 |
commit | 883f9dfe38ab081025220aafbf47f722b540d003 (patch) | |
tree | 6ab638add46bfd8e622301c2d704b3d6dfdba9b5 | |
parent | b97f0447b302746ab054aba0fd7417224ba73d86 (diff) |
Preliminary, fairly workable version of unit_trace, including the visualizer.
-rw-r--r-- | README | 106 | ||||
-rw-r--r-- | convert.py | 2 | ||||
-rw-r--r-- | gedf_test.py | 1 | ||||
-rw-r--r-- | gedf_test.py~ | 1 | ||||
-rwxr-xr-x | reader/sample_script.py | 8 | ||||
-rw-r--r-- | reader/trace_reader.py | 32 | ||||
-rwxr-xr-x | visualizer.py | 41 | ||||
-rw-r--r-- | viz/__init__.py | 8 | ||||
-rw-r--r-- | viz/draw.py | 138 | ||||
-rw-r--r-- | viz/format.py | 2 | ||||
-rw-r--r-- | viz/schedule.py | 210 | ||||
-rw-r--r-- | viz/viewer.py | 253 |
12 files changed, 600 insertions, 202 deletions
@@ -1 +1,105 @@ | |||
1 | See the LITMUS Wiki page for an explanation of this tool. | 1 | See the LITMUS Wiki page for a general explanation of this tool. |
2 | |||
3 | unit_trace consists of two modules and a core. The ``core'' is basically | ||
4 | a bunch of code, implemented as Python iterators, which converts the | ||
5 | raw trace data into a sequence of record objects, implemented in | ||
6 | Python. The modules are: | ||
7 | |||
8 | 1) A simple module that outputs the contents of each record to | ||
9 | stdout. This module, along with most of the core, can be found in the | ||
10 | reader/ directory. There is a sample script -- look at | ||
11 | sample_script.py in the reader/ directory (it's pretty | ||
12 | self-explanatory). Note that Mac is the one who coded most of the | ||
13 | this, though I can probably try to answer any questions about it since | ||
14 | I've had to go in there from time to time. | ||
15 | |||
16 | 2) The visualizer. Now, the GUI as it stands is very basic -- it's | ||
17 | basically just a shell for the core visualizer component. How to open | ||
18 | a file is obvious -- but note that you can open several files at a | ||
19 | time (since more often than not a trace consists of more than one | ||
20 | file, typically one for each CPU). | ||
21 | |||
22 | Most of the code for this is in the viz/ directory, but to run it, the | ||
23 | file you want to execute is visualizer.py (in the main directory). | ||
24 | |||
25 | A 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 | |||
55 | But 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 | |||
85 | How to install: | ||
86 | |||
87 | You should type | ||
88 | |||
89 | git clone -b wip-gary ssh://cvs.cs.unc.edu/cvs/proj/litmus/repo/unit-trace.git | ||
90 | |||
91 | to check out the repository. Note that you shouldn't check out the | ||
92 | master branch, as it's pretty outdated. It's all Python so far, so no | ||
93 | compiling or anything like that is necessary. | ||
94 | |||
95 | Requirements: | ||
96 | |||
97 | You're going to need Python 2.5 to run this. You'll also need to | ||
98 | install the pycairo and pygtk libraries. If anyone has questions about | ||
99 | how to do this or what these are, ask me. | ||
100 | |||
101 | Miscellanies: | ||
102 | |||
103 | Of course, let me know if you find any bugs (I'm sure there are | ||
104 | plenty, though, since this is fairly alpha software), if you're | ||
105 | unable to run it, or if you have any questions. \ No newline at end of file | ||
@@ -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 | ||
85 | def _pid_to_task_name(pid): | 85 | def _pid_to_task_name(pid): |
diff --git a/gedf_test.py b/gedf_test.py index 2381dad..8457901 100644 --- a/gedf_test.py +++ b/gedf_test.py | |||
@@ -7,7 +7,6 @@ | |||
7 | ############################################################################### | 7 | ############################################################################### |
8 | # Imports | 8 | # Imports |
9 | ############################################################################### | 9 | ############################################################################### |
10 | #test | ||
11 | 10 | ||
12 | import copy | 11 | import copy |
13 | 12 | ||
diff --git a/gedf_test.py~ b/gedf_test.py~ index 8457901..2381dad 100644 --- a/gedf_test.py~ +++ b/gedf_test.py~ | |||
@@ -7,6 +7,7 @@ | |||
7 | ############################################################################### | 7 | ############################################################################### |
8 | # Imports | 8 | # Imports |
9 | ############################################################################### | 9 | ############################################################################### |
10 | #test | ||
10 | 11 | ||
11 | import copy | 12 | import copy |
12 | 13 | ||
diff --git a/reader/sample_script.py b/reader/sample_script.py index f7e9297..676cfac 100755 --- a/reader/sample_script.py +++ b/reader/sample_script.py | |||
@@ -14,10 +14,10 @@ import stdout_printer | |||
14 | 14 | ||
15 | # Specify your trace files | 15 | # Specify your trace files |
16 | g6 = [ | 16 | g6 = [ |
17 | '../sample_traces/st-g6-0.bin', | 17 | '../traces/st-g6-0.bin', |
18 | '../sample_traces/st-g6-1.bin', | 18 | '../traces/st-g6-1.bin', |
19 | '../sample_traces/st-g6-2.bin', | 19 | '../traces/st-g6-2.bin', |
20 | '../sample_traces/st-g6-3.bin', | 20 | '../traces/st-g6-3.bin', |
21 | ] | 21 | ] |
22 | 22 | ||
23 | # Here is an example of a custom filter function. | 23 | # Here is an example of a custom filter function. |
diff --git a/reader/trace_reader.py b/reader/trace_reader.py index a4ff964..831a06e 100644 --- a/reader/trace_reader.py +++ b/reader/trace_reader.py | |||
@@ -27,7 +27,12 @@ | |||
27 | 27 | ||
28 | import struct | 28 | import struct |
29 | 29 | ||
30 | 30 | ############################################################################### | |
31 | # Class definitions | ||
32 | ############################################################################### | ||
33 | class 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): | |||
242 | def _get_type_name(type_num): | 254 | def _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/visualizer.py b/visualizer.py index 80c2af9..43d74a8 100755 --- a/visualizer.py +++ b/visualizer.py | |||
@@ -8,24 +8,31 @@ import viz | |||
8 | 8 | ||
9 | import gtk | 9 | import gtk |
10 | 10 | ||
11 | path = 'sample_traces/' | 11 | TIME_PER_MAJ = 10000000 |
12 | MAX_NUM_SLOTS = 10000 | ||
12 | 13 | ||
13 | trace_list = [ | 14 | def request_renderer_change(widget, file_list, params): |
14 | path + 'st-g6-0.bin', | 15 | try: |
15 | path + 'st-g6-1.bin', | 16 | stream = reader.trace_reader.trace_reader(file_list) |
16 | path + 'st-g6-2.bin', | 17 | #stream = reader.sanitizer.sanitizer(stream) |
17 | path + 'st-g6-3.bin' | 18 | #stream = reader.gedf_test.gedf_test(stream) |
18 | ] | 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 | ||
19 | 26 | ||
20 | if __name__ == '__main__': | 27 | sched.scan(TIME_PER_MAJ, MAX_NUM_SLOTS) |
21 | stream = reader.trace_reader.trace_reader(trace_list) | 28 | |
22 | stream = reader.sanitizer.sanitizer(stream) | 29 | task_renderer = viz.renderer.Renderer(sched) |
23 | stream = reader.gedf_test.gedf_test(stream) | 30 | task_renderer.prepare_task_graph(attrs=viz.format.GraphFormat(time_per_maj=TIME_PER_MAJ)) |
24 | sched = convert.convert_trace_to_schedule(stream) | 31 | cpu_renderer = viz.renderer.Renderer(sched) |
25 | sched.scan(10000000) | 32 | cpu_renderer.prepare_cpu_graph(attrs=viz.format.GraphFormat(time_per_maj=TIME_PER_MAJ)) |
26 | renderer = viz.renderer.Renderer(sched) | 33 | widget.set_renderers({'Tasks' : task_renderer, 'CPUs' : cpu_renderer}) |
27 | renderer.prepare_task_graph(attrs=viz.format.GraphFormat(time_per_maj=10000000)) | 34 | |
28 | 35 | if __name__ == '__main__': | |
29 | viz.viewer.MainWindow(renderer) | 36 | window = viz.viewer.MainWindow(request_renderer_change) |
30 | gtk.main() | 37 | gtk.main() |
31 | 38 | ||
diff --git a/viz/__init__.py b/viz/__init__.py index ef409f6..0f9bd1c 100644 --- a/viz/__init__.py +++ b/viz/__init__.py | |||
@@ -5,6 +5,10 @@ import gobject | |||
5 | import gtk | 5 | import gtk |
6 | 6 | ||
7 | gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 7 | gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, |
8 | None, (gtk.Adjustment, gtk.Adjustment)) | 8 | None, (gtk.Adjustment, gtk.Adjustment)) |
9 | gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 9 | gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, |
10 | None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) | 10 | None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) |
11 | gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | ||
12 | None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT)) | ||
13 | gobject.signal_new('request-renderer-change', viewer.MainWindow, gobject.SIGNAL_RUN_FIRST, | ||
14 | None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) | ||
diff --git a/viz/draw.py b/viz/draw.py index c3ab756..db246ec 100644 --- a/viz/draw.py +++ b/viz/draw.py | |||
@@ -84,31 +84,40 @@ class ImageSurface(Surface): | |||
84 | 84 | ||
85 | class Pattern(object): | 85 | class 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 | ||
510 | class CairoCanvas(Canvas): | 527 | class 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 | ||
685 | class Graph(object): | 702 | class 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 |
@@ -730,8 +753,8 @@ class Graph(object): | |||
730 | 753 | ||
731 | self.canvas = CanvasType(width, height, item_clist, bar_plist, surface) | 754 | self.canvas = CanvasType(width, height, item_clist, bar_plist, surface) |
732 | 755 | ||
733 | def get_selected_regions(self, real_x, real_y): | 756 | def get_selected_regions(self, real_x, real_y, width, height): |
734 | return self.canvas.get_selected_regions(real_x, real_y) | 757 | return self.canvas.get_selected_regions(real_x, real_y, width, height) |
735 | 758 | ||
736 | def get_width(self): | 759 | def get_width(self): |
737 | return self.width | 760 | return self.width |
@@ -788,12 +811,12 @@ class Graph(object): | |||
788 | def ycoor_to_item_no(self, y): | 811 | def ycoor_to_item_no(self, y): |
789 | return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size) | 812 | return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size) |
790 | 813 | ||
791 | def get_offset_params(self): | 814 | def get_offset_params(self, real_x, real_y, width, height): |
792 | start_time = self.xcoor_to_time(self.canvas.surface.virt_x) | 815 | 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) | 816 | end_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x + width) |
794 | 817 | ||
795 | start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y) | 818 | 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) | 819 | end_item = 1 + self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y + height) |
797 | 820 | ||
798 | return (start_time, end_time, start_item, end_item) | 821 | return (start_time, end_time, start_item, end_item) |
799 | 822 | ||
@@ -802,7 +825,7 @@ class Graph(object): | |||
802 | self.draw_x_axis_with_labels_at_time(start_time, end_time) | 825 | self.draw_x_axis_with_labels_at_time(start_time, end_time) |
803 | self.draw_y_axis_with_labels() | 826 | self.draw_y_axis_with_labels() |
804 | 827 | ||
805 | def render_surface(self, sched, list_type): | 828 | def render_surface(self, sched, real_x, real_y, width, height, selectable=False): |
806 | raise NotImplementedError | 829 | raise NotImplementedError |
807 | 830 | ||
808 | def render_all(self, schedule): | 831 | def render_all(self, schedule): |
@@ -919,21 +942,24 @@ class Graph(object): | |||
919 | raise NotImplementedError | 942 | raise NotImplementedError |
920 | 943 | ||
921 | class TaskGraph(Graph): | 944 | class TaskGraph(Graph): |
922 | def render_surface(self, sched): | 945 | def render_surface(self, sched, real_x, real_y, width, height, selectable=False): |
923 | self.canvas.whiteout() | 946 | if not selectable: |
924 | self.canvas.clear_selectable_regions() | 947 | self.canvas.whiteout(real_x, real_y, width, height) |
948 | else: | ||
949 | self.canvas.clear_selectable_regions(real_x, real_y, width, height) | ||
925 | 950 | ||
926 | start_time, end_time, start_item, end_item = self.get_offset_params() | 951 | start_time, end_time, start_item, end_item = self.get_offset_params(real_x, real_y, width, height) |
927 | 952 | ||
928 | self.draw_skeleton(start_time, end_time, start_item, end_item) | 953 | if not selectable: |
954 | self.draw_skeleton(start_time, end_time, start_item, end_item) | ||
929 | 955 | ||
930 | for layer in Canvas.LAYERS: | 956 | for layer in Canvas.LAYERS: |
931 | prev_events = {} | 957 | prev_events = {} |
932 | for event in sched.get_time_slot_array().iter_over_period( | 958 | for event in sched.get_time_slot_array().iter_over_period( |
933 | start_time, end_time, start_item, end_item, | 959 | start_time, end_time, start_item, end_item, |
934 | schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST): | 960 | schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST): |
935 | event.render(self, layer, prev_events) | 961 | event.render(self, layer, prev_events, selectable) |
936 | 962 | ||
937 | def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): | 963 | def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): |
938 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | 964 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR |
939 | x = self._get_time_xpos(time) | 965 | x = self._get_time_xpos(time) |
@@ -1046,7 +1072,7 @@ class TaskGraph(Graph): | |||
1046 | width = self._get_bar_width(start_time, end_time) | 1072 | width = self._get_bar_width(start_time, end_time) |
1047 | height = self._get_mini_bar_height() | 1073 | height = self._get_mini_bar_height() |
1048 | 1074 | ||
1049 | self.canvas.draw_mini_bar(x, y, width, height, cpu_no, selected) | 1075 | self.canvas.draw_mini_bar(x, y, width, height, Canvas.NULL_PATTERN, selected) |
1050 | 1076 | ||
1051 | if job_no is not None: | 1077 | if job_no is not None: |
1052 | x += GraphFormat.MINI_BAR_LABEL_OFS | 1078 | x += GraphFormat.MINI_BAR_LABEL_OFS |
@@ -1063,35 +1089,42 @@ class TaskGraph(Graph): | |||
1063 | self.canvas.add_sel_mini_bar(x, y, width, height, event) | 1089 | self.canvas.add_sel_mini_bar(x, y, width, height, event) |
1064 | 1090 | ||
1065 | class CpuGraph(Graph): | 1091 | class CpuGraph(Graph): |
1066 | def render_surface(self, sched): | 1092 | def render_surface(self, sched, real_x, real_y, width, height, selectable=False): |
1067 | self.canvas.whiteout() | 1093 | if not selectable: |
1068 | self.canvas.clear_selectable_regions() | 1094 | self.canvas.whiteout(real_x, real_y, width, height) |
1095 | else: | ||
1096 | self.canvas.clear_selectable_regions(real_x, real_y, width, height) | ||
1069 | 1097 | ||
1070 | start_time, end_time, start_item, end_item = self.get_offset_params() | 1098 | start_time, end_time, start_item, end_item = self.get_offset_params(real_x, real_y, width, height) |
1071 | 1099 | ||
1072 | self.draw_skeleton(start_time, end_time, start_item, end_item) | 1100 | if not selectable: |
1101 | self.draw_skeleton(start_time, end_time, start_item, end_item) | ||
1073 | 1102 | ||
1074 | event_list = dict(schedule.EVENT_LIST) | 1103 | event_list = dict(schedule.EVENT_LIST) |
1075 | 1104 | ||
1076 | del event_list[schedule.ReleaseEvent] | 1105 | bottom_events = [schedule.ReleaseEvent, schedule.DeadlineEvent, schedule.InversionStartEvent, |
1077 | del event_list[schedule.DeadlineEvent] | 1106 | schedule.InversionEndEvent, schedule.InversionDummy] |
1107 | |||
1108 | for event in bottom_events: | ||
1109 | del event_list[event] | ||
1078 | 1110 | ||
1079 | for layer in Canvas.LAYERS: | 1111 | for layer in Canvas.LAYERS: |
1080 | prev_events = {} | 1112 | prev_events = {} |
1081 | for event in sched.get_time_slot_array().iter_over_period( | 1113 | for event in sched.get_time_slot_array().iter_over_period( |
1082 | start_time, end_time, start_item, end_item, | 1114 | start_time, end_time, start_item, end_item, |
1083 | schedule.TimeSlotArray.CPU_LIST, schedule.EVENT_LIST): | 1115 | schedule.TimeSlotArray.CPU_LIST, schedule.EVENT_LIST): |
1084 | event.render(self, layer, prev_events) | 1116 | event.render(self, layer, prev_events, selectable) |
1085 | 1117 | ||
1086 | if end_item >= len(self.y_item_list): | 1118 | if end_item >= len(self.y_item_list): |
1087 | # we are far down enough that we should render the releases and deadlines | 1119 | # we are far down enough that we should render the releases and deadlines and inversions, |
1120 | # which appear near the x-axis | ||
1088 | for layer in Canvas.LAYERS: | 1121 | for layer in Canvas.LAYERS: |
1089 | prev_events = {} | 1122 | prev_events = {} |
1090 | for event in sched.get_time_slot_array().iter_over_period( | 1123 | for event in sched.get_time_slot_array().iter_over_period( |
1091 | start_time, end_time, start_item, end_item, | 1124 | start_time, end_time, 0, sched.get_num_cpus(), |
1092 | schedule.TimeSlotArray.CPU_LIST, | 1125 | schedule.TimeSlotArray.CPU_LIST, |
1093 | (schedule.ReleaseEvent, schedule.DeadlineEvent)): | 1126 | bottom_events): |
1094 | event.render(self, layer, prev_events) | 1127 | event.render(self, layer, prev_events, selectable) |
1095 | 1128 | ||
1096 | def render(self, schedule, start_time=None, end_time=None): | 1129 | def render(self, schedule, start_time=None, end_time=None): |
1097 | if end_time < start_time: | 1130 | if end_time < start_time: |
@@ -1145,9 +1178,10 @@ class CpuGraph(Graph): | |||
1145 | self.canvas.draw_completion_marker(x, y, height, selected) | 1178 | self.canvas.draw_completion_marker(x, y, height, selected) |
1146 | 1179 | ||
1147 | def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): | 1180 | def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): |
1148 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | 1181 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR |
1182 | |||
1149 | x = self._get_time_xpos(time) | 1183 | x = self._get_time_xpos(time) |
1150 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | 1184 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() - height |
1151 | 1185 | ||
1152 | self.canvas.add_sel_completion_marker(x, y, height, event) | 1186 | self.canvas.add_sel_completion_marker(x, y, height, event) |
1153 | 1187 | ||
@@ -1169,7 +1203,7 @@ class CpuGraph(Graph): | |||
1169 | AlignMode.CENTER, AlignMode.BOTTOM) | 1203 | AlignMode.CENTER, AlignMode.BOTTOM) |
1170 | 1204 | ||
1171 | def add_sel_release_arrow_at_time(self, time, task_no, event): | 1205 | def add_sel_release_arrow_at_time(self, time, task_no, event): |
1172 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | 1206 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR |
1173 | 1207 | ||
1174 | x = self._get_time_xpos(time) | 1208 | x = self._get_time_xpos(time) |
1175 | y = self.origin[1] - height | 1209 | y = self.origin[1] - height |
@@ -1194,7 +1228,7 @@ class CpuGraph(Graph): | |||
1194 | AlignMode.CENTER, AlignMode.BOTTOM) | 1228 | AlignMode.CENTER, AlignMode.BOTTOM) |
1195 | 1229 | ||
1196 | def add_sel_deadline_arrow_at_time(self, time, task_no, event): | 1230 | def add_sel_deadline_arrow_at_time(self, time, task_no, event): |
1197 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | 1231 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR |
1198 | 1232 | ||
1199 | x = self._get_time_xpos(time) | 1233 | x = self._get_time_xpos(time) |
1200 | y = self.origin[1] - height | 1234 | y = self.origin[1] - height |
@@ -1226,18 +1260,18 @@ class CpuGraph(Graph): | |||
1226 | width = self._get_bar_width(start_time, end_time) | 1260 | width = self._get_bar_width(start_time, end_time) |
1227 | height = self._get_bar_height() | 1261 | height = self._get_bar_height() |
1228 | 1262 | ||
1229 | self.canvas.add_sel_region(SelectableRegion(x, y, width, height, event)) | 1263 | self.canvas.add_sel_bar(x, y, width, height, event) |
1230 | 1264 | ||
1231 | def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False): | 1265 | 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: | 1266 | if start_time > end_time: |
1233 | raise ValueError("Litmus is not a time machine") | 1267 | raise ValueError("Litmus is not a time machine") |
1234 | 1268 | ||
1235 | x = self._get_time_xpos(start_time) | 1269 | x = self._get_time_xpos(start_time) |
1236 | y = self._get_item_ypos(cpu_no) - self._get_mini_bar_ofs() | 1270 | y = self._get_item_ypos(len(self.y_item_list)) |
1237 | width = self._get_bar_width(start_time, end_time) | 1271 | width = self._get_bar_width(start_time, end_time) |
1238 | height = self._get_mini_bar_height() | 1272 | height = self._get_mini_bar_height() |
1239 | 1273 | ||
1240 | self.canvas.draw_mini_bar(x, y, width, height, cpu_no, selected) | 1274 | self.canvas.draw_mini_bar(x, y, width, height, task_no, selected) |
1241 | 1275 | ||
1242 | if job_no is not None: | 1276 | if job_no is not None: |
1243 | x += GraphFormat.MINI_BAR_LABEL_OFS | 1277 | x += GraphFormat.MINI_BAR_LABEL_OFS |
@@ -1247,8 +1281,8 @@ class CpuGraph(Graph): | |||
1247 | 1281 | ||
1248 | def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | 1282 | 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) | 1283 | x = self._get_time_xpos(start_time) |
1250 | y = self._get_item_ypos(cpu_no) - self._get_mini_bar_ofs() | 1284 | y = self._get_item_ypos(len(self.y_item_list)) |
1251 | width = self._get_bar_width(start_time, end_time) | 1285 | width = self._get_bar_width(start_time, end_time) |
1252 | height = self._get_mini_bar_height() | 1286 | height = self._get_mini_bar_height() |
1253 | 1287 | ||
1254 | self.canvas.add_sel_mini_bar(x, y, width, height, cpu_no, selected) | 1288 | self.canvas.add_sel_mini_bar(x, y, width, height, event) |
diff --git a/viz/format.py b/viz/format.py index fed39f0..6469467 100644 --- a/viz/format.py +++ b/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/viz/schedule.py b/viz/schedule.py index f842c8d..e525b17 100644 --- a/viz/schedule.py +++ b/viz/schedule.py | |||
@@ -8,6 +8,10 @@ graphic.""" | |||
8 | from draw import * | 8 | from draw import * |
9 | import util | 9 | import util |
10 | 10 | ||
11 | import copy | ||
12 | |||
13 | EVENT_LIST = None | ||
14 | |||
11 | class TimeSlotArray(object): | 15 | class 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,14 +21,28 @@ 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 i in range(0, int((end - start) // self.time_per_maj + 1)): | ||
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 | self.array[type].append([]) | ||
43 | for j in range(0, num): | ||
44 | self.array[type][i].append(dict(zip(EVENT_LIST, \ | ||
45 | [[] for j in range(0, len(EVENT_LIST))]))) | ||
28 | 46 | ||
29 | def get_time_slot(self, time): | 47 | def get_time_slot(self, time): |
30 | return int((time - self.start) // self.time_per_maj) | 48 | return int((time - self.start) // self.time_per_maj) |
@@ -34,29 +52,32 @@ class TimeSlotArray(object): | |||
34 | cpu = event.get_cpu() | 52 | cpu = event.get_cpu() |
35 | time_slot = self.get_time_slot(event.get_time()) | 53 | time_slot = self.get_time_slot(event.get_time()) |
36 | 54 | ||
37 | self.array[time_slot][TimeSlotArray.TASK_LIST][task_no][event.__class__] = event | 55 | self.array[TimeSlotArray.TASK_LIST][time_slot][task_no][event.__class__].append(event) |
38 | self.array[time_slot][TimeSlotArray.CPU_LIST][cpu][event.__class__] = event | 56 | self.array[TimeSlotArray.CPU_LIST][time_slot][cpu][event.__class__].append(event) |
39 | 57 | ||
40 | span_events = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy} | 58 | span_events = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy} |
41 | 59 | ||
42 | for span_event in span_events: | 60 | for span_event in span_events: |
43 | if isinstance(event, span_event) and not event.is_erroneous(): | 61 | 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()) | 62 | start_slot = self.get_time_slot(event.corresp_start_event.get_time()) |
45 | end_slot = self.get_time_slot(event.get_time()) | 63 | end_slot = time_slot |
46 | for slot in range(start_slot + 1, end_slot): | 64 | for slot in range(start_slot + 1, end_slot): |
47 | dummy = span_events[span_event](task_no, cpu) | 65 | dummy = span_events[span_event](task_no, cpu) |
48 | dummy.corresp_start_event = event.corresp_start_event | 66 | dummy.corresp_start_event = event.corresp_start_event |
49 | self.array[slot][TimeSlotArray.TASK_LIST][task_no][dummy.__class__] = dummy | 67 | self.array[TimeSlotArray.TASK_LIST][slot][task_no][dummy.__class__].append(dummy) |
50 | self.array[slot][TimeSlotArray.CPU_LIST][cpu][dummy.__class__] = dummy | 68 | self.array[TimeSlotArray.CPU_LIST][slot][cpu][dummy.__class__].append(dummy) |
51 | 69 | ||
52 | def iter_over_period(self, start, end, start_no, end_no, list_type, event_types): | 70 | def iter_over_period(self, start, end, start_no, end_no, list_type, event_types): |
71 | if self.array is None: | ||
72 | return # empty schedule | ||
73 | |||
53 | if start > end: | 74 | if start > end: |
54 | raise ValueError('Litmus is not a time machine') | 75 | raise ValueError('Litmus is not a time machine') |
55 | if start_no > end_no: | 76 | if start_no > end_no: |
56 | raise ValueError('start no should be less than end no') | 77 | raise ValueError('start no should be less than end no') |
57 | 78 | ||
58 | start_slot = max(0, self.get_time_slot(start)) | 79 | start_slot = max(0, self.get_time_slot(start)) |
59 | end_slot = min(len(self.array), self.get_time_slot(end) + 2) | 80 | end_slot = min(len(self.array[list_type]), self.get_time_slot(end) + 2) |
60 | 81 | ||
61 | start_no = max(0, start_no) | 82 | start_no = max(0, start_no) |
62 | end_no = min(self.list_sizes[list_type] - 1, end_no) | 83 | end_no = min(self.list_sizes[list_type] - 1, end_no) |
@@ -64,8 +85,8 @@ class TimeSlotArray(object): | |||
64 | for slot in range(start_slot, end_slot): | 85 | for slot in range(start_slot, end_slot): |
65 | for no in range(start_no, end_no + 1): | 86 | for no in range(start_no, end_no + 1): |
66 | for type in event_types: | 87 | for type in event_types: |
67 | if type in self.array[slot][list_type][no]: | 88 | for event in self.array[list_type][slot][no][type]: |
68 | yield self.array[slot][list_type][no][type] | 89 | yield event |
69 | 90 | ||
70 | class Schedule(object): | 91 | class Schedule(object): |
71 | """The total schedule (task system), consisting of a certain number of | 92 | """The total schedule (task system), consisting of a certain number of |
@@ -75,36 +96,47 @@ class Schedule(object): | |||
75 | self.name = name | 96 | self.name = name |
76 | self.tasks = {} | 97 | self.tasks = {} |
77 | self.task_list = [] | 98 | self.task_list = [] |
99 | self.selected = {} | ||
78 | self.time_slot_array = None | 100 | self.time_slot_array = None |
79 | self.cur_task_no = 0 | 101 | self.cur_task_no = 0 |
80 | self.num_cpus = num_cpus | 102 | self.num_cpus = num_cpus |
81 | for task in task_list: | 103 | for task in task_list: |
82 | self.add_task(task) | 104 | self.add_task(task) |
83 | 105 | ||
84 | def set_time_params(self, time_per_maj=None): | 106 | def get_selected(self): |
85 | if self.get_task_list() is None: | 107 | return self.selected |
86 | return (0, 0) | ||
87 | 108 | ||
109 | def set_selected(self, new_selected): | ||
110 | for event in self.selected: | ||
111 | event.selected = False | ||
112 | for event in new_selected: | ||
113 | event.selected = True | ||
114 | self.selected = new_selected | ||
115 | |||
116 | def get_selected(self): | ||
117 | return copy.copy(self.selected) | ||
118 | |||
119 | def set_time_params(self, time_per_maj=None, max_num_slots=None): | ||
88 | def find_extreme_time_sched(sched, cmp): | 120 | def find_extreme_time_sched(sched, cmp): |
89 | def find_extreme_time_task(task, cmp): | 121 | def find_extreme_time_task(task, cmp): |
90 | def find_extreme_time_job(job, cmp): | 122 | def find_extreme_time_job(job, cmp): |
91 | extreme_time = None | 123 | extreme_time = None |
92 | for time in job.get_events(): | 124 | for time in job.get_events(): |
93 | if extreme_time is None or cmp(time, extreme_time) < 0: | 125 | if (extreme_time is None) or cmp(time, extreme_time) < 0: |
94 | extreme_time = time | 126 | extreme_time = time |
95 | return extreme_time | 127 | return extreme_time |
96 | 128 | ||
97 | extreme_time = None | 129 | extreme_time = None |
98 | for job_no in task.get_jobs(): | 130 | for job_no in task.get_jobs(): |
99 | time = find_extreme_time_job(task.get_jobs()[job_no], cmp) | 131 | 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): | 132 | if (time is not None) and ((extreme_time is None) or cmp(time, extreme_time) < 0): |
101 | extreme_time = time | 133 | extreme_time = time |
102 | return extreme_time | 134 | return extreme_time |
103 | 135 | ||
104 | extreme_time = None | 136 | extreme_time = None |
105 | for task in sched.get_task_list(): | 137 | for task in sched.get_task_list(): |
106 | time = find_extreme_time_task(task, cmp) | 138 | time = find_extreme_time_task(task, cmp) |
107 | if time is not None and (extreme_time is None or cmp(time, extreme_time) < 0): | 139 | if (time is not None) and ((extreme_time is None) or cmp(time, extreme_time) < 0): |
108 | extreme_time = time | 140 | extreme_time = time |
109 | 141 | ||
110 | return extreme_time | 142 | return extreme_time |
@@ -129,20 +161,32 @@ class Schedule(object): | |||
129 | 161 | ||
130 | self.start = find_extreme_time_sched(self, earliest_cmp) | 162 | self.start = find_extreme_time_sched(self, earliest_cmp) |
131 | self.end = find_extreme_time_sched(self, latest_cmp) | 163 | self.end = find_extreme_time_sched(self, latest_cmp) |
132 | self.time_per_maj = time_per_maj | 164 | if self.start is None or self.end is None: |
165 | self.time_slot_array = TimeSlotArray() | ||
166 | return | ||
167 | |||
168 | min_time_per_maj = (self.end - self.start) * 1.0 / max_num_slots | ||
169 | if max_num_slots is not None and time_per_maj is not None: | ||
170 | if time_per_maj < min_time_per_maj: | ||
171 | self.time_per_maj = min_time_per_maj | ||
172 | else: | ||
173 | self.time_per_maj = time_per_maj | ||
174 | else: | ||
175 | self.time_per_maj = time_per_maj | ||
133 | self.time_slot_array = None | 176 | self.time_slot_array = None |
134 | if self.time_per_maj is not None: | 177 | if self.time_per_maj is not None: |
135 | self.time_slot_array = TimeSlotArray(self.start, self.end, time_per_maj, \ | 178 | self.time_slot_array = TimeSlotArray(self.start, self.end, self.time_per_maj, \ |
136 | len(self.task_list), self.num_cpus) | 179 | len(self.task_list), self.num_cpus) |
137 | 180 | ||
181 | |||
138 | def get_time_slot_array(self): | 182 | def get_time_slot_array(self): |
139 | return self.time_slot_array | 183 | return self.time_slot_array |
140 | 184 | ||
141 | def get_time_bounds(self): | 185 | def get_time_bounds(self): |
142 | return (self.start, self.end) | 186 | return (self.start, self.end) |
143 | 187 | ||
144 | def scan(self, time_per_maj): | 188 | def scan(self, time_per_maj, max_num_slots): |
145 | self.set_time_params(time_per_maj) | 189 | self.set_time_params(time_per_maj, max_num_slots) |
146 | 190 | ||
147 | # we scan the graph task by task, and job by job | 191 | # we scan the graph task by task, and job by job |
148 | switches = {} | 192 | switches = {} |
@@ -261,9 +305,13 @@ class DummyEvent(object): | |||
261 | def get_layer(self): | 305 | def get_layer(self): |
262 | return self.layer | 306 | return self.layer |
263 | 307 | ||
264 | def render(self, graph, layer, prev_events): | 308 | def render(self, graph, layer, prev_events, selectable=False): |
265 | """Method that the visualizer calls to tell the event to render itself | 309 | """Method that the visualizer calls to tell the event to render itself |
266 | Obviously only implemented by subclasses (actual event types)""" | 310 | Obviously only implemented by subclasses (actual event types) |
311 | |||
312 | ``Rendering'' can mean either actually drawing the event or just | ||
313 | adding it as a selectable region. This is controlled by the | ||
314 | ``selectable'' parameter""" | ||
267 | raise NotImplementdError | 315 | raise NotImplementdError |
268 | 316 | ||
269 | class Event(DummyEvent): | 317 | class Event(DummyEvent): |
@@ -295,10 +343,6 @@ class Event(DummyEvent): | |||
295 | def is_selected(self): | 343 | def is_selected(self): |
296 | """Returns whether the event has been selected by the user. (needed for rendering)""" | 344 | """Returns whether the event has been selected by the user. (needed for rendering)""" |
297 | return self.selected | 345 | return self.selected |
298 | |||
299 | def set_selected(self, sel): | ||
300 | """Sets the event's state to selected.""" | ||
301 | self.selected = sel | ||
302 | 346 | ||
303 | def scan(self, cur_cpu, switches): | 347 | def scan(self, cur_cpu, switches): |
304 | """Part of the procedure that walks through all the events and sets | 348 | """Part of the procedure that walks through all the events and sets |
@@ -328,13 +372,16 @@ class SuspendEvent(Event): | |||
328 | print "suspending on a CPU different from the CPU we are on!" | 372 | print "suspending on a CPU different from the CPU we are on!" |
329 | super(SuspendEvent, self).scan(cur_cpu, switches) | 373 | super(SuspendEvent, self).scan(cur_cpu, switches) |
330 | 374 | ||
331 | def render(self, graph, layer, prev_events): | 375 | def render(self, graph, layer, prev_events, selectable=False): |
332 | if layer == self.layer: | 376 | if layer == self.layer: |
333 | prev_events[self] = None | 377 | prev_events[self] = None |
334 | graph.draw_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | 378 | if selectable: |
335 | self.get_cpu(), self.is_selected()) | 379 | 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) | 380 | self.get_cpu(), self) |
381 | else: | ||
382 | graph.draw_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | ||
383 | self.get_cpu(), self.is_selected()) | ||
384 | |||
338 | 385 | ||
339 | class ResumeEvent(Event): | 386 | class ResumeEvent(Event): |
340 | def __init__(self, time, cpu): | 387 | def __init__(self, time, cpu): |
@@ -350,13 +397,16 @@ class ResumeEvent(Event): | |||
350 | print "Resuming when currently scheduled on a CPU, but on a different CPU from the current CPU!" | 397 | 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) | 398 | super(ResumeEvent, self).scan(cur_cpu, switches) |
352 | 399 | ||
353 | def render(self, graph, layer, prev_events): | 400 | def render(self, graph, layer, prev_events, selectable=False): |
354 | if layer == self.layer: | 401 | if layer == self.layer: |
355 | prev_events[self] = None | 402 | prev_events[self] = None |
356 | graph.draw_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | 403 | if selectable: |
357 | self.get_cpu(), self.is_selected()) | 404 | 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) | 405 | self.get_cpu(), self) |
406 | else: | ||
407 | graph.draw_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | ||
408 | self.get_cpu(), self.is_selected()) | ||
409 | |||
360 | 410 | ||
361 | class CompleteEvent(Event): | 411 | class CompleteEvent(Event): |
362 | def __init__(self, time, cpu): | 412 | def __init__(self, time, cpu): |
@@ -369,14 +419,15 @@ class CompleteEvent(Event): | |||
369 | def scan(self, cur_cpu, switches): | 419 | def scan(self, cur_cpu, switches): |
370 | super(CompleteEvent, self).scan(cur_cpu, switches) | 420 | super(CompleteEvent, self).scan(cur_cpu, switches) |
371 | 421 | ||
372 | def render(self, graph, layer, prev_events): | 422 | def render(self, graph, layer, prev_events, selectable=False): |
373 | if layer == Canvas.TOP_LAYER: | 423 | if layer == Canvas.TOP_LAYER: |
374 | prev_events[self] = None | 424 | prev_events[self] = None |
375 | graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | 425 | if selectable: |
376 | self.get_cpu(), self.is_selected()) | 426 | 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) | 427 | self.get_cpu(), self) |
379 | 428 | else: | |
429 | graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | ||
430 | self.get_cpu(), self.is_selected()) | ||
380 | 431 | ||
381 | class SwitchAwayEvent(Event): | 432 | class SwitchAwayEvent(Event): |
382 | def __init__(self, time, cpu): | 433 | def __init__(self, time, cpu): |
@@ -412,10 +463,10 @@ class SwitchAwayEvent(Event): | |||
412 | 463 | ||
413 | super(SwitchAwayEvent, self).scan(cur_cpu, switches) | 464 | super(SwitchAwayEvent, self).scan(cur_cpu, switches) |
414 | 465 | ||
415 | def render(self, graph, layer, prev_events): | 466 | 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: | 467 | if self.corresp_start_event is None or self.corresp_start_event in prev_events: |
417 | return # erroneous switch away or already rendered | 468 | return # erroneous switch away or already rendered |
418 | self.corresp_start_event.render(graph, layer, prev_events) | 469 | self.corresp_start_event.render(graph, layer, prev_events, selectable) |
419 | 470 | ||
420 | class SwitchToEvent(Event): | 471 | class SwitchToEvent(Event): |
421 | def __init__(self, time, cpu): | 472 | def __init__(self, time, cpu): |
@@ -442,17 +493,19 @@ class SwitchToEvent(Event): | |||
442 | 493 | ||
443 | super(SwitchToEvent, self).scan(cur_cpu, switches) | 494 | super(SwitchToEvent, self).scan(cur_cpu, switches) |
444 | 495 | ||
445 | def render(self, graph, layer, prev_events): | 496 | def render(self, graph, layer, prev_events, selectable=False): |
446 | if self.is_erroneous(): | 497 | if self.corresp_end_event is None: |
447 | return # erroneous switch to | 498 | return # fatally erroneous switch to |
448 | if layer == Canvas.BOTTOM_LAYER: | 499 | if layer == Canvas.BOTTOM_LAYER: |
449 | prev_events[self] = None | 500 | prev_events[self] = None |
450 | cpu = self.get_cpu() | 501 | cpu = self.get_cpu() |
451 | task_no = self.get_job().get_task().get_task_no() | 502 | 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(), | 503 | if selectable: |
453 | task_no, cpu, self.get_job().get_job_no(), self.is_selected()) | 504 | 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) | 505 | task_no, cpu, self) |
506 | else: | ||
507 | graph.draw_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), | ||
508 | task_no, cpu, self.get_job().get_job_no(), self.is_selected()) | ||
456 | 509 | ||
457 | class ReleaseEvent(Event): | 510 | class ReleaseEvent(Event): |
458 | def __init__(self, time, cpu): | 511 | def __init__(self, time, cpu): |
@@ -465,13 +518,16 @@ class ReleaseEvent(Event): | |||
465 | def scan(self, cur_cpu, switches): | 518 | def scan(self, cur_cpu, switches): |
466 | super(ReleaseEvent, self).scan(cur_cpu, switches) | 519 | super(ReleaseEvent, self).scan(cur_cpu, switches) |
467 | 520 | ||
468 | def render(self, graph, layer, prev_events): | 521 | def render(self, graph, layer, prev_events, selectable=False): |
469 | prev_events[self] = None | 522 | prev_events[self] = None |
470 | if layer == Canvas.TOP_LAYER: | 523 | if layer == Canvas.TOP_LAYER: |
471 | graph.draw_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | 524 | if selectable: |
525 | graph.add_sel_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | ||
526 | self) | ||
527 | else: | ||
528 | 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()) | 529 | 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(), | 530 | |
474 | self) | ||
475 | 531 | ||
476 | class DeadlineEvent(Event): | 532 | class DeadlineEvent(Event): |
477 | def __init__(self, time, cpu): | 533 | def __init__(self, time, cpu): |
@@ -484,13 +540,16 @@ class DeadlineEvent(Event): | |||
484 | def scan(self, cur_cpu, switches): | 540 | def scan(self, cur_cpu, switches): |
485 | super(DeadlineEvent, self).scan(cur_cpu, switches) | 541 | super(DeadlineEvent, self).scan(cur_cpu, switches) |
486 | 542 | ||
487 | def render(self, graph, layer, prev_events): | 543 | def render(self, graph, layer, prev_events, selectable=False): |
488 | prev_events[self] = None | 544 | prev_events[self] = None |
489 | if layer == Canvas.TOP_LAYER: | 545 | if layer == Canvas.TOP_LAYER: |
490 | graph.draw_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | 546 | if selectable: |
491 | self.get_job().get_job_no(), self.is_selected()) | 547 | 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) | 548 | self) |
549 | else: | ||
550 | graph.draw_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), | ||
551 | self.get_job().get_job_no(), self.is_selected()) | ||
552 | |||
494 | 553 | ||
495 | class InversionStartEvent(ErrorEvent): | 554 | class InversionStartEvent(ErrorEvent): |
496 | def __init__(self, time): | 555 | def __init__(self, time): |
@@ -512,15 +571,18 @@ class InversionStartEvent(ErrorEvent): | |||
512 | # the corresp_end_event should already be set | 571 | # the corresp_end_event should already be set |
513 | super(InversionStartEvent, self).scan(cur_cpu, switches) | 572 | super(InversionStartEvent, self).scan(cur_cpu, switches) |
514 | 573 | ||
515 | def render(self, graph, layer, prev_events): | 574 | def render(self, graph, layer, prev_events, selectable=False): |
516 | if layer == Canvas.BOTTOM_LAYER: | 575 | if layer == Canvas.BOTTOM_LAYER: |
517 | prev_events[self] = None | 576 | prev_events[self] = None |
518 | cpu = self.get_cpu() | 577 | cpu = self.get_cpu() |
519 | task_no = self.get_job().get_task().get_task_no() | 578 | 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(), | 579 | if selectable: |
521 | task_no, cpu, self.get_job().get_job_no(), self.is_selected()) | 580 | 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) | 581 | task_no, cpu, self) |
582 | else: | ||
583 | graph.draw_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), | ||
584 | task_no, cpu, self.get_job().get_job_no(), self.is_selected()) | ||
585 | |||
524 | 586 | ||
525 | class InversionEndEvent(ErrorEvent): | 587 | class InversionEndEvent(ErrorEvent): |
526 | def __init__(self, time): | 588 | def __init__(self, time): |
@@ -549,23 +611,25 @@ class InversionEndEvent(ErrorEvent): | |||
549 | 611 | ||
550 | super(InversionEndEvent, self).scan(cur_cpu, switches) | 612 | super(InversionEndEvent, self).scan(cur_cpu, switches) |
551 | 613 | ||
552 | def render(self, graph, layer, prev_events): | 614 | 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: | 615 | if self.corresp_start_event is None or self.corresp_start_event in prev_events: |
554 | return # erroneous inversion end or already rendered | 616 | return # erroneous inversion end or already rendered |
555 | self.corresp_start_event.render(graph, layer, prev_events) | 617 | self.corresp_start_event.render(graph, layer, prev_events, selectable) |
556 | 618 | ||
557 | class InversionDummy(DummyEvent): | 619 | class InversionDummy(DummyEvent): |
558 | def render(self, graph, layer, prev_events): | 620 | def render(self, graph, layer, prev_events, selectable=False): |
559 | if self.corresp_start_event in prev_events: | 621 | if self.corresp_start_event in prev_events: |
560 | return # we have already been rendered | 622 | return # we have already been rendered |
561 | self.corresp_start_event.render(graph, layer, prev_events) | 623 | self.corresp_start_event.render(graph, layer, prev_events, selectable) |
562 | 624 | ||
563 | class IsRunningDummy(DummyEvent): | 625 | class IsRunningDummy(DummyEvent): |
564 | def render(self, graph, layer, prev_events): | 626 | def render(self, graph, layer, prev_events, selectable=False): |
565 | if self.corresp_start_event in prev_events: | 627 | if self.corresp_start_event in prev_events: |
566 | return # we have already been rendered | 628 | return # we have already been rendered |
567 | self.corresp_start_event.render(graph, layer, prev_events) | 629 | self.corresp_start_event.render(graph, layer, prev_events, selectable) |
568 | 630 | ||
569 | EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, SwitchAwayEvent : None, | 631 | EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, |
570 | SwitchToEvent : None, ReleaseEvent : None, DeadlineEvent : None, IsRunningDummy : None, | 632 | SwitchAwayEvent : None, SwitchToEvent : None, ReleaseEvent : None, |
571 | InversionStartEvent : None, InversionEndEvent : None, InversionDummy : None} | 633 | DeadlineEvent : None, IsRunningDummy : None, |
634 | InversionStartEvent : None, InversionEndEvent : None, | ||
635 | InversionDummy : None} | ||
diff --git a/viz/viewer.py b/viz/viewer.py index a695473..a7871a7 100644 --- a/viz/viewer.py +++ b/viz/viewer.py | |||
@@ -3,20 +3,36 @@ | |||
3 | """GUI stuff.""" | 3 | """GUI stuff.""" |
4 | 4 | ||
5 | from schedule import * | 5 | from schedule import * |
6 | |||
6 | from renderer import * | 7 | from renderer import * |
7 | 8 | ||
8 | import pygtk | 9 | import pygtk |
9 | import gtk | 10 | import gtk |
10 | import gobject | 11 | import gobject |
12 | |||
13 | class 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 | |||
12 | class GraphArea(gtk.DrawingArea): | 27 | class GraphArea(gtk.DrawingArea): |
13 | DAREA_WIDTH_REQ = 500 | ||
14 | DAREA_HEIGHT_REQ = 300 | ||
15 | HORIZ_PAGE_SCROLL_FACTOR = 4.8 | 28 | 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,25 +43,38 @@ 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) |
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() | ||
49 | 78 | ||
50 | def set_scroll_adjustments(self, widget, horizontal, vertical, data=None): | 79 | def set_scroll_adjustments(self, widget, horizontal, vertical, data=None): |
51 | graph = self.renderer.get_graph() | 80 | graph = self.renderer.get_graph() |
@@ -55,9 +84,11 @@ class GraphArea(gtk.DrawingArea): | |||
55 | self.horizontal = horizontal | 84 | self.horizontal = horizontal |
56 | self.vertical = vertical | 85 | self.vertical = vertical |
57 | self.config_scrollbars(self.cur_x, self.cur_y) | 86 | self.config_scrollbars(self.cur_x, self.cur_y) |
58 | 87 | ||
59 | self.horizontal.connect('value-changed', self.horizontal_value_changed) | 88 | if self.horizontal is not None: |
60 | self.vertical.connect('value-changed', self.vertical_value_changed) | 89 | self.horizontal.connect('value-changed', self.horizontal_value_changed) |
90 | if self.vertical is not None: | ||
91 | self.vertical.connect('value-changed', self.vertical_value_changed) | ||
61 | 92 | ||
62 | def horizontal_value_changed(self, adjustment): | 93 | def horizontal_value_changed(self, adjustment): |
63 | self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) | 94 | self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) |
@@ -85,7 +116,7 @@ class GraphArea(gtk.DrawingArea): | |||
85 | 116 | ||
86 | if self.horizontal is not None: | 117 | 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, | 118 | 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) | 119 | graph.get_attrs().maj_sep * GraphArea.HORIZ_PAGE_SCROLL_FACTOR, self.width) |
89 | if self.vertical is not None: | 120 | 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, | 121 | 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) | 122 | graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR, self.height) |
@@ -101,7 +132,9 @@ class GraphArea(gtk.DrawingArea): | |||
101 | msg = None | 132 | msg = None |
102 | 133 | ||
103 | graph = self.renderer.get_graph() | 134 | graph = self.renderer.get_graph() |
104 | just_selected = graph.get_selected_regions(motion_event.x, motion_event.y) | 135 | |
136 | graph.render_surface(self.renderer.get_schedule(), motion_event.x, motion_event.y, 0, 0, True) | ||
137 | just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0) | ||
105 | if not just_selected: | 138 | if not just_selected: |
106 | msg = '' | 139 | msg = '' |
107 | the_event = None | 140 | the_event = None |
@@ -116,36 +149,96 @@ class GraphArea(gtk.DrawingArea): | |||
116 | msg = str(the_event) | 149 | msg = str(the_event) |
117 | 150 | ||
118 | self.emit('update-event-description', the_event, msg) | 151 | self.emit('update-event-description', the_event, msg) |
152 | |||
153 | if self.left_button_start_coor is not None: | ||
154 | selected = {} | ||
155 | if self.ctrl_clicked: | ||
156 | selected = dict(self.last_selected) | ||
157 | |||
158 | # dragging a rectangle | ||
159 | x = min(self.left_button_start_coor[0], motion_event.x) | ||
160 | y = min(self.left_button_start_coor[1], motion_event.y) | ||
161 | width = abs(self.left_button_start_coor[0] - motion_event.x) | ||
162 | height = abs(self.left_button_start_coor[1] - motion_event.y) | ||
163 | |||
164 | graph.render_surface(self.renderer.get_schedule(), x, y, width, height, True) | ||
165 | selected.update(graph.get_selected_regions(x, y, width, height)) | ||
166 | self.renderer.get_schedule().set_selected(selected) | ||
167 | |||
168 | self.select_rect = (x, y, width, height) | ||
119 | 169 | ||
170 | self._dirty() | ||
171 | |||
120 | def button_press(self, widget, button_event, data=None): | 172 | def button_press(self, widget, button_event, data=None): |
121 | graph = self.renderer.get_graph() | 173 | graph = self.renderer.get_graph() |
122 | 174 | ||
175 | self.ctrl_clicked = button_event.state & gtk.gdk.CONTROL_MASK | ||
176 | |||
123 | if button_event.button == 1: | 177 | if button_event.button == 1: |
124 | just_selected = graph.get_selected_regions(button_event.x, button_event.y) | 178 | self.left_button_start_coor = (button_event.x, button_event.y) |
179 | graph.render_surface(self.renderer.get_schedule(), \ | ||
180 | button_event.x, button_event.y, 0, 0, True) | ||
181 | |||
182 | just_selected = graph.get_selected_regions(button_event.x, button_event.y, 0, 0) | ||
125 | 183 | ||
126 | max_layer = self._find_max_layer(just_selected) | 184 | max_layer = self._find_max_layer(just_selected) |
127 | 185 | ||
186 | new_now_selected = None | ||
187 | if self.ctrl_clicked: | ||
188 | new_now_selected = self.renderer.get_schedule().get_selected() | ||
189 | else: | ||
190 | new_now_selected = {} | ||
191 | |||
128 | # only select those events which were in the top layer (it's | 192 | # only select those events which were in the top layer (it's |
129 | # not intuitive to click something and then have something | 193 | # not intuitive to click something and then have something |
130 | # below it get selected). Also, clicking something that | 194 | # below it get selected). Also, clicking something that |
131 | # is selected deselects it | 195 | # is selected deselects it, if it's the only thing selected |
132 | new_now_selected = {} | ||
133 | for event in just_selected: | 196 | for event in just_selected: |
134 | if event.get_layer() == max_layer: | 197 | if event.get_layer() == max_layer: |
135 | if not event.is_selected(): | 198 | val = True |
199 | now_selected = self.renderer.get_schedule().get_selected() | ||
200 | if (len(now_selected) == 1 or self.ctrl_clicked) and event in now_selected: | ||
201 | val = not event.is_selected | ||
202 | if val: | ||
136 | new_now_selected[event] = None | 203 | new_now_selected[event] = None |
137 | event.set_selected(not event.is_selected()) | 204 | elif event in new_now_selected: |
138 | break | 205 | del new_now_selected[event] |
206 | break # only pick one event when just clicking | ||
139 | 207 | ||
140 | for event in self.now_selected: | 208 | self.renderer.get_schedule().set_selected(new_now_selected) |
141 | if event not in new_now_selected: | 209 | self.last_selected = dict(new_now_selected) |
142 | event.set_selected(False) | ||
143 | |||
144 | self.now_selected = new_now_selected | ||
145 | 210 | ||
146 | rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) | 211 | self._dirty() |
147 | self.window.invalidate_rect(rect, True) | 212 | elif button_event.button == 3: |
148 | 213 | self._release_band() | |
214 | self.emit('request-context-menu', button_event, self.renderer.get_schedule().get_selected()) | ||
215 | |||
216 | def button_release(self, widget, button_event, data=None): | ||
217 | self.ctrl_clicked = False | ||
218 | self.last_selected = dict(self.renderer.get_schedule().get_selected()) | ||
219 | |||
220 | if button_event.button == 1: | ||
221 | self._release_band() | ||
222 | |||
223 | def _release_band(self): | ||
224 | old_select_rect = self.select_rect | ||
225 | self.left_button_start_coor = None | ||
226 | self.select_rect = None | ||
227 | if old_select_rect is not None: | ||
228 | self._dirty() | ||
229 | |||
230 | def _dirty(self, x=None, y=None, width=None, height=None): | ||
231 | if x is None: | ||
232 | x = 0 | ||
233 | if y is None: | ||
234 | y = 0 | ||
235 | if width is None: | ||
236 | width = self.width | ||
237 | if height is None: | ||
238 | height = self.height | ||
239 | rect = gtk.gdk.Rectangle(x, y, width, height) | ||
240 | self.window.invalidate_rect(rect, True) | ||
241 | |||
149 | class GraphWindow(gtk.ScrolledWindow): | 242 | class GraphWindow(gtk.ScrolledWindow): |
150 | def __init__(self, renderer): | 243 | def __init__(self, renderer): |
151 | super(GraphWindow, self).__init__(None, None) | 244 | super(GraphWindow, self).__init__(None, None) |
@@ -157,37 +250,115 @@ class GraphWindow(gtk.ScrolledWindow): | |||
157 | def get_graph_area(self): | 250 | def get_graph_area(self): |
158 | return self.garea | 251 | return self.garea |
159 | 252 | ||
160 | class MainWindow(object): | 253 | class MainWindow(gtk.Window): |
161 | def __init__(self, renderer): | 254 | WINDOW_WIDTH_REQ = 500 |
162 | self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) | 255 | WINDOW_HEIGHT_REQ = 300 |
256 | |||
257 | def __init__(self, request_renderer_change): | ||
258 | super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) | ||
259 | |||
260 | self.connect('delete_event', self.delete_event) | ||
261 | self.connect('destroy', self.die) | ||
262 | |||
263 | self.connect('request-renderer-change', request_renderer_change) | ||
264 | |||
265 | self.file_menu = gtk.Menu() | ||
266 | self.open_item = gtk.MenuItem('_Open', True) | ||
267 | self.open_item.connect('activate', self.open_item_activate) | ||
268 | self.file_menu.append(self.open_item) | ||
269 | self.open_item.show() | ||
270 | |||
271 | self.quit_item = gtk.MenuItem('_Quit', True) | ||
272 | self.quit_item.connect('activate', self.quit_item_activate) | ||
273 | self.quit_item.show() | ||
274 | self.file_menu.append(self.quit_item) | ||
163 | 275 | ||
164 | self.window.connect('delete_event', self.delete_event) | 276 | self.file_item = gtk.MenuItem('_File', True) |
165 | self.window.connect('destroy', self.destroy) | 277 | self.file_item.set_submenu(self.file_menu) |
278 | self.file_item.show() | ||
279 | |||
280 | self.menu_bar = gtk.MenuBar() | ||
281 | self.menu_bar.append(self.file_item) | ||
282 | |||
283 | self.menu_bar.show() | ||
166 | 284 | ||
167 | self.vbox = gtk.VBox(False, 0) | 285 | self.vbox = gtk.VBox(False, 0) |
168 | 286 | ||
169 | self.gwindow = GraphWindow(renderer) | 287 | self.notebook = gtk.Notebook() |
170 | self.gwindow.get_graph_area().connect('update-event-description', | 288 | |
171 | self.update_event_description) | 289 | self.notebook.last_page = -1 |
172 | self.gwindow.show() | 290 | self.notebook.connect('switch-page', self.switch_page) |
291 | |||
292 | self.notebook.show() | ||
173 | 293 | ||
174 | self.desc_label = gtk.Label('') | 294 | self.desc_label = gtk.Label('') |
175 | self.desc_label.set_justify(gtk.JUSTIFY_LEFT) | 295 | self.desc_label.set_justify(gtk.JUSTIFY_LEFT) |
176 | self.desc_label.show() | 296 | self.desc_label.show() |
177 | 297 | ||
178 | self.vbox.pack_start(self.gwindow, True, True, 0) | 298 | self.vbox.pack_start(self.menu_bar, False, False, 0) |
299 | self.vbox.pack_start(self.notebook, True, True, 0) | ||
179 | self.vbox.pack_start(self.desc_label, False, False, 0) | 300 | self.vbox.pack_start(self.desc_label, False, False, 0) |
180 | self.vbox.show() | 301 | self.vbox.show() |
181 | 302 | ||
182 | self.window.add(self.vbox) | 303 | self.add(self.vbox) |
183 | self.window.show() | 304 | |
184 | 305 | self.set_size_request(MainWindow.WINDOW_WIDTH_REQ, MainWindow.WINDOW_HEIGHT_REQ) | |
306 | |||
307 | self.show() | ||
308 | |||
309 | def connect_widgets(self, garea): | ||
310 | garea.connect('update-event-description', self.update_event_description) | ||
311 | garea.connect('request-context-menu', self.request_context_menu) | ||
312 | |||
313 | def set_renderers(self, renderers): | ||
314 | for i in range(0, self.notebook.get_n_pages()): | ||
315 | self.notebook.remove_page(0) | ||
316 | for title in renderers: | ||
317 | gwindow = GraphWindow(renderers[title]) | ||
318 | self.connect_widgets(gwindow.get_graph_area()) | ||
319 | gwindow.show() | ||
320 | self.notebook.append_page(gwindow, gtk.Label(title)) | ||
321 | |||
322 | def switch_page(self, widget, page, page_num): | ||
323 | if self.notebook.last_page >= 0: | ||
324 | hvalue = self.notebook.get_nth_page(self.notebook.last_page).get_hadjustment().get_value() | ||
325 | self.notebook.get_nth_page(page_num).get_hadjustment().set_value(hvalue) | ||
326 | |||
327 | self.notebook.last_page = page_num | ||
328 | |||
185 | def update_event_description(self, widget, event, msg): | 329 | def update_event_description(self, widget, event, msg): |
186 | self.desc_label.set_text(msg) | 330 | self.desc_label.set_text(msg) |
187 | 331 | ||
332 | def request_context_menu(self, widget, gdk_event, selected): | ||
333 | button = 0 | ||
334 | if hasattr(gdk_event, 'button'): | ||
335 | button = gdk_event.button | ||
336 | time = gdk_event.time | ||
337 | |||
338 | menu = GraphContextMenu(selected) | ||
339 | menu.popup(None, None, None, button, time) | ||
340 | |||
341 | def open_item_activate(self, widget): | ||
342 | dialog = gtk.FileChooserDialog('Open File', self, | ||
343 | gtk.FILE_CHOOSER_ACTION_OPEN, | ||
344 | (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, | ||
345 | gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)) | ||
346 | dialog.set_select_multiple(True) | ||
347 | |||
348 | if dialog.run() == gtk.RESPONSE_ACCEPT: | ||
349 | filenames = dialog.get_filenames() | ||
350 | dialog.destroy() | ||
351 | self.emit('request-renderer-change', filenames, None) | ||
352 | else: | ||
353 | dialog.destroy() | ||
354 | |||
355 | |||
356 | def quit_item_activate(self, widget): | ||
357 | self.destroy() | ||
358 | |||
188 | def delete_event(self, widget, event, data=None): | 359 | def delete_event(self, widget, event, data=None): |
189 | return False | 360 | return False |
190 | 361 | ||
191 | def destroy(self, widget, data=None): | 362 | def die(self, widget, data=None): |
192 | gtk.main_quit() | 363 | gtk.main_quit() |
193 | 364 | ||