summaryrefslogtreecommitdiffstats
path: root/unit_trace/viz/schedule.py
diff options
context:
space:
mode:
authorMac Mollison <mollison@cs.unc.edu>2010-03-13 12:12:37 -0500
committerMac Mollison <mollison@cs.unc.edu>2010-03-13 12:12:37 -0500
commit122f457226f54ad23b7cd138512502e430e704dc (patch)
treefee0690936c3ae95255e559cd0fd09f0fa8c2ad4 /unit_trace/viz/schedule.py
parent14a40b99735f09f6e70b8e897acbb622f9115ca3 (diff)
Further restructuring to create 'unit_trace' pkg
The unit_trace folder should be placed in /usr/local/lib/pythonX.Y/site-packages. This makes unit-trace submodules available from anywhere on the system.
Diffstat (limited to 'unit_trace/viz/schedule.py')
-rw-r--r--unit_trace/viz/schedule.py571
1 files changed, 571 insertions, 0 deletions
diff --git a/unit_trace/viz/schedule.py b/unit_trace/viz/schedule.py
new file mode 100644
index 0000000..f842c8d
--- /dev/null
+++ b/unit_trace/viz/schedule.py
@@ -0,0 +1,571 @@
1#!/usr/bin/env python
2
3"""The data structures to store a schedule (task system), along with all
4the job releases and other events that have occurred for each task. This gives
5a high-level representation of a schedule that can be converted to, say, a
6graphic."""
7
8from draw import *
9import util
10
11class TimeSlotArray(object):
12 """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
14 time are assigned the same ``slot'', and each slot organizes its events by task number
15 as well as by CPU."""
16
17 TASK_LIST = 0
18 CPU_LIST = 1
19
20 def __init__(self, start, end, time_per_maj, num_tasks, num_cpus):
21 self.start = start
22 self.end = end
23 self.time_per_maj = time_per_maj
24 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)], \
26 TimeSlotArray.CPU_LIST : [{} for i in range (0, num_cpus)]} \
27 for i in range(0, (end - start) // self.time_per_maj + 1)]
28
29 def get_time_slot(self, time):
30 return int((time - self.start) // self.time_per_maj)
31
32 def add_event_to_time_slot(self, event):
33 task_no = event.get_job().get_task().get_task_no()
34 cpu = event.get_cpu()
35 time_slot = self.get_time_slot(event.get_time())
36
37 self.array[time_slot][TimeSlotArray.TASK_LIST][task_no][event.__class__] = event
38 self.array[time_slot][TimeSlotArray.CPU_LIST][cpu][event.__class__] = event
39
40 span_events = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy}
41
42 for span_event in span_events:
43 if isinstance(event, span_event) and not event.is_erroneous():
44 start_slot = self.get_time_slot(event.corresp_start_event.get_time())
45 end_slot = self.get_time_slot(event.get_time())
46 for slot in range(start_slot + 1, end_slot):
47 dummy = span_events[span_event](task_no, cpu)
48 dummy.corresp_start_event = event.corresp_start_event
49 self.array[slot][TimeSlotArray.TASK_LIST][task_no][dummy.__class__] = dummy
50 self.array[slot][TimeSlotArray.CPU_LIST][cpu][dummy.__class__] = dummy
51
52 def iter_over_period(self, start, end, start_no, end_no, list_type, event_types):
53 if start > end:
54 raise ValueError('Litmus is not a time machine')
55 if start_no > end_no:
56 raise ValueError('start no should be less than end no')
57
58 start_slot = max(0, self.get_time_slot(start))
59 end_slot = min(len(self.array), self.get_time_slot(end) + 2)
60
61 start_no = max(0, start_no)
62 end_no = min(self.list_sizes[list_type] - 1, end_no)
63
64 for slot in range(start_slot, end_slot):
65 for no in range(start_no, end_no + 1):
66 for type in event_types:
67 if type in self.array[slot][list_type][no]:
68 yield self.array[slot][list_type][no][type]
69
70class Schedule(object):
71 """The total schedule (task system), consisting of a certain number of
72 tasks."""
73
74 def __init__(self, name, num_cpus, task_list=[]):
75 self.name = name
76 self.tasks = {}
77 self.task_list = []
78 self.time_slot_array = None
79 self.cur_task_no = 0
80 self.num_cpus = num_cpus
81 for task in task_list:
82 self.add_task(task)
83
84 def set_time_params(self, time_per_maj=None):
85 if self.get_task_list() is None:
86 return (0, 0)
87
88 def find_extreme_time_sched(sched, cmp):
89 def find_extreme_time_task(task, cmp):
90 def find_extreme_time_job(job, cmp):
91 extreme_time = None
92 for time in job.get_events():
93 if extreme_time is None or cmp(time, extreme_time) < 0:
94 extreme_time = time
95 return extreme_time
96
97 extreme_time = None
98 for job_no in task.get_jobs():
99 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):
101 extreme_time = time
102 return extreme_time
103
104 extreme_time = None
105 for task in sched.get_task_list():
106 time = find_extreme_time_task(task, cmp)
107 if time is not None and (extreme_time is None or cmp(time, extreme_time) < 0):
108 extreme_time = time
109
110 return extreme_time
111
112 def earliest_cmp(x, y):
113 diff = x - y
114 if diff > 0.0:
115 return 1
116 elif diff == 0.0:
117 return 0
118 elif diff < 0.0:
119 return -1
120
121 def latest_cmp(x, y):
122 diff = x - y
123 if diff < 0.0:
124 return 1
125 elif diff == 0.0:
126 return 0
127 elif diff > 0.0:
128 return -1
129
130 self.start = find_extreme_time_sched(self, earliest_cmp)
131 self.end = find_extreme_time_sched(self, latest_cmp)
132 self.time_per_maj = time_per_maj
133 self.time_slot_array = None
134 if self.time_per_maj is not None:
135 self.time_slot_array = TimeSlotArray(self.start, self.end, time_per_maj, \
136 len(self.task_list), self.num_cpus)
137
138 def get_time_slot_array(self):
139 return self.time_slot_array
140
141 def get_time_bounds(self):
142 return (self.start, self.end)
143
144 def scan(self, time_per_maj):
145 self.set_time_params(time_per_maj)
146
147 # we scan the graph task by task, and job by job
148 switches = {}
149 for event in EVENT_LIST:
150 switches[event] = None
151 for task_no, task in enumerate(self.get_task_list()):
152 cur_cpu = [Event.NO_CPU]
153 for job_no in sorted(task.get_jobs().keys()):
154 job = task.get_jobs()[job_no]
155 for event_time in sorted(job.get_events().keys()):
156 # could have multiple events at the same time (unlikely but possible)
157 for event in job.get_events()[event_time]:
158 print "task, job, event:", task.name, job.job_no, event.__class__.__name__
159 event.scan(cur_cpu, switches)
160
161 def add_task(self, task):
162 if task.name in self.tasks:
163 raise ValueError("task already in list!")
164 self.tasks[task.name] = task
165 self.task_list.append(task)
166 task.schedule = self
167 task.task_no = self.cur_task_no
168 self.cur_task_no += 1
169
170 def get_tasks(self):
171 return self.tasks
172
173 def get_task_list(self):
174 return self.task_list
175
176 def get_name(self):
177 return self.name
178
179 def get_num_cpus(self):
180 return self.num_cpus
181
182class Task(object):
183 """Represents a task, including the set of jobs that were run under
184 this task."""
185
186 def __init__(self, name, job_list=[]):
187 self.name = name
188 self.jobs = {}
189 self.task_no = None
190 self.schedule = None
191 for job in job_list:
192 self.add_job(job)
193
194 def add_job(self, job):
195 if job.job_no in self.jobs:
196 raise ScheduleError("a job is already being released at this time for this task")
197 self.jobs[job.job_no] = job
198 job.task = self
199
200 def get_schedule(self):
201 return self.schedule
202
203 def get_jobs(self):
204 return self.jobs
205
206 def get_task_no(self):
207 return self.task_no
208
209 def get_name(self):
210 return self.name
211
212class Job(object):
213 """Represents a job, including everything that happens related to the job"""
214 def __init__(self, job_no, event_list=[]):
215 self.job_no = job_no
216 self.events = {}
217 self.task = None
218 for event in event_list:
219 self.add_event(event)
220
221 def add_event(self, event):
222 if event.time not in self.events:
223 self.events[event.time] = []
224 self.events[event.time].append(event)
225 event.job = self
226
227 def get_events(self):
228 return self.events
229
230 def get_task(self):
231 return self.task
232
233 def get_job_no(self):
234 return self.job_no
235
236class DummyEvent(object):
237 """Represents some event that occurs, but might not actually be
238 a full-fledged ``event'' in the schedule. It might instead be a dummy
239 event added by the application to speed things up or keep track of
240 something. Such an event won't be added to the schedule tree, but
241 might appear in the time slot array."""
242
243 def __init__(self, time, cpu):
244 self.time = time
245 self.cpu = cpu
246 self.job = None
247 self.layer = None
248
249 def __str__(self):
250 return '[Dummy Event]'
251
252 def get_time(self):
253 return self.time
254
255 def get_cpu(self):
256 return self.cpu
257
258 def get_job(self):
259 return self.job
260
261 def get_layer(self):
262 return self.layer
263
264 def render(self, graph, layer, prev_events):
265 """Method that the visualizer calls to tell the event to render itself
266 Obviously only implemented by subclasses (actual event types)"""
267 raise NotImplementdError
268
269class Event(DummyEvent):
270 """Represents an event that occurs while a job is running (e.g. get scheduled
271 on a CPU, block, ...)"""
272 NO_CPU = -1
273 NUM_DEC_PLACES = 2
274
275 def __init__(self, time, cpu):
276 super(Event, self).__init__(time, cpu)
277 self.erroneous = False
278 self.selected = False
279
280 def __str__(self):
281 return '[Event]'
282
283 def _common_str(self):
284 job = self.get_job()
285 task = job.get_task()
286 return ' for task ' + str(task.get_name()) + ': (TASK, JOB)=' + str((task.get_task_no(), \
287 job.get_job_no())) + ', CPU=' + str(self.get_cpu())
288
289 def is_erroneous(self):
290 """An erroneous event is where something with the event is not quite right,
291 something significantly wrong that we don't have logical information telling
292 us how we should render the event."""
293 return self.erroneous
294
295 def is_selected(self):
296 """Returns whether the event has been selected by the user. (needed for rendering)"""
297 return self.selected
298
299 def set_selected(self, sel):
300 """Sets the event's state to selected."""
301 self.selected = sel
302
303 def scan(self, cur_cpu, switches):
304 """Part of the procedure that walks through all the events and sets
305 some parameters that are unknown at first. For instance, a SwitchAwayEvent
306 should know when the previous corresponding SwitchToEvent occurred, but
307 the data does not tell us this, so we have to figure that out on our own
308 by scanning through the events. ``cur_cpu'' gives the current CPU at this
309 time in the scan, and ``switches'' gives the last time a certain switch
310 (e.g. SwitchToEvent, InversionStartEvent) occurred"""
311
312 self.get_job().get_task().get_schedule().get_time_slot_array().add_event_to_time_slot(self)
313
314class ErrorEvent(Event):
315 pass
316
317class SuspendEvent(Event):
318 def __init__(self, time, cpu):
319 super(SuspendEvent, self).__init__(time, cpu)
320 self.layer = Canvas.MIDDLE_LAYER
321
322 def __str__(self):
323 return 'Suspend' + self._common_str() + ', TIME=' + util.format_float(self.get_time(), Event.NUM_DEC_PLACES)
324
325 def scan(self, cur_cpu, switches):
326 if self.get_cpu() != cur_cpu[0]:
327 self.erroneous = True
328 print "suspending on a CPU different from the CPU we are on!"
329 super(SuspendEvent, self).scan(cur_cpu, switches)
330
331 def render(self, graph, layer, prev_events):
332 if layer == self.layer:
333 prev_events[self] = None
334 graph.draw_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
335 self.get_cpu(), self.is_selected())
336 graph.add_sel_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
337 self.get_cpu(), self)
338
339class ResumeEvent(Event):
340 def __init__(self, time, cpu):
341 super(ResumeEvent, self).__init__(time, cpu)
342 self.layer = Canvas.MIDDLE_LAYER
343
344 def __str__(self):
345 return 'Resume' + self._common_str() + ', TIME=' + util.format_float(self.get_time(), Event.NUM_DEC_PLACES)
346
347 def scan(self, cur_cpu, switches):
348 if cur_cpu[0] != Event.NO_CPU and cur_cpu[0] != self.get_cpu():
349 self.erroneous = True
350 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)
352
353 def render(self, graph, layer, prev_events):
354 if layer == self.layer:
355 prev_events[self] = None
356 graph.draw_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
357 self.get_cpu(), self.is_selected())
358 graph.add_sel_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
359 self.get_cpu(), self)
360
361class CompleteEvent(Event):
362 def __init__(self, time, cpu):
363 super(CompleteEvent, self).__init__(time, cpu)
364 self.layer = Canvas.TOP_LAYER
365
366 def __str__(self):
367 return 'Complete' + self._common_str() + ', TIME=' + util.format_float(self.get_time(), Event.NUM_DEC_PLACES)
368
369 def scan(self, cur_cpu, switches):
370 super(CompleteEvent, self).scan(cur_cpu, switches)
371
372 def render(self, graph, layer, prev_events):
373 if layer == Canvas.TOP_LAYER:
374 prev_events[self] = None
375 graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
376 self.get_cpu(), self.is_selected())
377 graph.add_sel_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
378 self.get_cpu(), self)
379
380
381class SwitchAwayEvent(Event):
382 def __init__(self, time, cpu):
383 super(SwitchAwayEvent, self).__init__(time, cpu)
384 self.layer = Canvas.BOTTOM_LAYER
385
386 def __str__(self):
387 if self.corresp_start_event is None:
388 return 'Switch Away (w/o Switch To)' + self._common_str() + 'TIME=' \
389 + self.get_time()
390 return str(self.corresp_start_event)
391
392 def scan(self, cur_cpu, switches):
393 old_cur_cpu = cur_cpu[0]
394
395 self.corresp_start_event = switches[SwitchToEvent]
396
397 cur_cpu[0] = Event.NO_CPU
398 switches[SwitchToEvent] = None
399
400 if self.corresp_start_event is not None:
401 self.corresp_start_event.corresp_end_event = self
402
403 if self.get_cpu() != old_cur_cpu:
404 self.erroneous = True
405 print "switching away from a CPU different from the CPU we are currently on"
406 if self.corresp_start_event is None:
407 self.erroneous = True
408 print "switch away was not matched by a corresponding switch to"
409 elif self.get_time() < self.corresp_start_event.get_time():
410 self.erroneous = True
411 print "switching away from a processor before we switched to it?!"
412
413 super(SwitchAwayEvent, self).scan(cur_cpu, switches)
414
415 def render(self, graph, layer, prev_events):
416 if self.corresp_start_event is None or self.corresp_start_event in prev_events:
417 return # erroneous switch away or already rendered
418 self.corresp_start_event.render(graph, layer, prev_events)
419
420class SwitchToEvent(Event):
421 def __init__(self, time, cpu):
422 super(SwitchToEvent, self).__init__(time, cpu)
423 self.layer = Canvas.BOTTOM_LAYER
424
425 def __str__(self):
426 if self.corresp_end_event is None:
427 return 'Switch To (w/o Switch Away)' + self._common_str() + ', TIME=' \
428 + self.get_time()
429 return 'Scheduled' + self._common_str() + ', START=' \
430 + util.format_float(self.get_time(), Event.NUM_DEC_PLACES) \
431 + ', END=' + util.format_float(self.corresp_end_event.get_time(), Event.NUM_DEC_PLACES)
432
433 def scan(self, cur_cpu, switches):
434 old_cur_cpu = cur_cpu[0]
435 cur_cpu[0] = self.get_cpu()
436 switches[SwitchToEvent] = self
437 self.corresp_end_event = None
438
439 if old_cur_cpu != Event.NO_CPU:
440 self.erroneous = True
441 print "currently scheduled somewhere, can't switch to a CPU"
442
443 super(SwitchToEvent, self).scan(cur_cpu, switches)
444
445 def render(self, graph, layer, prev_events):
446 if self.is_erroneous():
447 return # erroneous switch to
448 if layer == Canvas.BOTTOM_LAYER:
449 prev_events[self] = None
450 cpu = self.get_cpu()
451 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(),
453 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
454 graph.add_sel_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
455 task_no, cpu, self)
456
457class ReleaseEvent(Event):
458 def __init__(self, time, cpu):
459 super(ReleaseEvent, self).__init__(time, cpu)
460 self.layer = Canvas.TOP_LAYER
461
462 def __str__(self):
463 return 'Release' + self._common_str() + ', TIME=' + util.format_float(self.get_time(), Event.NUM_DEC_PLACES)
464
465 def scan(self, cur_cpu, switches):
466 super(ReleaseEvent, self).scan(cur_cpu, switches)
467
468 def render(self, graph, layer, prev_events):
469 prev_events[self] = None
470 if layer == Canvas.TOP_LAYER:
471 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())
473 graph.add_sel_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
474 self)
475
476class DeadlineEvent(Event):
477 def __init__(self, time, cpu):
478 super(DeadlineEvent, self).__init__(time, cpu)
479 self.layer = Canvas.TOP_LAYER
480
481 def __str__(self):
482 return 'Deadline' + self._common_str() + ', TIME=' + util.format_float(self.get_time(), Event.NUM_DEC_PLACES)
483
484 def scan(self, cur_cpu, switches):
485 super(DeadlineEvent, self).scan(cur_cpu, switches)
486
487 def render(self, graph, layer, prev_events):
488 prev_events[self] = None
489 if layer == Canvas.TOP_LAYER:
490 graph.draw_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
491 self.get_job().get_job_no(), self.is_selected())
492 graph.add_sel_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
493 self)
494
495class InversionStartEvent(ErrorEvent):
496 def __init__(self, time):
497 super(InversionStartEvent, self).__init__(time, Event.NO_CPU)
498 self.layer = Canvas.BOTTOM_LAYER
499
500 def __str__(self):
501 if self.corresp_end_event is None:
502 print 'Inversion Start (w/o Inversion End)' + self._common_str() \
503 + ', TIME=' + util.format_float(self.get_time(), Event.NUM_DEC_PLACES)
504 return 'Priority Inversion' + self._common_str() + ', START=' \
505 + util.format_float(self.get_time(), Event.NUM_DEC_PLACES) \
506 + ', END=' + util.format_float(self.corresp_end_event.get_time(), Event.NUM_DEC_PLACES)
507
508 def scan(self, cur_cpu, switches):
509 switches[InversionStartEvent] = self
510 self.corresp_end_event = None
511
512 # the corresp_end_event should already be set
513 super(InversionStartEvent, self).scan(cur_cpu, switches)
514
515 def render(self, graph, layer, prev_events):
516 if layer == Canvas.BOTTOM_LAYER:
517 prev_events[self] = None
518 cpu = self.get_cpu()
519 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(),
521 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
522 graph.add_sel_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
523 task_no, cpu, self)
524
525class InversionEndEvent(ErrorEvent):
526 def __init__(self, time):
527 super(InversionEndEvent, self).__init__(time, Event.NO_CPU)
528 self.layer = Canvas.BOTTOM_LAYER
529
530 def __str__(self):
531 if self.corresp_start_event is None:
532 print 'Inversion End (w/o Inversion Start)' + self._common_str() \
533 + ', TIME=' + util.format_float(self.get_time(), Event.NUM_DEC_PLACES)
534
535 return str(self.corresp_start_event)
536
537 def scan(self, cur_cpu, switches):
538 self.corresp_start_event = switches[InversionStartEvent]
539
540 cur_cpu[0] = Event.NO_CPU
541 switches[InversionStartEvent] = None
542
543 if self.corresp_start_event is not None:
544 self.corresp_start_event.corresp_end_event = self
545
546 if self.corresp_start_event is None:
547 self.erroneous = True
548 print "inversion end was not matched by a corresponding inversion start"
549
550 super(InversionEndEvent, self).scan(cur_cpu, switches)
551
552 def render(self, graph, layer, prev_events):
553 if self.corresp_start_event is None or self.corresp_start_event in prev_events:
554 return # erroneous inversion end or already rendered
555 self.corresp_start_event.render(graph, layer, prev_events)
556
557class InversionDummy(DummyEvent):
558 def render(self, graph, layer, prev_events):
559 if self.corresp_start_event in prev_events:
560 return # we have already been rendered
561 self.corresp_start_event.render(graph, layer, prev_events)
562
563class IsRunningDummy(DummyEvent):
564 def render(self, graph, layer, prev_events):
565 if self.corresp_start_event in prev_events:
566 return # we have already been rendered
567 self.corresp_start_event.render(graph, layer, prev_events)
568
569EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, SwitchAwayEvent : None,
570 SwitchToEvent : None, ReleaseEvent : None, DeadlineEvent : None, IsRunningDummy : None,
571 InversionStartEvent : None, InversionEndEvent : None, InversionDummy : None}