diff options
author | Adrian Hunter <adrian.hunter@intel.com> | 2018-10-01 02:28:47 -0400 |
---|---|---|
committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2018-10-23 13:27:30 -0400 |
commit | 1beb5c7b07040b70975a2ae0e90b87d412fabf06 (patch) | |
tree | 325ff512632d483afdc0585cee775e11a37839f5 /tools/perf/scripts/python/exported-sql-viewer.py | |
parent | 031c2a004ba75a4f8f2a6d0a7ca6f2fe5912de22 (diff) |
perf scripts python: exported-sql-viewer.py: Add support for multiple sub-windows
Use Qt MDI (multiple document interface) to support multiple sub-windows.
Put the data model in a cache so that each sub-window can share the same
data. This allows mutiple views of the call-graph at the same time and
paves the way to add more reports.
Committer testing:
Starts with a "File Reports Windows" main menu, from the "Reports" I
can get what was available up to now, the "Context-Sensitivi Call Graph"
option.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/20181001062853.28285-14-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/scripts/python/exported-sql-viewer.py')
-rwxr-xr-x | tools/perf/scripts/python/exported-sql-viewer.py | 182 |
1 files changed, 173 insertions, 9 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py index 03e7a1de7f31..c2f44351821e 100755 --- a/tools/perf/scripts/python/exported-sql-viewer.py +++ b/tools/perf/scripts/python/exported-sql-viewer.py | |||
@@ -47,6 +47,8 @@ | |||
47 | # functions that it calls | 47 | # functions that it calls |
48 | 48 | ||
49 | import sys | 49 | import sys |
50 | import weakref | ||
51 | import threading | ||
50 | from PySide.QtCore import * | 52 | from PySide.QtCore import * |
51 | from PySide.QtGui import * | 53 | from PySide.QtGui import * |
52 | from PySide.QtSql import * | 54 | from PySide.QtSql import * |
@@ -138,6 +140,23 @@ class TreeModel(QAbstractItemModel): | |||
138 | item = index.internalPointer() | 140 | item = index.internalPointer() |
139 | return self.DisplayData(item, index) | 141 | return self.DisplayData(item, index) |
140 | 142 | ||
143 | # Model cache | ||
144 | |||
145 | model_cache = weakref.WeakValueDictionary() | ||
146 | model_cache_lock = threading.Lock() | ||
147 | |||
148 | def LookupCreateModel(model_name, create_fn): | ||
149 | model_cache_lock.acquire() | ||
150 | try: | ||
151 | model = model_cache[model_name] | ||
152 | except: | ||
153 | model = None | ||
154 | if model is None: | ||
155 | model = create_fn() | ||
156 | model_cache[model_name] = model | ||
157 | model_cache_lock.release() | ||
158 | return model | ||
159 | |||
141 | # Context-sensitive call graph data model item base | 160 | # Context-sensitive call graph data model item base |
142 | 161 | ||
143 | class CallGraphLevelItemBase(object): | 162 | class CallGraphLevelItemBase(object): |
@@ -289,6 +308,144 @@ class CallGraphModel(TreeModel): | |||
289 | alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] | 308 | alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] |
290 | return alignment[column] | 309 | return alignment[column] |
291 | 310 | ||
311 | # Context-sensitive call graph window | ||
312 | |||
313 | class CallGraphWindow(QMdiSubWindow): | ||
314 | |||
315 | def __init__(self, glb, parent=None): | ||
316 | super(CallGraphWindow, self).__init__(parent) | ||
317 | |||
318 | self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x)) | ||
319 | |||
320 | self.view = QTreeView() | ||
321 | self.view.setModel(self.model) | ||
322 | |||
323 | for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): | ||
324 | self.view.setColumnWidth(c, w) | ||
325 | |||
326 | self.setWidget(self.view) | ||
327 | |||
328 | AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") | ||
329 | |||
330 | # Action Definition | ||
331 | |||
332 | def CreateAction(label, tip, callback, parent=None, shortcut=None): | ||
333 | action = QAction(label, parent) | ||
334 | if shortcut != None: | ||
335 | action.setShortcuts(shortcut) | ||
336 | action.setStatusTip(tip) | ||
337 | action.triggered.connect(callback) | ||
338 | return action | ||
339 | |||
340 | # Typical application actions | ||
341 | |||
342 | def CreateExitAction(app, parent=None): | ||
343 | return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit) | ||
344 | |||
345 | # Typical MDI actions | ||
346 | |||
347 | def CreateCloseActiveWindowAction(mdi_area): | ||
348 | return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area) | ||
349 | |||
350 | def CreateCloseAllWindowsAction(mdi_area): | ||
351 | return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area) | ||
352 | |||
353 | def CreateTileWindowsAction(mdi_area): | ||
354 | return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area) | ||
355 | |||
356 | def CreateCascadeWindowsAction(mdi_area): | ||
357 | return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area) | ||
358 | |||
359 | def CreateNextWindowAction(mdi_area): | ||
360 | return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild) | ||
361 | |||
362 | def CreatePreviousWindowAction(mdi_area): | ||
363 | return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild) | ||
364 | |||
365 | # Typical MDI window menu | ||
366 | |||
367 | class WindowMenu(): | ||
368 | |||
369 | def __init__(self, mdi_area, menu): | ||
370 | self.mdi_area = mdi_area | ||
371 | self.window_menu = menu.addMenu("&Windows") | ||
372 | self.close_active_window = CreateCloseActiveWindowAction(mdi_area) | ||
373 | self.close_all_windows = CreateCloseAllWindowsAction(mdi_area) | ||
374 | self.tile_windows = CreateTileWindowsAction(mdi_area) | ||
375 | self.cascade_windows = CreateCascadeWindowsAction(mdi_area) | ||
376 | self.next_window = CreateNextWindowAction(mdi_area) | ||
377 | self.previous_window = CreatePreviousWindowAction(mdi_area) | ||
378 | self.window_menu.aboutToShow.connect(self.Update) | ||
379 | |||
380 | def Update(self): | ||
381 | self.window_menu.clear() | ||
382 | sub_window_count = len(self.mdi_area.subWindowList()) | ||
383 | have_sub_windows = sub_window_count != 0 | ||
384 | self.close_active_window.setEnabled(have_sub_windows) | ||
385 | self.close_all_windows.setEnabled(have_sub_windows) | ||
386 | self.tile_windows.setEnabled(have_sub_windows) | ||
387 | self.cascade_windows.setEnabled(have_sub_windows) | ||
388 | self.next_window.setEnabled(have_sub_windows) | ||
389 | self.previous_window.setEnabled(have_sub_windows) | ||
390 | self.window_menu.addAction(self.close_active_window) | ||
391 | self.window_menu.addAction(self.close_all_windows) | ||
392 | self.window_menu.addSeparator() | ||
393 | self.window_menu.addAction(self.tile_windows) | ||
394 | self.window_menu.addAction(self.cascade_windows) | ||
395 | self.window_menu.addSeparator() | ||
396 | self.window_menu.addAction(self.next_window) | ||
397 | self.window_menu.addAction(self.previous_window) | ||
398 | if sub_window_count == 0: | ||
399 | return | ||
400 | self.window_menu.addSeparator() | ||
401 | nr = 1 | ||
402 | for sub_window in self.mdi_area.subWindowList(): | ||
403 | label = str(nr) + " " + sub_window.name | ||
404 | if nr < 10: | ||
405 | label = "&" + label | ||
406 | action = self.window_menu.addAction(label) | ||
407 | action.setCheckable(True) | ||
408 | action.setChecked(sub_window == self.mdi_area.activeSubWindow()) | ||
409 | action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x)) | ||
410 | self.window_menu.addAction(action) | ||
411 | nr += 1 | ||
412 | |||
413 | def setActiveSubWindow(self, nr): | ||
414 | self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) | ||
415 | |||
416 | # Unique name for sub-windows | ||
417 | |||
418 | def NumberedWindowName(name, nr): | ||
419 | if nr > 1: | ||
420 | name += " <" + str(nr) + ">" | ||
421 | return name | ||
422 | |||
423 | def UniqueSubWindowName(mdi_area, name): | ||
424 | nr = 1 | ||
425 | while True: | ||
426 | unique_name = NumberedWindowName(name, nr) | ||
427 | ok = True | ||
428 | for sub_window in mdi_area.subWindowList(): | ||
429 | if sub_window.name == unique_name: | ||
430 | ok = False | ||
431 | break | ||
432 | if ok: | ||
433 | return unique_name | ||
434 | nr += 1 | ||
435 | |||
436 | # Add a sub-window | ||
437 | |||
438 | def AddSubWindow(mdi_area, sub_window, name): | ||
439 | unique_name = UniqueSubWindowName(mdi_area, name) | ||
440 | sub_window.setMinimumSize(200, 100) | ||
441 | sub_window.resize(800, 600) | ||
442 | sub_window.setWindowTitle(unique_name) | ||
443 | sub_window.setAttribute(Qt.WA_DeleteOnClose) | ||
444 | sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon)) | ||
445 | sub_window.name = unique_name | ||
446 | mdi_area.addSubWindow(sub_window) | ||
447 | sub_window.show() | ||
448 | |||
292 | # Main window | 449 | # Main window |
293 | 450 | ||
294 | class MainWindow(QMainWindow): | 451 | class MainWindow(QMainWindow): |
@@ -298,21 +455,28 @@ class MainWindow(QMainWindow): | |||
298 | 455 | ||
299 | self.glb = glb | 456 | self.glb = glb |
300 | 457 | ||
301 | self.setWindowTitle("Call Graph: " + glb.dbname) | 458 | self.setWindowTitle("Exported SQL Viewer: " + glb.dbname) |
302 | self.move(100, 100) | ||
303 | self.resize(800, 600) | ||
304 | self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) | 459 | self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) |
305 | self.setMinimumSize(200, 100) | 460 | self.setMinimumSize(200, 100) |
306 | 461 | ||
307 | self.model = CallGraphModel(glb) | 462 | self.mdi_area = QMdiArea() |
463 | self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) | ||
464 | self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) | ||
308 | 465 | ||
309 | self.view = QTreeView() | 466 | self.setCentralWidget(self.mdi_area) |
310 | self.view.setModel(self.model) | ||
311 | 467 | ||
312 | for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): | 468 | menu = self.menuBar() |
313 | self.view.setColumnWidth(c, w) | 469 | |
470 | file_menu = menu.addMenu("&File") | ||
471 | file_menu.addAction(CreateExitAction(glb.app, self)) | ||
472 | |||
473 | reports_menu = menu.addMenu("&Reports") | ||
474 | reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) | ||
475 | |||
476 | self.window_menu = WindowMenu(self.mdi_area, menu) | ||
314 | 477 | ||
315 | self.setCentralWidget(self.view) | 478 | def NewCallGraph(self): |
479 | CallGraphWindow(self.glb, self) | ||
316 | 480 | ||
317 | # Global data | 481 | # Global data |
318 | 482 | ||