diff options
author | Gary Bressler <garybressler@nc.rr.com> | 2010-03-15 13:40:58 -0400 |
---|---|---|
committer | Gary Bressler <garybressler@nc.rr.com> | 2010-03-15 13:40:58 -0400 |
commit | 7203974deea94b11b3a0a99619f3c24091b157ed (patch) | |
tree | 1ac2297c45a887b736ba3b79d4402576348bf241 | |
parent | 1f6656c3b8f8e72e3be4ad3e357e748b7d6e0603 (diff) | |
parent | 452023b74dfc6332c1ec548b15a0ed79e0a51b32 (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-- | README | 105 | ||||
-rw-r--r-- | gedf_test.py~ | 164 | ||||
-rwxr-xr-x | reader/sample_script.py | 41 | ||||
-rw-r--r-- | unit_trace/viz/__init__.py | 8 | ||||
-rw-r--r-- | unit_trace/viz/convert.py | 2 | ||||
-rw-r--r-- | unit_trace/viz/draw.py | 142 | ||||
-rw-r--r-- | unit_trace/viz/format.py | 2 | ||||
-rw-r--r-- | unit_trace/viz/schedule.py | 226 | ||||
-rw-r--r-- | unit_trace/viz/trace_reader.py | 32 | ||||
-rw-r--r-- | unit_trace/viz/viewer.py | 315 | ||||
-rwxr-xr-x | visualizer.py | 38 |
11 files changed, 890 insertions, 185 deletions
@@ -0,0 +1,105 @@ | |||
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 | ||
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 | |||
12 | import copy | ||
13 | |||
14 | |||
15 | ############################################################################### | ||
16 | # Public Functions | ||
17 | ############################################################################### | ||
18 | |||
19 | def 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 | ||
84 | class 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 | ||
96 | class 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 | ||
108 | def _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 | ||
115 | def _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. | ||
9 | import trace_reader | ||
10 | import sanitizer | ||
11 | import gedf_test | ||
12 | import stats | ||
13 | import stdout_printer | ||
14 | |||
15 | # Specify your trace files | ||
16 | g6 = [ | ||
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. | ||
29 | def 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 | ||
36 | stream = trace_reader.trace_reader(g6) # Read events from traces | ||
37 | stream = sanitizer.sanitizer(stream) # Remove garbage events | ||
38 | stream = gedf_test.gedf_test(stream) # Produce G-EDF error records | ||
39 | stream = stats.stats(stream) # Produce a statistics record | ||
40 | #stream = filter(my_filter, stream) # Filter some records before printing | ||
41 | stdout_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 | |||
6 | import convert | 6 | import convert |
7 | 7 | ||
8 | gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 8 | gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, |
9 | None, (gtk.Adjustment, gtk.Adjustment)) | 9 | None, (gtk.Adjustment, gtk.Adjustment)) |
10 | gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 10 | gobject.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)) |
12 | gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | ||
13 | None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT)) | ||
14 | gobject.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 | ||
85 | def _pid_to_task_name(pid): | 85 | def _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 | ||
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 |
@@ -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 | ||
921 | class TaskGraph(Graph): | 948 | class 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 | ||
1065 | class CpuGraph(Graph): | 1095 | class 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.""" | |||
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,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 | ||
70 | class Schedule(object): | 97 | class 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 | ||
269 | class Event(DummyEvent): | 323 | class 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 | ||
339 | class ResumeEvent(Event): | 392 | class 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 | ||
361 | class CompleteEvent(Event): | 417 | class 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 | ||
381 | class SwitchAwayEvent(Event): | 438 | class 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 | ||
420 | class SwitchToEvent(Event): | 477 | class 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 | ||
457 | class ReleaseEvent(Event): | 516 | class 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 | ||
476 | class DeadlineEvent(Event): | 538 | class 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 | ||
495 | class InversionStartEvent(ErrorEvent): | 560 | class 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 | ||
525 | class InversionEndEvent(ErrorEvent): | 593 | class 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 | ||
557 | class InversionDummy(DummyEvent): | 625 | class 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 | ||
563 | class IsRunningDummy(DummyEvent): | 631 | class 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 | ||
569 | EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, SwitchAwayEvent : None, | 637 | EVENT_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 | ||
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/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 | ||
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 | 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 | |||
149 | class GraphWindow(gtk.ScrolledWindow): | 248 | class 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 | ||
160 | class MainWindow(object): | 309 | class 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 | |||
5 | import convert | ||
6 | import reader | ||
7 | import viz | ||
8 | |||
9 | import gtk | ||
10 | |||
11 | TIME_PER_MAJ = 10000000 | ||
12 | MAX_NUM_SLOTS = 10000 | ||
13 | |||
14 | def 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 | |||
35 | if __name__ == '__main__': | ||
36 | window = viz.viewer.MainWindow(request_renderer_change) | ||
37 | gtk.main() | ||
38 | |||