arm: Don't truncate 16-bit ASIDs to 8 bits
[gem5.git] / util / minorview / view.py
1 # Copyright (c) 2013 ARM Limited
2 # All rights reserved
3 #
4 # The license below extends only to copyright in the software and shall
5 # not be construed as granting a license to any other intellectual
6 # property including but not limited to intellectual property relating
7 # to a hardware implementation of the functionality of the software
8 # licensed hereunder. You may use the software subject to the license
9 # terms below provided that you ensure that this notice is replicated
10 # unmodified and in its entirety in all distributions of the software,
11 # modified or unmodified, in source code or in binary form.
12 #
13 # Redistribution and use in source and binary forms, with or without
14 # modification, are permitted provided that the following conditions are
15 # met: redistributions of source code must retain the above copyright
16 # notice, this list of conditions and the following disclaimer;
17 # redistributions in binary form must reproduce the above copyright
18 # notice, this list of conditions and the following disclaimer in the
19 # documentation and/or other materials provided with the distribution;
20 # neither the name of the copyright holders nor the names of its
21 # contributors may be used to endorse or promote products derived from
22 # this software without specific prior written permission.
23 #
24 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 #
36 # Authors: Andrew Bardsley
37
38 import pygtk
39 pygtk.require('2.0')
40 import gtk
41 import gobject
42 import cairo
43 import re
44
45 from point import Point
46 import parse
47 import colours
48 import model
49 from model import Id, BlobModel, BlobDataSelect, special_state_chars
50 import blobs
51
52 class BlobView(object):
53 """The canvas view of the pipeline"""
54 def __init__(self, model):
55 # A unit blob will appear at size blobSize inside a space of
56 # size pitch.
57 self.blobSize = Point(45.0, 45.0)
58 self.pitch = Point(60.0, 60.0)
59 self.origin = Point(50.0, 50.0)
60 # Some common line definitions to cut down on arbitrary
61 # set_line_widths
62 self.thickLineWidth = 10.0
63 self.thinLineWidth = 4.0
64 self.midLineWidth = 6.0
65 # The scale from the units of pitch to device units (nominally
66 # pixels for 1.0 to 1.0
67 self.masterScale = Point(1.0,1.0)
68 self.model = model
69 self.fillColour = colours.emptySlotColour
70 self.timeIndex = 0
71 self.time = 0
72 self.positions = []
73 self.controlbar = None
74 # The sequence number selector state
75 self.dataSelect = BlobDataSelect()
76 # Offset of this view's time from self.time used for miniviews
77 # This is actually an offset of the index into the array of times
78 # seen in the event file)
79 self.timeOffset = 0
80 # Maximum view size for initial window mapping
81 self.initialHeight = 600.0
82
83 # Overlays are speech bubbles explaining blob data
84 self.overlays = []
85
86 self.da = gtk.DrawingArea()
87 def draw(arg1, arg2):
88 self.redraw()
89 self.da.connect('expose_event', draw)
90
91 # Handy offsets from the blob size
92 self.blobIndent = (self.pitch - self.blobSize).scale(0.5)
93 self.blobIndentFactor = self.blobIndent / self.pitch
94
95 def add_control_bar(self, controlbar):
96 """Add a BlobController to this view"""
97 self.controlbar = controlbar
98
99 def draw_to_png(self, filename):
100 """Draw the view to a PNG file"""
101 surface = cairo.ImageSurface(
102 cairo.FORMAT_ARGB32,
103 self.da.get_allocation().width,
104 self.da.get_allocation().height)
105 cr = gtk.gdk.CairoContext(cairo.Context(surface))
106 self.draw_to_cr(cr)
107 surface.write_to_png(filename)
108
109 def draw_to_cr(self, cr):
110 """Draw to a given CairoContext"""
111 cr.set_source_color(colours.backgroundColour)
112 cr.set_line_width(self.thickLineWidth)
113 cr.paint()
114 cr.save()
115 cr.scale(*self.masterScale.to_pair())
116 cr.translate(*self.origin.to_pair())
117
118 positions = [] # {}
119
120 # Draw each blob
121 for blob in self.model.blobs:
122 blob_event = self.model.find_unit_event_by_time(
123 blob.unit, self.time)
124
125 cr.save()
126 pos = blob.render(cr, self, blob_event, self.dataSelect,
127 self.time)
128 cr.restore()
129 if pos is not None:
130 (centre, size) = pos
131 positions.append((blob, centre, size))
132
133 # Draw all the overlays over the top
134 for overlay in self.overlays:
135 overlay.show(cr)
136
137 cr.restore()
138
139 return positions
140
141 def redraw(self):
142 """Redraw the whole view"""
143 buffer = cairo.ImageSurface(
144 cairo.FORMAT_ARGB32,
145 self.da.get_allocation().width,
146 self.da.get_allocation().height)
147
148 cr = gtk.gdk.CairoContext(cairo.Context(buffer))
149 positions = self.draw_to_cr(cr)
150
151 # Assume that blobs are in order for depth so we want to
152 # hit the frontmost blob first if we search by position
153 positions.reverse()
154 self.positions = positions
155
156 # Paint the drawn buffer onto the DrawingArea
157 dacr = self.da.window.cairo_create()
158 dacr.set_source_surface(buffer, 0.0, 0.0)
159 dacr.paint()
160
161 buffer.finish()
162
163 def set_time_index(self, time):
164 """Set the time index for the view. A time index is an index into
165 the model's times array of seen event times"""
166 self.timeIndex = time + self.timeOffset
167 if len(self.model.times) != 0:
168 if self.timeIndex >= len(self.model.times):
169 self.time = self.model.times[len(self.model.times) - 1]
170 else:
171 self.time = self.model.times[self.timeIndex]
172 else:
173 self.time = 0
174
175 def get_pic_size(self):
176 """Return the size of ASCII-art picture of the pipeline scaled by
177 the blob pitch"""
178 return (self.origin + self.pitch *
179 (self.model.picSize + Point(1.0,1.0)))
180
181 def set_da_size(self):
182 """Set the DrawingArea size after scaling"""
183 self.da.set_size_request(10 , int(self.initialHeight))
184
185 class BlobController(object):
186 """The controller bar for the viewer"""
187 def __init__(self, model, view,
188 defaultEventFile="", defaultPictureFile=""):
189 self.model = model
190 self.view = view
191 self.playTimer = None
192 self.filenameEntry = gtk.Entry()
193 self.filenameEntry.set_text(defaultEventFile)
194 self.pictureEntry = gtk.Entry()
195 self.pictureEntry.set_text(defaultPictureFile)
196 self.timeEntry = None
197 self.defaultEventFile = defaultEventFile
198 self.startTime = None
199 self.endTime = None
200
201 self.otherViews = []
202
203 def make_bar(elems):
204 box = gtk.HBox(homogeneous=False, spacing=2)
205 box.set_border_width(2)
206 for widget, signal, handler in elems:
207 if signal is not None:
208 widget.connect(signal, handler)
209 box.pack_start(widget, False, True, 0)
210 return box
211
212 self.timeEntry = gtk.Entry()
213
214 t = gtk.ToggleButton('T')
215 t.set_active(False)
216 s = gtk.ToggleButton('S')
217 s.set_active(True)
218 p = gtk.ToggleButton('P')
219 p.set_active(True)
220 l = gtk.ToggleButton('L')
221 l.set_active(True)
222 f = gtk.ToggleButton('F')
223 f.set_active(True)
224 e = gtk.ToggleButton('E')
225 e.set_active(True)
226
227 # Should really generate this from above
228 self.view.dataSelect.ids = set("SPLFE")
229
230 self.bar = gtk.VBox()
231 self.bar.set_homogeneous(False)
232
233 row1 = make_bar([
234 (gtk.Button('Start'), 'clicked', self.time_start),
235 (gtk.Button('End'), 'clicked', self.time_end),
236 (gtk.Button('Back'), 'clicked', self.time_back),
237 (gtk.Button('Forward'), 'clicked', self.time_forward),
238 (gtk.Button('Play'), 'clicked', self.time_play),
239 (gtk.Button('Stop'), 'clicked', self.time_stop),
240 (self.timeEntry, 'activate', self.time_set),
241 (gtk.Label('Visible ids:'), None, None),
242 (t, 'clicked', self.toggle_id('T')),
243 (gtk.Label('/'), None, None),
244 (s, 'clicked', self.toggle_id('S')),
245 (gtk.Label('.'), None, None),
246 (p, 'clicked', self.toggle_id('P')),
247 (gtk.Label('/'), None, None),
248 (l, 'clicked', self.toggle_id('L')),
249 (gtk.Label('/'), None, None),
250 (f, 'clicked', self.toggle_id('F')),
251 (gtk.Label('.'), None, None),
252 (e, 'clicked', self.toggle_id('E')),
253 (self.filenameEntry, 'activate', self.load_events),
254 (gtk.Button('Reload'), 'clicked', self.load_events)
255 ])
256
257 self.bar.pack_start(row1, False, True, 0)
258 self.set_time_index(0)
259
260 def toggle_id(self, id):
261 """One of the sequence number selector buttons has been toggled"""
262 def toggle(button):
263 if button.get_active():
264 self.view.dataSelect.ids.add(id)
265 else:
266 self.view.dataSelect.ids.discard(id)
267
268 # Always leave one thing visible
269 if len(self.view.dataSelect.ids) == 0:
270 self.view.dataSelect.ids.add(id)
271 button.set_active(True)
272 self.view.redraw()
273 return toggle
274
275 def set_time_index(self, time):
276 """Set the time index in the view"""
277 self.view.set_time_index(time)
278
279 for view in self.otherViews:
280 view.set_time_index(time)
281 view.redraw()
282
283 self.timeEntry.set_text(str(self.view.time))
284
285 def time_start(self, button):
286 """Start pressed"""
287 self.set_time_index(0)
288 self.view.redraw()
289
290 def time_end(self, button):
291 """End pressed"""
292 self.set_time_index(len(self.model.times) - 1)
293 self.view.redraw()
294
295 def time_forward(self, button):
296 """Step forward pressed"""
297 self.set_time_index(min(self.view.timeIndex + 1,
298 len(self.model.times) - 1))
299 self.view.redraw()
300 gtk.gdk.flush()
301
302 def time_back(self, button):
303 """Step back pressed"""
304 self.set_time_index(max(self.view.timeIndex - 1, 0))
305 self.view.redraw()
306
307 def time_set(self, entry):
308 """Time dialogue changed. Need to find a suitable time
309 <= the entry's time"""
310 newTime = self.model.find_time_index(int(entry.get_text()))
311 self.set_time_index(newTime)
312 self.view.redraw()
313
314 def time_step(self):
315 """Time step while playing"""
316 if not self.playTimer \
317 or self.view.timeIndex == len(self.model.times) - 1:
318 self.time_stop(None)
319 return False
320 else:
321 self.time_forward(None)
322 return True
323
324 def time_play(self, play):
325 """Automatically advance time every 100 ms"""
326 if not self.playTimer:
327 self.playTimer = gobject.timeout_add(100, self.time_step)
328
329 def time_stop(self, play):
330 """Stop play pressed"""
331 if self.playTimer:
332 gobject.source_remove(self.playTimer)
333 self.playTimer = None
334
335 def load_events(self, button):
336 """Reload events file"""
337 self.model.load_events(self.filenameEntry.get_text(),
338 startTime=self.startTime, endTime=self.endTime)
339 self.set_time_index(min(len(self.model.times) - 1,
340 self.view.timeIndex))
341 self.view.redraw()
342
343 class Overlay(object):
344 """An Overlay is a speech bubble explaining the data in a blob"""
345 def __init__(self, model, view, point, blob):
346 self.model = model
347 self.view = view
348 self.point = point
349 self.blob = blob
350
351 def find_event(self):
352 """Find the event for a changing time and a fixed blob"""
353 return self.model.find_unit_event_by_time(self.blob.unit,
354 self.view.time)
355
356 def show(self, cr):
357 """Draw the overlay"""
358 event = self.find_event()
359
360 if event is None:
361 return
362
363 insts = event.find_ided_objects(self.model, self.blob.picChar,
364 False)
365
366 cr.set_line_width(self.view.thinLineWidth)
367 cr.translate(*(Point(0.0,0.0) - self.view.origin).to_pair())
368 cr.scale(*(Point(1.0,1.0) / self.view.masterScale).to_pair())
369
370 # Get formatted data from the insts to format into a table
371 lines = list(inst.table_line() for inst in insts)
372
373 text_size = 10.0
374 cr.set_font_size(text_size)
375
376 def text_width(str):
377 xb, yb, width, height, dx, dy = cr.text_extents(str)
378 return width
379
380 # Find the maximum number of columns and the widths of each column
381 num_columns = 0
382 for line in lines:
383 num_columns = max(num_columns, len(line))
384
385 widths = [0] * num_columns
386 for line in lines:
387 for i in xrange(0, len(line)):
388 widths[i] = max(widths[i], text_width(line[i]))
389
390 # Calculate the size of the speech bubble
391 column_gap = 1 * text_size
392 id_width = 6 * text_size
393 total_width = sum(widths) + id_width + column_gap * (num_columns + 1)
394 gap_step = Point(1.0, 0.0).scale(column_gap)
395
396 text_point = self.point
397 text_step = Point(0.0, text_size)
398
399 size = Point(total_width, text_size * len(insts))
400
401 # Draw the speech bubble
402 blobs.speech_bubble(cr, self.point, size, text_size)
403 cr.set_source_color(colours.backgroundColour)
404 cr.fill_preserve()
405 cr.set_source_color(colours.black)
406 cr.stroke()
407
408 text_point += Point(1.0,1.0).scale(2.0 * text_size)
409
410 id_size = Point(id_width, text_size)
411
412 # Draw the rows in the table
413 for i in xrange(0, len(insts)):
414 row_point = text_point
415 inst = insts[i]
416 line = lines[i]
417 blobs.striped_box(cr, row_point + id_size.scale(0.5),
418 id_size, inst.id.to_striped_block(self.view.dataSelect))
419 cr.set_source_color(colours.black)
420
421 row_point += Point(1.0, 0.0).scale(id_width)
422 row_point += text_step
423 # Draw the columns of each row
424 for j in xrange(0, len(line)):
425 row_point += gap_step
426 cr.move_to(*row_point.to_pair())
427 cr.show_text(line[j])
428 row_point += Point(1.0, 0.0).scale(widths[j])
429
430 text_point += text_step
431
432 class BlobWindow(object):
433 """The top-level window and its mouse control"""
434 def __init__(self, model, view, controller):
435 self.model = model
436 self.view = view
437 self.controller = controller
438 self.controlbar = None
439 self.window = None
440 self.miniViewCount = 0
441
442 def add_control_bar(self, controlbar):
443 self.controlbar = controlbar
444
445 def show_window(self):
446 self.window = gtk.Window()
447
448 self.vbox = gtk.VBox()
449 self.vbox.set_homogeneous(False)
450 if self.controlbar:
451 self.vbox.pack_start(self.controlbar, False, True, 0)
452 self.vbox.add(self.view.da)
453
454 if self.miniViewCount > 0:
455 self.miniViews = []
456 self.miniViewHBox = gtk.HBox(homogeneous=True, spacing=2)
457
458 # Draw mini views
459 for i in xrange(1, self.miniViewCount + 1):
460 miniView = BlobView(self.model)
461 miniView.set_time_index(0)
462 miniView.masterScale = Point(0.1, 0.1)
463 miniView.set_da_size()
464 miniView.timeOffset = i + 1
465 self.miniViews.append(miniView)
466 self.miniViewHBox.pack_start(miniView.da, False, True, 0)
467
468 self.controller.otherViews = self.miniViews
469 self.vbox.add(self.miniViewHBox)
470
471 self.window.add(self.vbox)
472
473 def show_event(picChar, event):
474 print '**** Comments for', event.unit, \
475 'at time', self.view.time
476 for name, value in event.pairs.iteritems():
477 print name, '=', value
478 for comment in event.comments:
479 print comment
480 if picChar in event.visuals:
481 # blocks = event.visuals[picChar].elems()
482 print '**** Colour data'
483 objs = event.find_ided_objects(self.model, picChar, True)
484 for obj in objs:
485 print ' '.join(obj.table_line())
486
487 def clicked_da(da, b):
488 point = Point(b.x, b.y)
489
490 overlay = None
491 for blob, centre, size in self.view.positions:
492 if point.is_within_box((centre, size)):
493 event = self.model.find_unit_event_by_time(blob.unit,
494 self.view.time)
495 if event is not None:
496 if overlay is None:
497 overlay = Overlay(self.model, self.view, point,
498 blob)
499 show_event(blob.picChar, event)
500 if overlay is not None:
501 self.view.overlays = [overlay]
502 else:
503 self.view.overlays = []
504
505 self.view.redraw()
506
507 # Set initial size and event callbacks
508 self.view.set_da_size()
509 self.view.da.add_events(gtk.gdk.BUTTON_PRESS_MASK)
510 self.view.da.connect('button-press-event', clicked_da)
511 self.window.connect('destroy', lambda(widget): gtk.main_quit())
512
513 def resize(window, event):
514 """Resize DrawingArea to match new window size"""
515 size = Point(float(event.width), float(event.height))
516 proportion = size / self.view.get_pic_size()
517 # Preserve aspect ratio
518 daScale = min(proportion.x, proportion.y)
519 self.view.masterScale = Point(daScale, daScale)
520 self.view.overlays = []
521
522 self.view.da.connect('configure-event', resize)
523
524 self.window.show_all()