From c4edd95d28b9212e09e8caca0d6b11937f453995 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 27 Jan 2011 00:51:17 -0500 Subject: Added support for visualizing arbitrary actions. --- unit_trace/sanitizer.py | 10 ++++ unit_trace/trace_reader.py | 14 +++-- unit_trace/viz/canvas.py | 63 +++++++++++++++++++++-- unit_trace/viz/convert.py | 31 +++++++---- unit_trace/viz/graph.py | 72 +++++++++++++++++++++++--- unit_trace/viz/schedule.py | 125 +++++++++++++++++++++++++++++++++++---------- 6 files changed, 263 insertions(+), 52 deletions(-) diff --git a/unit_trace/sanitizer.py b/unit_trace/sanitizer.py index 598379a..fc66170 100644 --- a/unit_trace/sanitizer.py +++ b/unit_trace/sanitizer.py @@ -14,6 +14,8 @@ def sanitizer(stream): job_2s_released = [] # list of tasks which have released their job 2s jobs_switched_to = [] + released = False + for record in stream: # Ignore records which are not events (e.g. the num_cpus record) @@ -21,6 +23,13 @@ def sanitizer(stream): yield record continue + if record.type_name == 'release': + released = released or True + + if record.type_name == 'action' and released: + yield record + continue + # All records with job < 2 are garbage if record.job < 2: continue @@ -50,4 +59,5 @@ def sanitizer(stream): if (record.pid,record.job) not in jobs_switched_to: record.job -= 1 + yield record diff --git a/unit_trace/trace_reader.py b/unit_trace/trace_reader.py index a4c3c05..fe5c03c 100644 --- a/unit_trace/trace_reader.py +++ b/unit_trace/trace_reader.py @@ -93,7 +93,6 @@ def trace_reader(files): # Keep pulling records as long as we have a buffer while len(file_iter_buff) > 0: - # Select the earliest record from those at the heads of the buffers earliest = -1 buff_to_refill = -1 @@ -120,7 +119,8 @@ def trace_reader(files): del file_iters[buff_to_refill] # Check for monotonically increasing time - if last_time is not None and earliest.when < last_time: + if last_time is not None and earliest.when != 0 and earliest.when < last_time: + print("FATAL Old: %s, New: %s" % (last_time, earliest.when)) exit("FATAL ERROR: trace_reader.py: out-of-order record produced") else: last_time = earliest.when @@ -203,6 +203,12 @@ class StHeader: keys = ['type','cpu','pid','job'] message = 'The header.' +class StActionData: + format = 'Qb' + formatStr = struct.Struct(StHeader.format + format) + keys = StHeader.keys + ['when','action'] + message = 'An action was performed.' + class StNameData: format = '16s' formatStr = struct.Struct(StHeader.format + format) @@ -269,7 +275,7 @@ class StSysReleaseData: def _get_type(type_num): types = [None,StNameData,StParamData,StReleaseData,StAssignedData, StSwitchToData,StSwitchAwayData,StCompletionData,StBlockData, - StResumeData,StSysReleaseData] + StResumeData,StActionData,StSysReleaseData] if type_num > len(types)-1 or type_num < 1: raise Exception return types[type_num] @@ -278,5 +284,5 @@ def _get_type(type_num): # programmers of other modules) def _get_type_name(type_num): type_names = [None,"name","params","release","assign","switch_to", - "switch_away","completion","block","resume","sys_release"] + "switch_away","completion","block","resume","action","sys_release"] return type_names[type_num] diff --git a/unit_trace/viz/canvas.py b/unit_trace/viz/canvas.py index badebe9..2c977ae 100644 --- a/unit_trace/viz/canvas.py +++ b/unit_trace/viz/canvas.py @@ -521,14 +521,44 @@ class Canvas(object): self.add_sel_region(SelectableRegion(x - big_arrowhead_height / Canvas.SQRT3, y, 2.0 * big_arrowhead_height / Canvas.SQRT3, height, event)) + def draw_action_symbol(self, item, action, x, y, height, selected): + """Draws a release arrow: x, y should give the top (northernmost + point) of the arrow. The height includes the arrowhead.""" + + color = {False : GraphFormat.BORDER_COLOR, + True : GraphFormat.HIGHLIGHT_COLOR}[selected] + + colors = [self.get_bar_pattern(item).get_color_list()[0], color] + draw_funcs = [self.__class__.fill_polyline, self.__class__.draw_polyline] + + for i in range(0, 2): + color = colors[i] + draw_func = draw_funcs[i] + + draw_func(self, [(x, y), (x - height / Canvas.SQRT3, \ + y - height), \ + (x + height / Canvas.SQRT3, \ + y - height), \ + (x, y)], color, GraphFormat.BORDER_THICKNESS) + + self.draw_label(str(action), x, y - height / 2 - .1 * height, + GraphFormat.DEF_FOPTS_LABEL, + AlignMode.CENTER, AlignMode.CENTER, True) + + def add_sel_action_symbol(self, x, y, height, event): + self.add_sel_region(SelectableRegion(x - height / Canvas.SQRT3, + y - height, 2.0 * height / Canvas.SQRT3, height, event)) + def draw_release_arrow_small(self, x, y, height, selected): """Draws a small release arrow (most likely coming off the x-axis, although this method doesn't enforce this): x, y should give the top of the arrow""" small_arrowhead_height = GraphFormat.SMALL_ARROWHEAD_FACTOR * height - color = {False : GraphFormat.BORDER_COLOR, True : GraphFormat.HIGHLIGHT_COLOR}[selected] + color = {False : GraphFormat.BORDER_COLOR, + True : GraphFormat.HIGHLIGHT_COLOR}[selected] - self.draw_line((x, y), (x - small_arrowhead_height, y + small_arrowhead_height), \ + self.draw_line((x, y), + (x - small_arrowhead_height, y + small_arrowhead_height), color, GraphFormat.BORDER_THICKNESS) self.draw_line((x, y), (x + small_arrowhead_height, y + small_arrowhead_height), \ color, GraphFormat.BORDER_THICKNESS) @@ -754,13 +784,35 @@ class CairoCanvas(Canvas): self.surface.ctx.set_line_width(thickness * self.scale) self.surface.ctx.stroke() + def draw_circle(self, x, y, radius, fill_color, border_color, + thickness, do_snap=True): + p = self.surface.get_real_coor(x, y) + if do_snap: + p = (snap(p[0]), snap(p[1])) + + self.surface.ctx.save() + self.surface.ctx.arc(p[0], p[1], radius, 0.0, 2 * math.pi) + self.surface.ctx.set_source_rgb(border_color[0], + border_color[1], + border_color[2]) + self.surface.ctx.set_line_width(thickness * self.scale) + self.surface.ctx.stroke() + self.surface.ctx.arc(p[0], p[1], radius, 0.0, 2 * math.pi) + self.surface.ctx.set_source_rgb(fill_color[0], + fill_color[1], + fill_color[2]) + self.surface.ctx.fill() + self.surface.ctx.restore() + def _polyline_common(self, coor_list, color, thickness, do_snap=True): - real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) for coor in coor_list] + real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) \ + for coor in coor_list] self.surface.ctx.move_to(real_coor_list[0][0], real_coor_list[0][1]) if do_snap: for i in range(0, len(real_coor_list)): - real_coor_list[i] = (snap(real_coor_list[i][0]), snap(real_coor_list[i][1])) + real_coor_list[i] = (snap(real_coor_list[i][0]), + snap(real_coor_list[i][1])) for coor in real_coor_list[1:]: self.surface.ctx.line_to(coor[0], coor[1]) @@ -830,7 +882,8 @@ class CairoCanvas(Canvas): f_descent_factor, width_factor, f_height_factor, do_snap) - def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, do_snap=True): + def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, + halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, do_snap=True): """Draws a label with the given parameters, with the given horizontal and vertical justification.""" actual_x, actual_y, width, height, f_height = self.get_label_dim(text, x, y, fopts, halign, valign, do_snap) diff --git a/unit_trace/viz/convert.py b/unit_trace/viz/convert.py index d19bc73..278a541 100644 --- a/unit_trace/viz/convert.py +++ b/unit_trace/viz/convert.py @@ -16,14 +16,17 @@ def get_type_num(type): return nums[type] def _get_job_from_record(sched, record): - tname = _pid_to_task_name(record.pid) - job_no = record.job - if tname not in sched.get_tasks(): - sched.add_task(Task(tname, [])) - if job_no not in sched.get_tasks()[tname].get_jobs(): - sched.get_tasks()[tname].add_job(Job(job_no, [])) - job = sched.get_tasks()[tname].get_jobs()[job_no] - return job + if record.pid == 0: + return None + else: + tname = _pid_to_task_name(record.pid) + job_no = record.job + if tname not in sched.get_tasks(): + sched.add_task(Task(tname, [])) + if job_no not in sched.get_tasks()[tname].get_jobs(): + sched.get_tasks()[tname].add_job(Job(job_no, [])) + job = sched.get_tasks()[tname].get_jobs()[job_no] + return job def convert_trace_to_schedule(stream): """The main function of interest in this module. Coverts a stream of records @@ -45,12 +48,20 @@ def convert_trace_to_schedule(stream): if not hasattr(record, 'deadline'): record.deadline = None + # This whole method should be refactored for this posibility + if job is None: + if record.type_name == "action": + event = ActionEvent(record.when, cpu, record.action) + event.set_schedule(sched) + sched.add_jobless(event) + continue + actions = { 'name' : (noop), 'params' : (noop), 'release' : (lambda : (job.add_event(ReleaseEvent(record.when, cpu)), - job.add_event(DeadlineEvent(record.deadline, cpu)))), + job.add_event(DeadlineEvent(record.deadline, cpu)))), 'switch_to' : (lambda : job.add_event(SwitchToEvent(record.when, cpu))), 'switch_away' : (lambda : @@ -62,6 +73,8 @@ def convert_trace_to_schedule(stream): job.add_event(SuspendEvent(record.when, cpu))), 'resume' : (lambda : job.add_event(ResumeEvent(record.when, cpu))), + 'action' : (lambda : + job.add_event(ActionEvent(record.when, cpu, record.action))), 'sys_release' : (noop) } diff --git a/unit_trace/viz/graph.py b/unit_trace/viz/graph.py index 73c4ce4..e157335 100644 --- a/unit_trace/viz/graph.py +++ b/unit_trace/viz/graph.py @@ -8,11 +8,12 @@ than the canvas classes (time and task/cpu number rather than plain coordinates) update themselves, unlike the Canvas which can only overwrite itself.""" class Graph(object): - DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), Pattern([(0.9, 0.7, 0.0)]), - Pattern([(0.0, 0.0, 0.8)]), Pattern([(0.0, 0.2, 0.9)]), Pattern([(0.0, 0.6, 0.6)]), + DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), + Pattern([(0.9, 0.7, 0.0)]), Pattern([(0.0, 0.0, 0.8)]), + Pattern([(0.0, 0.2, 0.9)]), Pattern([(0.0, 0.6, 0.6)]), Pattern([(0.75, 0.75, 0.75)])] - DEF_ITEM_CLIST = [(0.3, 0.0, 0.0), (0.0, 0.3, 0.0), (0.0, 0.0, 0.3), (0.3, 0.3, 0.0), (0.0, 0.3, 0.3), - (0.3, 0.0, 0.3)] + DEF_ITEM_CLIST = [(0.3, 0.0, 0.0), (0.0, 0.3, 0.0), (0.0, 0.0, 0.3), + (0.3, 0.3, 0.0), (0.0, 0.3, 0.3), (0.3, 0.0, 0.3)] def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, top_item_list, attrs=GraphFormat(), item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST): @@ -166,9 +167,12 @@ class Graph(object): """get x so that x is at instant ``time'' on the graph""" return self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + 1.0 * (time - self.start_time) / self.attrs.time_per_maj * self.attrs.maj_sep + def get_item_yorigin(self, item_no): + return self.origin[1] - self._get_y_axis_height() + self.attrs.y_item_size * item_no; + def get_item_ypos(self, item_no): """get y so that y is where the top of a bar would be in item #n's area""" - return self.origin[1] - self._get_y_axis_height() + self.attrs.y_item_size * (item_no + 0.5 - GraphFormat.BAR_SIZE_FACTOR / 2.0) + return self.get_item_yorigin(item_no) + self.attrs.y_item_size * (0.5 - GraphFormat.BAR_SIZE_FACTOR / 2.0) def _get_bar_width(self, start_time, end_time): return 1.0 * (end_time - start_time) / self.attrs.time_per_maj * self.attrs.maj_sep @@ -256,6 +260,8 @@ class Graph(object): for event in sched.get_time_slot_array().get_events(slots, self.list_type, schedule.EVENT_LIST): events_to_render[event.get_layer()][event] = None + for event in sched.get_jobless(): + events_to_render[event.get_layer()][event] = None return events_to_render @@ -403,6 +409,16 @@ class Graph(object): a certain time.""" raise NotImplementedError + def draw_action_symbol_at_time(self, time, task_no, cpu_no, action, + job_no, selected=False): + """Draws an action symbol at a certain time for some task and job""" + raise NotImplementedError + + def add_sel_action_symbol_at_time(self, time, task_no, cpu_no, event): + """Same as above, except instead of drawing adds a selectable region at + a certain time.""" + raise NotImplementedError + def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None): """Draws a bar over a certain time period for some task, optionally labelling it.""" raise NotImplementedError @@ -526,6 +542,33 @@ class TaskGraph(Graph): self.canvas.add_sel_deadline_arrow_big(x, y, height, event) + def draw_action_symbol_at_time(self, time, task_no, cpu_no, action, + job_no=None, selected=False): + x = self.get_time_xpos(time) + y = None + + if task_no != -1: + y = self.get_item_ypos(task_no) + else: + y = self.origin[1] + + height = 1.5 * (self.get_item_ypos(0) - self.get_item_yorigin(0)) + + self.canvas.draw_action_symbol(cpu_no, action, x, y, height, selected) + + def add_sel_action_symbol_at_time(self, time, task_no, cpu_no, event): + x = self.get_time_xpos(time) + y = None + + if task_no != -1: + y = self.get_item_ypos(task_no) + else: + y = self.origin[1] + + height = 1.5 * (self.get_item_ypos(0) - self.get_item_yorigin(0)) + + self.canvas.add_sel_action_symbol(x, y, height, event) + def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): if start_time > end_time: raise ValueError("Litmus is not a time machine") @@ -699,10 +742,27 @@ class CpuGraph(Graph): height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR x = self.get_time_xpos(time) - y = self.origin[1] - height + y = self.get_item_ypos(task_no) self.canvas.add_sel_deadline_arrow_small(x, y, height, event) + def draw_action_symbol_at_time(self, time, task_no, cpu_no, action, + job_no=None, selected=False): + x = self.get_time_xpos(time) + y = self.get_item_ypos(cpu_no) + + height = 1.5 * (y - self.get_item_yorigin(cpu_no)) + + self.canvas.draw_action_symbol(task_no, action, x, y, height, selected) + + def add_sel_action_symbol_at_time(self, time, task_no, cpu_no, event): + x = self.get_time_xpos(time) + y = self.get_item_ypos(cpu_no) + + height = 1.5 * (y - self.get_item_yorigin(cpu_no)) + + self.canvas.add_sel_action_symbol(x, y, height, event) + def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): if start_time > end_time: raise ValueError("Litmus is not a time machine") diff --git a/unit_trace/viz/schedule.py b/unit_trace/viz/schedule.py index 542524b..fcce10d 100644 --- a/unit_trace/viz/schedule.py +++ b/unit_trace/viz/schedule.py @@ -181,6 +181,7 @@ class Schedule(object): self.time_slot_array = None self.cur_task_no = 0 self.num_cpus = num_cpus + self.jobless = [] for task in task_list: self.add_task(task) @@ -264,6 +265,9 @@ class Schedule(object): task.task_no = self.cur_task_no self.cur_task_no += 1 + def add_jobless(self, event): + self.jobless.append(event) + def sort_task_nos_numeric(self): # sort task numbers by the numeric value of the task names. nums = [] @@ -275,6 +279,9 @@ class Schedule(object): for no, task in enumerate(nums): self.tasks[task[1]].task_no = no + def get_jobless(self): + return self.jobless + def get_tasks(self): return self.tasks @@ -418,12 +425,12 @@ class DummyEvent(object): event added by the application to speed things up or keep track of something. Such an event won't be added to the schedule tree, but might appear in the time slot array.""" - def __init__(self, time, cpu): self.time = time self.cpu = cpu self.job = None self.layer = None + self.saved_schedule = None def get_time(self): return self.time @@ -431,11 +438,24 @@ class DummyEvent(object): def get_cpu(self): return self.cpu + # Refactor, shouldn't depend on job def get_schedule(self): - return self.get_task().get_schedule() + if self.saved_schedule is not None: + return self.saved_schedule + elif self.get_task() is not None: + return self.get_task().get_schedule() + else: + return None + + # Needed for events not assigned to specific tasks + def set_schedule(self, schedule): + self.saved_schedule = schedule def get_task(self): - return self.get_job().get_task() + if self.get_job() is not None: + return self.get_job().get_task() + else: + return None def get_job(self): return self.job @@ -476,21 +496,33 @@ class Event(DummyEvent): ' ' + str(unit) def str_long(self, unit): - """Prints the event as a string, in ``long'' form.""" - return 'Event Information\n-----------------\n' + \ - 'Event Type: ' + self.get_name() + \ - '\nTask Name: ' + str(self.get_job().get_task().get_name()) + \ - '\n(Task no., Job no.): ' + str((self.get_job().get_task().get_task_no(), \ - self.get_job().get_job_no())) + \ - '\nCPU: ' + str(self.get_cpu()) + \ - '\nTime: ' + _format_time(self.get_time(), unit) + \ - '\n\n' + self.get_job().str_long(unit) + if self.get_job() is not None: + """Prints the event as a string, in ``long'' form.""" + return 'Event Information\n-----------------\n' + \ + 'Event Type: ' + self.get_name() + \ + '\nTask Name: ' + str(self.get_job().get_task().get_name()) + \ + '\n(Task no., Job no.): ' + str((self.get_job().get_task().get_task_no(), \ + self.get_job().get_job_no())) + \ + '\nCPU: ' + str(self.get_cpu()) + \ + '\nTime: ' + _format_time(self.get_time(), unit) + \ + '\n\n' + self.get_job().str_long(unit) + else: + """Prints the event as a string, in ``long'' form.""" + return 'Event Information\n-----------------\n' + \ + 'Event Type: ' + self.get_name() + \ + '\nTask Name: None' + \ + '\nCPU: ' + str(self.get_cpu()) + \ + '\nTime: ' + _format_time(self.get_time(), unit) def _common_str(self): - job = self.get_job() - task = job.get_task() - return ' for task ' + str(task.get_name()) + ': (TASK, JOB)=' + str((task.get_task_no(), \ - job.get_job_no())) + ', CPU=' + str(self.get_cpu()) + if self.get_job() is not None: + job = self.get_job() + task = job.get_task() + return ' for task ' + str(task.get_name()) + ': (TASK, JOB)=' + \ + str((task.get_task_no(), job.get_job_no())) + \ + ', CPU=' + str(self.get_cpu()) + else: + return ', Cpu=' + str(self.get_cpu()) def is_erroneous(self): """An erroneous event is where something with the event is not quite right, @@ -507,16 +539,20 @@ class Event(DummyEvent): time in the scan, and ``switches'' gives the last time a certain switch (e.g. SwitchToEvent, InversionStartEvent) occurred""" time = self.get_time() + sched = self.get_schedule() - if sched.start is None or time < sched.start: - sched.start = time - if sched.end is None or time > sched.end: - sched.end = time - - if item_nos is None: - item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(), - TimeSlotArray.CPU_LIST : self.get_cpu() } - sched.get_time_slot_array().add_event_to_time_slot(self, item_nos) + + if sched is not None: + if sched.start is None or time < sched.start: + sched.start = time + if sched.end is None or time > sched.end: + sched.end = time + + if item_nos is None: + item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(), + TimeSlotArray.CPU_LIST : self.get_cpu() } + sched.get_time_slot_array().add_event_to_time_slot(self, item_nos) + self.fill_span_event_from_end() def fill_span_event_from_start(self): @@ -843,9 +879,42 @@ class DeadlineEvent(Event): graph.add_sel_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), self) else: - graph.draw_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), - self.get_job().get_job_no(), self.is_selected()) + graph.draw_deadline_arrow_at_time(self.get_time(), + self.get_job().get_task().get_task_no(), + self.get_job().get_job_no(), self.is_selected()) +class ActionEvent(Event): + def __init__(self, time, cpu, action): + super(ActionEvent, self).__init__(time, cpu) + self.layer = Canvas.TOP_LAYER + self.action = int(action) + + def get_name(self): + return 'Action' + + def scan(self, cur_cpu, switches): + item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(), + TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO } + super(ActionEvent, self).scan(cur_cpu, switches, item_nos) + + def render(self, graph, layer, prev_events, selectable=False): + prev_events[self] = None + if layer == Canvas.TOP_LAYER: + + # TODO: need a more official way of doing this + task_no = -1 + job_no = -1 + if self.get_job() is not None: + task_no = self.get_job().get_task().get_task_no() + job_no = self.get_job().get_job_no() + + if selectable: + graph.add_sel_action_symbol_at_time(self.get_time(), task_no, + self.get_cpu(), self) + else: + graph.draw_action_symbol_at_time(self.get_time(), task_no, + self.get_cpu(), self.action, + job_no, self.is_selected()) class InversionStartEvent(StartSpanEvent): def __init__(self, time): @@ -1041,7 +1110,7 @@ EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, SwitchAwayEvent : None, SwitchToEvent : None, ReleaseEvent : None, DeadlineEvent : None, IsRunningDummy : None, InversionStartEvent : None, InversionEndEvent : None, - InversionDummy : None, TaskDummy : None, CPUDummy : None} + InversionDummy : None, TaskDummy : None, CPUDummy : None, ActionEvent: None} SPAN_START_EVENTS = { SwitchToEvent : IsRunningDummy, InversionStartEvent : InversionDummy } SPAN_END_EVENTS = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy} -- cgit v1.2.2