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 | |
| 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')
| -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 | ||
