2 # SPDX-License-Identifier: GPL-2.0
3 # exported-sql-viewer.py: view data from sql database
4 # Copyright (c) 2014-2018, Intel Corporation.
6 # To use this script you will need to have exported data using either the
7 # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
10 # Following on from the example in the export scripts, a
11 # call-graph can be displayed for the pt_example database like this:
13 # python tools/perf/scripts/python/exported-sql-viewer.py pt_example
15 # Note that for PostgreSQL, this script supports connecting to remote databases
16 # by setting hostname, port, username, password, and dbname e.g.
18 # python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
20 # The result is a GUI window with a tree representing a context-sensitive
21 # call-graph. Expanding a couple of levels of the tree and adjusting column
22 # widths to suit will display something like:
24 # Call Graph: pt_example
25 # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
28 # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
29 # |- unknown unknown 1 13198 0.1 1 0.0
30 # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
31 # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
32 # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
33 # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
34 # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
35 # >- __libc_csu_init ls 1 10354 0.1 10 0.0
36 # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
37 # v- main ls 1 8182043 99.6 180254 99.9
40 # The top level is a command name (comm)
41 # The next level is a thread (pid:tid)
42 # Subsequent levels are functions
43 # 'Count' is the number of calls
44 # 'Time' is the elapsed time until the function returns
45 # Percentages are relative to the level above
46 # 'Branch Count' is the total number of branches for that function and all
47 # functions that it calls
49 # There is also a "All branches" report, which displays branches and
50 # possibly disassembly. However, presently, the only supported disassembler is
51 # Intel XED, and additionally the object code must be present in perf build ID
52 # cache. To use Intel XED, libxed.so must be present. To build and install
54 # git clone https://github.com/intelxed/mbuild.git mbuild
55 # git clone https://github.com/intelxed/xed
58 # sudo ./mfile.py --prefix=/usr/local install
63 # Time CPU Command PID TID Branch Type In Tx Branch
64 # 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
65 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
66 # 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
67 # 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
68 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
69 # 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930
70 # 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
71 # 7fab593ea930 55 pushq %rbp
72 # 7fab593ea931 48 89 e5 mov %rsp, %rbp
73 # 7fab593ea934 41 57 pushq %r15
74 # 7fab593ea936 41 56 pushq %r14
75 # 7fab593ea938 41 55 pushq %r13
76 # 7fab593ea93a 41 54 pushq %r12
77 # 7fab593ea93c 53 pushq %rbx
78 # 7fab593ea93d 48 89 fb mov %rdi, %rbx
79 # 7fab593ea940 48 83 ec 68 sub $0x68, %rsp
80 # 7fab593ea944 0f 31 rdtsc
81 # 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx
82 # 7fab593ea94a 89 c0 mov %eax, %eax
83 # 7fab593ea94c 48 09 c2 or %rax, %rdx
84 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
85 # 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
86 # 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
87 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
88 # 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip)
89 # 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
91 from __future__
import print_function
100 import cPickle
as pickle
101 # size of pickled integer big enough for record size
112 pyside_version_1
= True
113 if not "--pyside-version-1" in sys
.argv
:
115 from PySide2
.QtCore
import *
116 from PySide2
.QtGui
import *
117 from PySide2
.QtSql
import *
118 from PySide2
.QtWidgets
import *
119 pyside_version_1
= False
124 from PySide
.QtCore
import *
125 from PySide
.QtGui
import *
126 from PySide
.QtSql
import *
128 from decimal
import *
130 from multiprocessing
import Process
, Array
, Value
, Event
132 # xrange is range in Python3
138 def printerr(*args
, **keyword_args
):
139 print(*args
, file=sys
.stderr
, **keyword_args
)
141 # Data formatting helpers
150 return "+0x%x" % offset
154 if name
== "[kernel.kallsyms]":
158 def findnth(s
, sub
, n
, offs
=0):
164 return findnth(s
[pos
+ 1:], sub
, n
- 1, offs
+ pos
+ 1)
166 # Percent to one decimal place
168 def PercentToOneDP(n
, d
):
171 x
= (n
* Decimal(100)) / d
172 return str(x
.quantize(Decimal(".1"), rounding
=ROUND_HALF_UP
))
174 # Helper for queries that must not fail
176 def QueryExec(query
, stmt
):
177 ret
= query
.exec_(stmt
)
179 raise Exception("Query failed: " + query
.lastError().text())
183 class Thread(QThread
):
185 done
= Signal(object)
187 def __init__(self
, task
, param
=None, parent
=None):
188 super(Thread
, self
).__init
__(parent
)
194 if self
.param
is None:
195 done
, result
= self
.task()
197 done
, result
= self
.task(self
.param
)
198 self
.done
.emit(result
)
204 class TreeModel(QAbstractItemModel
):
206 def __init__(self
, glb
, params
, parent
=None):
207 super(TreeModel
, self
).__init
__(parent
)
210 self
.root
= self
.GetRoot()
211 self
.last_row_read
= 0
213 def Item(self
, parent
):
215 return parent
.internalPointer()
219 def rowCount(self
, parent
):
220 result
= self
.Item(parent
).childCount()
223 self
.dataChanged
.emit(parent
, parent
)
226 def hasChildren(self
, parent
):
227 return self
.Item(parent
).hasChildren()
229 def headerData(self
, section
, orientation
, role
):
230 if role
== Qt
.TextAlignmentRole
:
231 return self
.columnAlignment(section
)
232 if role
!= Qt
.DisplayRole
:
234 if orientation
!= Qt
.Horizontal
:
236 return self
.columnHeader(section
)
238 def parent(self
, child
):
239 child_item
= child
.internalPointer()
240 if child_item
is self
.root
:
242 parent_item
= child_item
.getParentItem()
243 return self
.createIndex(parent_item
.getRow(), 0, parent_item
)
245 def index(self
, row
, column
, parent
):
246 child_item
= self
.Item(parent
).getChildItem(row
)
247 return self
.createIndex(row
, column
, child_item
)
249 def DisplayData(self
, item
, index
):
250 return item
.getData(index
.column())
252 def FetchIfNeeded(self
, row
):
253 if row
> self
.last_row_read
:
254 self
.last_row_read
= row
255 if row
+ 10 >= self
.root
.child_count
:
256 self
.fetcher
.Fetch(glb_chunk_sz
)
258 def columnAlignment(self
, column
):
261 def columnFont(self
, column
):
264 def data(self
, index
, role
):
265 if role
== Qt
.TextAlignmentRole
:
266 return self
.columnAlignment(index
.column())
267 if role
== Qt
.FontRole
:
268 return self
.columnFont(index
.column())
269 if role
!= Qt
.DisplayRole
:
271 item
= index
.internalPointer()
272 return self
.DisplayData(item
, index
)
276 class TableModel(QAbstractTableModel
):
278 def __init__(self
, parent
=None):
279 super(TableModel
, self
).__init
__(parent
)
281 self
.child_items
= []
282 self
.last_row_read
= 0
284 def Item(self
, parent
):
286 return parent
.internalPointer()
290 def rowCount(self
, parent
):
291 return self
.child_count
293 def headerData(self
, section
, orientation
, role
):
294 if role
== Qt
.TextAlignmentRole
:
295 return self
.columnAlignment(section
)
296 if role
!= Qt
.DisplayRole
:
298 if orientation
!= Qt
.Horizontal
:
300 return self
.columnHeader(section
)
302 def index(self
, row
, column
, parent
):
303 return self
.createIndex(row
, column
, self
.child_items
[row
])
305 def DisplayData(self
, item
, index
):
306 return item
.getData(index
.column())
308 def FetchIfNeeded(self
, row
):
309 if row
> self
.last_row_read
:
310 self
.last_row_read
= row
311 if row
+ 10 >= self
.child_count
:
312 self
.fetcher
.Fetch(glb_chunk_sz
)
314 def columnAlignment(self
, column
):
317 def columnFont(self
, column
):
320 def data(self
, index
, role
):
321 if role
== Qt
.TextAlignmentRole
:
322 return self
.columnAlignment(index
.column())
323 if role
== Qt
.FontRole
:
324 return self
.columnFont(index
.column())
325 if role
!= Qt
.DisplayRole
:
327 item
= index
.internalPointer()
328 return self
.DisplayData(item
, index
)
332 model_cache
= weakref
.WeakValueDictionary()
333 model_cache_lock
= threading
.Lock()
335 def LookupCreateModel(model_name
, create_fn
):
336 model_cache_lock
.acquire()
338 model
= model_cache
[model_name
]
343 model_cache
[model_name
] = model
344 model_cache_lock
.release()
347 def LookupModel(model_name
):
348 model_cache_lock
.acquire()
350 model
= model_cache
[model_name
]
353 model_cache_lock
.release()
360 def __init__(self
, parent
, finder
, is_reg_expr
=False):
363 self
.last_value
= None
364 self
.last_pattern
= None
366 label
= QLabel("Find:")
367 label
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
369 self
.textbox
= QComboBox()
370 self
.textbox
.setEditable(True)
371 self
.textbox
.currentIndexChanged
.connect(self
.ValueChanged
)
373 self
.progress
= QProgressBar()
374 self
.progress
.setRange(0, 0)
378 self
.pattern
= QCheckBox("Regular Expression")
380 self
.pattern
= QCheckBox("Pattern")
381 self
.pattern
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
383 self
.next_button
= QToolButton()
384 self
.next_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_ArrowDown
))
385 self
.next_button
.released
.connect(lambda: self
.NextPrev(1))
387 self
.prev_button
= QToolButton()
388 self
.prev_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_ArrowUp
))
389 self
.prev_button
.released
.connect(lambda: self
.NextPrev(-1))
391 self
.close_button
= QToolButton()
392 self
.close_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_DockWidgetCloseButton
))
393 self
.close_button
.released
.connect(self
.Deactivate
)
395 self
.hbox
= QHBoxLayout()
396 self
.hbox
.setContentsMargins(0, 0, 0, 0)
398 self
.hbox
.addWidget(label
)
399 self
.hbox
.addWidget(self
.textbox
)
400 self
.hbox
.addWidget(self
.progress
)
401 self
.hbox
.addWidget(self
.pattern
)
402 self
.hbox
.addWidget(self
.next_button
)
403 self
.hbox
.addWidget(self
.prev_button
)
404 self
.hbox
.addWidget(self
.close_button
)
407 self
.bar
.setLayout(self
.hbox
)
415 self
.textbox
.lineEdit().selectAll()
416 self
.textbox
.setFocus()
418 def Deactivate(self
):
422 self
.textbox
.setEnabled(False)
424 self
.next_button
.hide()
425 self
.prev_button
.hide()
429 self
.textbox
.setEnabled(True)
432 self
.next_button
.show()
433 self
.prev_button
.show()
435 def Find(self
, direction
):
436 value
= self
.textbox
.currentText()
437 pattern
= self
.pattern
.isChecked()
438 self
.last_value
= value
439 self
.last_pattern
= pattern
440 self
.finder
.Find(value
, direction
, pattern
, self
.context
)
442 def ValueChanged(self
):
443 value
= self
.textbox
.currentText()
444 pattern
= self
.pattern
.isChecked()
445 index
= self
.textbox
.currentIndex()
446 data
= self
.textbox
.itemData(index
)
447 # Store the pattern in the combo box to keep it with the text value
449 self
.textbox
.setItemData(index
, pattern
)
451 self
.pattern
.setChecked(data
)
454 def NextPrev(self
, direction
):
455 value
= self
.textbox
.currentText()
456 pattern
= self
.pattern
.isChecked()
457 if value
!= self
.last_value
:
458 index
= self
.textbox
.findText(value
)
459 # Allow for a button press before the value has been added to the combo box
461 index
= self
.textbox
.count()
462 self
.textbox
.addItem(value
, pattern
)
463 self
.textbox
.setCurrentIndex(index
)
466 self
.textbox
.setItemData(index
, pattern
)
467 elif pattern
!= self
.last_pattern
:
468 # Keep the pattern recorded in the combo box up to date
469 index
= self
.textbox
.currentIndex()
470 self
.textbox
.setItemData(index
, pattern
)
474 QMessageBox
.information(self
.bar
, "Find", "'" + self
.textbox
.currentText() + "' not found")
476 # Context-sensitive call graph data model item base
478 class CallGraphLevelItemBase(object):
480 def __init__(self
, glb
, params
, row
, parent_item
):
484 self
.parent_item
= parent_item
485 self
.query_done
= False
487 self
.child_items
= []
489 self
.level
= parent_item
.level
+ 1
493 def getChildItem(self
, row
):
494 return self
.child_items
[row
]
496 def getParentItem(self
):
497 return self
.parent_item
502 def childCount(self
):
503 if not self
.query_done
:
505 if not self
.child_count
:
507 return self
.child_count
509 def hasChildren(self
):
510 if not self
.query_done
:
512 return self
.child_count
> 0
514 def getData(self
, column
):
515 return self
.data
[column
]
517 # Context-sensitive call graph data model level 2+ item base
519 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase
):
521 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, call_path_id
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
522 super(CallGraphLevelTwoPlusItemBase
, self
).__init
__(glb
, params
, row
, parent_item
)
523 self
.comm_id
= comm_id
524 self
.thread_id
= thread_id
525 self
.call_path_id
= call_path_id
526 self
.insn_cnt
= insn_cnt
527 self
.cyc_cnt
= cyc_cnt
528 self
.branch_count
= branch_count
532 self
.query_done
= True
533 query
= QSqlQuery(self
.glb
.db
)
534 if self
.params
.have_ipc
:
535 ipc_str
= ", SUM(insn_count), SUM(cyc_count)"
538 QueryExec(query
, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str
+ ", SUM(branch_count)"
540 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
541 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
542 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
543 " WHERE parent_call_path_id = " + str(self
.call_path_id
) +
544 " AND comm_id = " + str(self
.comm_id
) +
545 " AND thread_id = " + str(self
.thread_id
) +
546 " GROUP BY call_path_id, name, short_name"
547 " ORDER BY call_path_id")
549 if self
.params
.have_ipc
:
550 insn_cnt
= int(query
.value(5))
551 cyc_cnt
= int(query
.value(6))
552 branch_count
= int(query
.value(7))
556 branch_count
= int(query
.value(5))
557 child_item
= CallGraphLevelThreeItem(self
.glb
, self
.params
, self
.child_count
, self
.comm_id
, self
.thread_id
, query
.value(0), query
.value(1), query
.value(2), query
.value(3), int(query
.value(4)), insn_cnt
, cyc_cnt
, branch_count
, self
)
558 self
.child_items
.append(child_item
)
559 self
.child_count
+= 1
561 # Context-sensitive call graph data model level three item
563 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase
):
565 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, call_path_id
, name
, dso
, count
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
566 super(CallGraphLevelThreeItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, call_path_id
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
)
568 if self
.params
.have_ipc
:
569 insn_pcnt
= PercentToOneDP(insn_cnt
, parent_item
.insn_cnt
)
570 cyc_pcnt
= PercentToOneDP(cyc_cnt
, parent_item
.cyc_cnt
)
571 br_pcnt
= PercentToOneDP(branch_count
, parent_item
.branch_count
)
572 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
573 self
.data
= [ name
, dso
, str(count
), str(time
), PercentToOneDP(time
, parent_item
.time
), str(insn_cnt
), insn_pcnt
, str(cyc_cnt
), cyc_pcnt
, ipc
, str(branch_count
), br_pcnt
]
575 self
.data
= [ name
, dso
, str(count
), str(time
), PercentToOneDP(time
, parent_item
.time
), str(branch_count
), PercentToOneDP(branch_count
, parent_item
.branch_count
) ]
576 self
.dbid
= call_path_id
578 # Context-sensitive call graph data model level two item
580 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase
):
582 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, pid
, tid
, parent_item
):
583 super(CallGraphLevelTwoItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, 1, 0, 0, 0, 0, parent_item
)
584 if self
.params
.have_ipc
:
585 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", "", "", "", "", "", ""]
587 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", ""]
588 self
.dbid
= thread_id
591 super(CallGraphLevelTwoItem
, self
).Select()
592 for child_item
in self
.child_items
:
593 self
.time
+= child_item
.time
594 self
.insn_cnt
+= child_item
.insn_cnt
595 self
.cyc_cnt
+= child_item
.cyc_cnt
596 self
.branch_count
+= child_item
.branch_count
597 for child_item
in self
.child_items
:
598 child_item
.data
[4] = PercentToOneDP(child_item
.time
, self
.time
)
599 if self
.params
.have_ipc
:
600 child_item
.data
[6] = PercentToOneDP(child_item
.insn_cnt
, self
.insn_cnt
)
601 child_item
.data
[8] = PercentToOneDP(child_item
.cyc_cnt
, self
.cyc_cnt
)
602 child_item
.data
[11] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
604 child_item
.data
[6] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
606 # Context-sensitive call graph data model level one item
608 class CallGraphLevelOneItem(CallGraphLevelItemBase
):
610 def __init__(self
, glb
, params
, row
, comm_id
, comm
, parent_item
):
611 super(CallGraphLevelOneItem
, self
).__init
__(glb
, params
, row
, parent_item
)
612 if self
.params
.have_ipc
:
613 self
.data
= [comm
, "", "", "", "", "", "", "", "", "", "", ""]
615 self
.data
= [comm
, "", "", "", "", "", ""]
619 self
.query_done
= True
620 query
= QSqlQuery(self
.glb
.db
)
621 QueryExec(query
, "SELECT thread_id, pid, tid"
623 " INNER JOIN threads ON thread_id = threads.id"
624 " WHERE comm_id = " + str(self
.dbid
))
626 child_item
= CallGraphLevelTwoItem(self
.glb
, self
.params
, self
.child_count
, self
.dbid
, query
.value(0), query
.value(1), query
.value(2), self
)
627 self
.child_items
.append(child_item
)
628 self
.child_count
+= 1
630 # Context-sensitive call graph data model root item
632 class CallGraphRootItem(CallGraphLevelItemBase
):
634 def __init__(self
, glb
, params
):
635 super(CallGraphRootItem
, self
).__init
__(glb
, params
, 0, None)
637 self
.query_done
= True
639 if IsSelectable(glb
.db
, "comms", columns
= "has_calls"):
640 if_has_calls
= " WHERE has_calls = " + glb
.dbref
.TRUE
641 query
= QSqlQuery(glb
.db
)
642 QueryExec(query
, "SELECT id, comm FROM comms" + if_has_calls
)
644 if not query
.value(0):
646 child_item
= CallGraphLevelOneItem(glb
, params
, self
.child_count
, query
.value(0), query
.value(1), self
)
647 self
.child_items
.append(child_item
)
648 self
.child_count
+= 1
650 # Call graph model parameters
652 class CallGraphModelParams():
654 def __init__(self
, glb
, parent
=None):
655 self
.have_ipc
= IsSelectable(glb
.db
, "calls", columns
= "insn_count, cyc_count")
657 # Context-sensitive call graph data model base
659 class CallGraphModelBase(TreeModel
):
661 def __init__(self
, glb
, parent
=None):
662 super(CallGraphModelBase
, self
).__init
__(glb
, CallGraphModelParams(glb
), parent
)
664 def FindSelect(self
, value
, pattern
, query
):
666 # postgresql and sqlite pattern patching differences:
667 # postgresql LIKE is case sensitive but sqlite LIKE is not
668 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
669 # postgresql supports ILIKE which is case insensitive
670 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
671 if not self
.glb
.dbref
.is_sqlite3
:
673 s
= value
.replace("%", "\%")
674 s
= s
.replace("_", "\_")
675 # Translate * and ? into SQL LIKE pattern characters % and _
676 trans
= string
.maketrans("*?", "%_")
677 match
= " LIKE '" + str(s
).translate(trans
) + "'"
679 match
= " GLOB '" + str(value
) + "'"
681 match
= " = '" + str(value
) + "'"
682 self
.DoFindSelect(query
, match
)
684 def Found(self
, query
, found
):
686 return self
.FindPath(query
)
689 def FindValue(self
, value
, pattern
, query
, last_value
, last_pattern
):
690 if last_value
== value
and pattern
== last_pattern
:
691 found
= query
.first()
693 self
.FindSelect(value
, pattern
, query
)
695 return self
.Found(query
, found
)
697 def FindNext(self
, query
):
700 found
= query
.first()
701 return self
.Found(query
, found
)
703 def FindPrev(self
, query
):
704 found
= query
.previous()
707 return self
.Found(query
, found
)
709 def FindThread(self
, c
):
710 if c
.direction
== 0 or c
.value
!= c
.last_value
or c
.pattern
!= c
.last_pattern
:
711 ids
= self
.FindValue(c
.value
, c
.pattern
, c
.query
, c
.last_value
, c
.last_pattern
)
712 elif c
.direction
> 0:
713 ids
= self
.FindNext(c
.query
)
715 ids
= self
.FindPrev(c
.query
)
718 def Find(self
, value
, direction
, pattern
, context
, callback
):
720 def __init__(self
, *x
):
721 self
.value
, self
.direction
, self
.pattern
, self
.query
, self
.last_value
, self
.last_pattern
= x
722 def Update(self
, *x
):
723 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= x
+ (self
.value
, self
.pattern
)
725 context
[0].Update(value
, direction
, pattern
)
727 context
.append(Context(value
, direction
, pattern
, QSqlQuery(self
.glb
.db
), None, None))
728 # Use a thread so the UI is not blocked during the SELECT
729 thread
= Thread(self
.FindThread
, context
[0])
730 thread
.done
.connect(lambda ids
, t
=thread
, c
=callback
: self
.FindDone(t
, c
, ids
), Qt
.QueuedConnection
)
733 def FindDone(self
, thread
, callback
, ids
):
736 # Context-sensitive call graph data model
738 class CallGraphModel(CallGraphModelBase
):
740 def __init__(self
, glb
, parent
=None):
741 super(CallGraphModel
, self
).__init
__(glb
, parent
)
744 return CallGraphRootItem(self
.glb
, self
.params
)
746 def columnCount(self
, parent
=None):
747 if self
.params
.have_ipc
:
752 def columnHeader(self
, column
):
753 if self
.params
.have_ipc
:
754 headers
= ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
756 headers
= ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
757 return headers
[column
]
759 def columnAlignment(self
, column
):
760 if self
.params
.have_ipc
:
761 alignment
= [ Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
]
763 alignment
= [ Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
]
764 return alignment
[column
]
766 def DoFindSelect(self
, query
, match
):
767 QueryExec(query
, "SELECT call_path_id, comm_id, thread_id"
769 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
770 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
771 " WHERE symbols.name" + match
+
772 " GROUP BY comm_id, thread_id, call_path_id"
773 " ORDER BY comm_id, thread_id, call_path_id")
775 def FindPath(self
, query
):
776 # Turn the query result into a list of ids that the tree view can walk
777 # to open the tree at the right place.
779 parent_id
= query
.value(0)
781 ids
.insert(0, parent_id
)
782 q2
= QSqlQuery(self
.glb
.db
)
783 QueryExec(q2
, "SELECT parent_id"
785 " WHERE id = " + str(parent_id
))
788 parent_id
= q2
.value(0)
789 # The call path root is not used
792 ids
.insert(0, query
.value(2))
793 ids
.insert(0, query
.value(1))
796 # Call tree data model level 2+ item base
798 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase
):
800 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, calls_id
, call_time
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
801 super(CallTreeLevelTwoPlusItemBase
, self
).__init
__(glb
, params
, row
, parent_item
)
802 self
.comm_id
= comm_id
803 self
.thread_id
= thread_id
804 self
.calls_id
= calls_id
805 self
.call_time
= call_time
807 self
.insn_cnt
= insn_cnt
808 self
.cyc_cnt
= cyc_cnt
809 self
.branch_count
= branch_count
812 self
.query_done
= True
813 if self
.calls_id
== 0:
814 comm_thread
= " AND comm_id = " + str(self
.comm_id
) + " AND thread_id = " + str(self
.thread_id
)
817 if self
.params
.have_ipc
:
818 ipc_str
= ", insn_count, cyc_count"
821 query
= QSqlQuery(self
.glb
.db
)
822 QueryExec(query
, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str
+ ", branch_count"
824 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
825 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
826 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
827 " WHERE calls.parent_id = " + str(self
.calls_id
) + comm_thread
+
828 " ORDER BY call_time, calls.id")
830 if self
.params
.have_ipc
:
831 insn_cnt
= int(query
.value(5))
832 cyc_cnt
= int(query
.value(6))
833 branch_count
= int(query
.value(7))
837 branch_count
= int(query
.value(5))
838 child_item
= CallTreeLevelThreeItem(self
.glb
, self
.params
, self
.child_count
, self
.comm_id
, self
.thread_id
, query
.value(0), query
.value(1), query
.value(2), query
.value(3), int(query
.value(4)), insn_cnt
, cyc_cnt
, branch_count
, self
)
839 self
.child_items
.append(child_item
)
840 self
.child_count
+= 1
842 # Call tree data model level three item
844 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase
):
846 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, calls_id
, name
, dso
, call_time
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
847 super(CallTreeLevelThreeItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, calls_id
, call_time
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
)
849 if self
.params
.have_ipc
:
850 insn_pcnt
= PercentToOneDP(insn_cnt
, parent_item
.insn_cnt
)
851 cyc_pcnt
= PercentToOneDP(cyc_cnt
, parent_item
.cyc_cnt
)
852 br_pcnt
= PercentToOneDP(branch_count
, parent_item
.branch_count
)
853 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
854 self
.data
= [ name
, dso
, str(call_time
), str(time
), PercentToOneDP(time
, parent_item
.time
), str(insn_cnt
), insn_pcnt
, str(cyc_cnt
), cyc_pcnt
, ipc
, str(branch_count
), br_pcnt
]
856 self
.data
= [ name
, dso
, str(call_time
), str(time
), PercentToOneDP(time
, parent_item
.time
), str(branch_count
), PercentToOneDP(branch_count
, parent_item
.branch_count
) ]
859 # Call tree data model level two item
861 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase
):
863 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, pid
, tid
, parent_item
):
864 super(CallTreeLevelTwoItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, 0, 0, 0, 0, 0, 0, parent_item
)
865 if self
.params
.have_ipc
:
866 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", "", "", "", "", "", ""]
868 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", ""]
869 self
.dbid
= thread_id
872 super(CallTreeLevelTwoItem
, self
).Select()
873 for child_item
in self
.child_items
:
874 self
.time
+= child_item
.time
875 self
.insn_cnt
+= child_item
.insn_cnt
876 self
.cyc_cnt
+= child_item
.cyc_cnt
877 self
.branch_count
+= child_item
.branch_count
878 for child_item
in self
.child_items
:
879 child_item
.data
[4] = PercentToOneDP(child_item
.time
, self
.time
)
880 if self
.params
.have_ipc
:
881 child_item
.data
[6] = PercentToOneDP(child_item
.insn_cnt
, self
.insn_cnt
)
882 child_item
.data
[8] = PercentToOneDP(child_item
.cyc_cnt
, self
.cyc_cnt
)
883 child_item
.data
[11] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
885 child_item
.data
[6] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
887 # Call tree data model level one item
889 class CallTreeLevelOneItem(CallGraphLevelItemBase
):
891 def __init__(self
, glb
, params
, row
, comm_id
, comm
, parent_item
):
892 super(CallTreeLevelOneItem
, self
).__init
__(glb
, params
, row
, parent_item
)
893 if self
.params
.have_ipc
:
894 self
.data
= [comm
, "", "", "", "", "", "", "", "", "", "", ""]
896 self
.data
= [comm
, "", "", "", "", "", ""]
900 self
.query_done
= True
901 query
= QSqlQuery(self
.glb
.db
)
902 QueryExec(query
, "SELECT thread_id, pid, tid"
904 " INNER JOIN threads ON thread_id = threads.id"
905 " WHERE comm_id = " + str(self
.dbid
))
907 child_item
= CallTreeLevelTwoItem(self
.glb
, self
.params
, self
.child_count
, self
.dbid
, query
.value(0), query
.value(1), query
.value(2), self
)
908 self
.child_items
.append(child_item
)
909 self
.child_count
+= 1
911 # Call tree data model root item
913 class CallTreeRootItem(CallGraphLevelItemBase
):
915 def __init__(self
, glb
, params
):
916 super(CallTreeRootItem
, self
).__init
__(glb
, params
, 0, None)
918 self
.query_done
= True
920 if IsSelectable(glb
.db
, "comms", columns
= "has_calls"):
921 if_has_calls
= " WHERE has_calls = " + glb
.dbref
.TRUE
922 query
= QSqlQuery(glb
.db
)
923 QueryExec(query
, "SELECT id, comm FROM comms" + if_has_calls
)
925 if not query
.value(0):
927 child_item
= CallTreeLevelOneItem(glb
, params
, self
.child_count
, query
.value(0), query
.value(1), self
)
928 self
.child_items
.append(child_item
)
929 self
.child_count
+= 1
931 # Call Tree data model
933 class CallTreeModel(CallGraphModelBase
):
935 def __init__(self
, glb
, parent
=None):
936 super(CallTreeModel
, self
).__init
__(glb
, parent
)
939 return CallTreeRootItem(self
.glb
, self
.params
)
941 def columnCount(self
, parent
=None):
942 if self
.params
.have_ipc
:
947 def columnHeader(self
, column
):
948 if self
.params
.have_ipc
:
949 headers
= ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
951 headers
= ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
952 return headers
[column
]
954 def columnAlignment(self
, column
):
955 if self
.params
.have_ipc
:
956 alignment
= [ Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
]
958 alignment
= [ Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
]
959 return alignment
[column
]
961 def DoFindSelect(self
, query
, match
):
962 QueryExec(query
, "SELECT calls.id, comm_id, thread_id"
964 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
965 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
966 " WHERE symbols.name" + match
+
967 " ORDER BY comm_id, thread_id, call_time, calls.id")
969 def FindPath(self
, query
):
970 # Turn the query result into a list of ids that the tree view can walk
971 # to open the tree at the right place.
973 parent_id
= query
.value(0)
975 ids
.insert(0, parent_id
)
976 q2
= QSqlQuery(self
.glb
.db
)
977 QueryExec(q2
, "SELECT parent_id"
979 " WHERE id = " + str(parent_id
))
982 parent_id
= q2
.value(0)
983 ids
.insert(0, query
.value(2))
984 ids
.insert(0, query
.value(1))
989 class HBoxLayout(QHBoxLayout
):
991 def __init__(self
, *children
):
992 super(HBoxLayout
, self
).__init
__()
994 self
.layout().setContentsMargins(0, 0, 0, 0)
995 for child
in children
:
996 if child
.isWidgetType():
997 self
.layout().addWidget(child
)
999 self
.layout().addLayout(child
)
1003 class VBoxLayout(QVBoxLayout
):
1005 def __init__(self
, *children
):
1006 super(VBoxLayout
, self
).__init
__()
1008 self
.layout().setContentsMargins(0, 0, 0, 0)
1009 for child
in children
:
1010 if child
.isWidgetType():
1011 self
.layout().addWidget(child
)
1013 self
.layout().addLayout(child
)
1015 # Vertical layout widget
1019 def __init__(self
, *children
):
1020 self
.vbox
= QWidget()
1021 self
.vbox
.setLayout(VBoxLayout(*children
))
1028 class TreeWindowBase(QMdiSubWindow
):
1030 def __init__(self
, parent
=None):
1031 super(TreeWindowBase
, self
).__init
__(parent
)
1034 self
.find_bar
= None
1036 self
.view
= QTreeView()
1037 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
1038 self
.view
.CopyCellsToClipboard
= CopyTreeCellsToClipboard
1040 self
.context_menu
= TreeContextMenu(self
.view
)
1042 def DisplayFound(self
, ids
):
1045 parent
= QModelIndex()
1048 n
= self
.model
.rowCount(parent
)
1049 for row
in xrange(n
):
1050 child
= self
.model
.index(row
, 0, parent
)
1051 if child
.internalPointer().dbid
== dbid
:
1053 self
.view
.setCurrentIndex(child
)
1060 def Find(self
, value
, direction
, pattern
, context
):
1061 self
.view
.setFocus()
1062 self
.find_bar
.Busy()
1063 self
.model
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
1065 def FindDone(self
, ids
):
1067 if not self
.DisplayFound(ids
):
1069 self
.find_bar
.Idle()
1071 self
.find_bar
.NotFound()
1074 # Context-sensitive call graph window
1076 class CallGraphWindow(TreeWindowBase
):
1078 def __init__(self
, glb
, parent
=None):
1079 super(CallGraphWindow
, self
).__init
__(parent
)
1081 self
.model
= LookupCreateModel("Context-Sensitive Call Graph", lambda x
=glb
: CallGraphModel(x
))
1083 self
.view
.setModel(self
.model
)
1085 for c
, w
in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1086 self
.view
.setColumnWidth(c
, w
)
1088 self
.find_bar
= FindBar(self
, self
)
1090 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget())
1092 self
.setWidget(self
.vbox
.Widget())
1094 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Context-Sensitive Call Graph")
1098 class CallTreeWindow(TreeWindowBase
):
1100 def __init__(self
, glb
, parent
=None, thread_at_time
=None):
1101 super(CallTreeWindow
, self
).__init
__(parent
)
1103 self
.model
= LookupCreateModel("Call Tree", lambda x
=glb
: CallTreeModel(x
))
1105 self
.view
.setModel(self
.model
)
1107 for c
, w
in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1108 self
.view
.setColumnWidth(c
, w
)
1110 self
.find_bar
= FindBar(self
, self
)
1112 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget())
1114 self
.setWidget(self
.vbox
.Widget())
1116 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Call Tree")
1119 self
.DisplayThreadAtTime(*thread_at_time
)
1121 def DisplayThreadAtTime(self
, comm_id
, thread_id
, time
):
1122 parent
= QModelIndex()
1123 for dbid
in (comm_id
, thread_id
):
1125 n
= self
.model
.rowCount(parent
)
1126 for row
in xrange(n
):
1127 child
= self
.model
.index(row
, 0, parent
)
1128 if child
.internalPointer().dbid
== dbid
:
1130 self
.view
.setCurrentIndex(child
)
1137 n
= self
.model
.rowCount(parent
)
1141 for row
in xrange(n
):
1142 child
= self
.model
.index(row
, 0, parent
)
1143 child_call_time
= child
.internalPointer().call_time
1144 if child_call_time
< time
:
1146 elif child_call_time
== time
:
1147 self
.view
.setCurrentIndex(child
)
1149 elif child_call_time
> time
:
1153 child
= self
.model
.index(0, 0, parent
)
1154 self
.view
.setCurrentIndex(child
)
1157 self
.view
.setCurrentIndex(last_child
)
1160 # ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
1162 def ExecComm(db
, thread_id
, time
):
1163 query
= QSqlQuery(db
)
1164 QueryExec(query
, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag"
1165 " FROM comm_threads"
1166 " INNER JOIN comms ON comms.id = comm_threads.comm_id"
1167 " WHERE comm_threads.thread_id = " + str(thread_id
) +
1168 " ORDER BY comms.c_time, comms.id")
1173 first
= query
.value(0)
1174 if query
.value(2) and Decimal(query
.value(1)) <= Decimal(time
):
1175 last
= query
.value(0)
1176 if not(last
is None):
1180 # Container for (x, y) data
1183 def __init__(self
, x
=0, y
=0):
1188 return "XY({}, {})".format(str(self
.x
), str(self
.y
))
1190 # Container for sub-range data
1193 def __init__(self
, lo
=0, hi
=0):
1198 return "Subrange({}, {})".format(str(self
.lo
), str(self
.hi
))
1200 # Graph data region base class
1202 class GraphDataRegion(object):
1204 def __init__(self
, key
, title
= "", ordinal
= ""):
1207 self
.ordinal
= ordinal
1209 # Function to sort GraphDataRegion
1211 def GraphDataRegionOrdinal(data_region
):
1212 return data_region
.ordinal
1214 # Attributes for a graph region
1216 class GraphRegionAttribute():
1218 def __init__(self
, colour
):
1219 self
.colour
= colour
1221 # Switch graph data region represents a task
1223 class SwitchGraphDataRegion(GraphDataRegion
):
1225 def __init__(self
, key
, exec_comm_id
, pid
, tid
, comm
, thread_id
, comm_id
):
1226 super(SwitchGraphDataRegion
, self
).__init
__(key
)
1228 self
.title
= str(pid
) + " / " + str(tid
) + " " + comm
1229 # Order graph legend within exec comm by pid / tid / time
1230 self
.ordinal
= str(pid
).rjust(16) + str(exec_comm_id
).rjust(8) + str(tid
).rjust(16)
1231 self
.exec_comm_id
= exec_comm_id
1235 self
.thread_id
= thread_id
1236 self
.comm_id
= comm_id
1240 class GraphDataPoint():
1242 def __init__(self
, data
, index
, x
, y
, altx
=None, alty
=None, hregion
=None, vregion
=None):
1249 self
.hregion
= hregion
1250 self
.vregion
= vregion
1252 # Graph data (single graph) base class
1254 class GraphData(object):
1256 def __init__(self
, collection
, xbase
=Decimal(0), ybase
=Decimal(0)):
1257 self
.collection
= collection
1263 def AddPoint(self
, x
, y
, altx
=None, alty
=None, hregion
=None, vregion
=None):
1264 index
= len(self
.points
)
1266 x
= float(Decimal(x
) - self
.xbase
)
1267 y
= float(Decimal(y
) - self
.ybase
)
1269 self
.points
.append(GraphDataPoint(self
, index
, x
, y
, altx
, alty
, hregion
, vregion
))
1271 def XToData(self
, x
):
1272 return Decimal(x
) + self
.xbase
1274 def YToData(self
, y
):
1275 return Decimal(y
) + self
.ybase
1277 # Switch graph data (for one CPU)
1279 class SwitchGraphData(GraphData
):
1281 def __init__(self
, db
, collection
, cpu
, xbase
):
1282 super(SwitchGraphData
, self
).__init
__(collection
, xbase
)
1285 self
.title
= "CPU " + str(cpu
)
1286 self
.SelectSwitches(db
)
1288 def SelectComms(self
, db
, thread_id
, last_comm_id
, start_time
, end_time
):
1289 query
= QSqlQuery(db
)
1290 QueryExec(query
, "SELECT id, c_time"
1292 " WHERE c_thread_id = " + str(thread_id
) +
1293 " AND exec_flag = " + self
.collection
.glb
.dbref
.TRUE
+
1294 " AND c_time >= " + str(start_time
) +
1295 " AND c_time <= " + str(end_time
) +
1296 " ORDER BY c_time, id")
1298 comm_id
= query
.value(0)
1299 if comm_id
== last_comm_id
:
1301 time
= query
.value(1)
1302 hregion
= self
.HRegion(db
, thread_id
, comm_id
, time
)
1303 self
.AddPoint(time
, 1000, None, None, hregion
)
1305 def SelectSwitches(self
, db
):
1308 last_thread_id
= None
1309 query
= QSqlQuery(db
)
1310 QueryExec(query
, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags"
1311 " FROM context_switches"
1312 " WHERE machine_id = " + str(self
.collection
.machine_id
) +
1313 " AND cpu = " + str(self
.cpu
) +
1314 " ORDER BY time, id")
1316 flags
= int(query
.value(5))
1318 # Schedule-out: detect and add exec's
1319 if last_thread_id
== query
.value(1) and last_comm_id
is not None and last_comm_id
!= query
.value(3):
1320 self
.SelectComms(db
, last_thread_id
, last_comm_id
, last_time
, query
.value(0))
1322 # Schedule-in: add data point
1323 if len(self
.points
) == 0:
1324 start_time
= self
.collection
.glb
.StartTime(self
.collection
.machine_id
)
1325 hregion
= self
.HRegion(db
, query
.value(1), query
.value(3), start_time
)
1326 self
.AddPoint(start_time
, 1000, None, None, hregion
)
1327 time
= query
.value(0)
1328 comm_id
= query
.value(4)
1329 thread_id
= query
.value(2)
1330 hregion
= self
.HRegion(db
, thread_id
, comm_id
, time
)
1331 self
.AddPoint(time
, 1000, None, None, hregion
)
1333 last_comm_id
= comm_id
1334 last_thread_id
= thread_id
1336 def NewHRegion(self
, db
, key
, thread_id
, comm_id
, time
):
1337 exec_comm_id
= ExecComm(db
, thread_id
, time
)
1338 query
= QSqlQuery(db
)
1339 QueryExec(query
, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id
))
1341 pid
= query
.value(0)
1342 tid
= query
.value(1)
1346 query
= QSqlQuery(db
)
1347 QueryExec(query
, "SELECT comm FROM comms WHERE id = " + str(comm_id
))
1349 comm
= query
.value(0)
1352 return SwitchGraphDataRegion(key
, exec_comm_id
, pid
, tid
, comm
, thread_id
, comm_id
)
1354 def HRegion(self
, db
, thread_id
, comm_id
, time
):
1355 key
= str(thread_id
) + ":" + str(comm_id
)
1356 hregion
= self
.collection
.LookupHRegion(key
)
1358 hregion
= self
.NewHRegion(db
, key
, thread_id
, comm_id
, time
)
1359 self
.collection
.AddHRegion(key
, hregion
)
1362 # Graph data collection (multiple related graphs) base class
1364 class GraphDataCollection(object):
1366 def __init__(self
, glb
):
1370 self
.xrangelo
= None
1371 self
.xrangehi
= None
1372 self
.yrangelo
= None
1373 self
.yrangehi
= None
1376 def AddGraphData(self
, data
):
1377 self
.data
.append(data
)
1379 def LookupHRegion(self
, key
):
1380 if key
in self
.hregions
:
1381 return self
.hregions
[key
]
1384 def AddHRegion(self
, key
, hregion
):
1385 self
.hregions
[key
] = hregion
1387 # Switch graph data collection (SwitchGraphData for each CPU)
1389 class SwitchGraphDataCollection(GraphDataCollection
):
1391 def __init__(self
, glb
, db
, machine_id
):
1392 super(SwitchGraphDataCollection
, self
).__init
__(glb
)
1394 self
.machine_id
= machine_id
1395 self
.cpus
= self
.SelectCPUs(db
)
1397 self
.xrangelo
= glb
.StartTime(machine_id
)
1398 self
.xrangehi
= glb
.FinishTime(machine_id
)
1400 self
.yrangelo
= Decimal(0)
1401 self
.yrangehi
= Decimal(1000)
1403 for cpu
in self
.cpus
:
1404 self
.AddGraphData(SwitchGraphData(db
, self
, cpu
, self
.xrangelo
))
1406 def SelectCPUs(self
, db
):
1408 query
= QSqlQuery(db
)
1409 QueryExec(query
, "SELECT DISTINCT cpu"
1410 " FROM context_switches"
1411 " WHERE machine_id = " + str(self
.machine_id
))
1413 cpus
.append(int(query
.value(0)))
1416 # Switch graph data graphics item displays the graphed data
1418 class SwitchGraphDataGraphicsItem(QGraphicsItem
):
1420 def __init__(self
, data
, graph_width
, graph_height
, attrs
, event_handler
, parent
=None):
1421 super(SwitchGraphDataGraphicsItem
, self
).__init
__(parent
)
1424 self
.graph_width
= graph_width
1425 self
.graph_height
= graph_height
1427 self
.event_handler
= event_handler
1428 self
.setAcceptHoverEvents(True)
1430 def boundingRect(self
):
1431 return QRectF(0, 0, self
.graph_width
, self
.graph_height
)
1433 def PaintPoint(self
, painter
, last
, x
):
1434 if not(last
is None or last
.hregion
.pid
== 0 or x
< self
.attrs
.subrange
.x
.lo
):
1435 if last
.x
< self
.attrs
.subrange
.x
.lo
:
1436 x0
= self
.attrs
.subrange
.x
.lo
1439 if x
> self
.attrs
.subrange
.x
.hi
:
1440 x1
= self
.attrs
.subrange
.x
.hi
1443 x0
= self
.attrs
.XToPixel(x0
)
1444 x1
= self
.attrs
.XToPixel(x1
)
1446 y0
= self
.attrs
.YToPixel(last
.y
)
1448 colour
= self
.attrs
.region_attributes
[last
.hregion
.key
].colour
1452 painter
.setPen(colour
)
1453 painter
.drawLine(x0
, self
.graph_height
- y0
, x0
, self
.graph_height
)
1455 painter
.fillRect(x0
, self
.graph_height
- y0
, width
, self
.graph_height
- 1, colour
)
1457 def paint(self
, painter
, option
, widget
):
1459 for point
in self
.data
.points
:
1460 self
.PaintPoint(painter
, last
, point
.x
)
1461 if point
.x
> self
.attrs
.subrange
.x
.hi
:
1464 self
.PaintPoint(painter
, last
, self
.attrs
.subrange
.x
.hi
+ 1)
1466 def BinarySearchPoint(self
, target
):
1468 higher_pos
= len(self
.data
.points
)
1470 pos
= int((lower_pos
+ higher_pos
) / 2)
1471 val
= self
.data
.points
[pos
].x
1476 if higher_pos
<= lower_pos
+ 1:
1479 def XPixelToData(self
, x
):
1480 x
= self
.attrs
.PixelToX(x
)
1481 if x
< self
.data
.points
[0].x
:
1486 pos
= self
.BinarySearchPoint(x
)
1488 return (low
, pos
, self
.data
.XToData(x
))
1490 def EventToData(self
, event
):
1491 no_data
= (None,) * 4
1492 if len(self
.data
.points
) < 1:
1497 low0
, pos0
, time_from
= self
.XPixelToData(x
)
1498 low1
, pos1
, time_to
= self
.XPixelToData(x
+ 1)
1502 for i
in xrange(pos0
, pos1
+ 1):
1503 hregion
= self
.data
.points
[i
].hregion
1504 hregions
.add(hregion
)
1508 time
= self
.data
.XToData(self
.data
.points
[i
].x
)
1509 hregion_times
.append((hregion
, time
))
1510 return (time_from
, time_to
, hregions
, hregion_times
)
1512 def hoverMoveEvent(self
, event
):
1513 time_from
, time_to
, hregions
, hregion_times
= self
.EventToData(event
)
1514 if time_from
is not None:
1515 self
.event_handler
.PointEvent(self
.data
.cpu
, time_from
, time_to
, hregions
)
1517 def hoverLeaveEvent(self
, event
):
1518 self
.event_handler
.NoPointEvent()
1520 def mousePressEvent(self
, event
):
1521 if event
.button() != Qt
.RightButton
:
1522 super(SwitchGraphDataGraphicsItem
, self
).mousePressEvent(event
)
1524 time_from
, time_to
, hregions
, hregion_times
= self
.EventToData(event
)
1526 self
.event_handler
.RightClickEvent(self
.data
.cpu
, hregion_times
, event
.screenPos())
1528 # X-axis graphics item
1530 class XAxisGraphicsItem(QGraphicsItem
):
1532 def __init__(self
, width
, parent
=None):
1533 super(XAxisGraphicsItem
, self
).__init
__(parent
)
1536 self
.max_mark_sz
= 4
1537 self
.height
= self
.max_mark_sz
+ 1
1539 def boundingRect(self
):
1540 return QRectF(0, 0, self
.width
, self
.height
)
1543 attrs
= self
.parentItem().attrs
1544 subrange
= attrs
.subrange
.x
1545 t
= subrange
.hi
- subrange
.lo
1546 s
= (3.0 * t
) / self
.width
1552 def PaintMarks(self
, painter
, at_y
, lo
, hi
, step
, i
):
1553 attrs
= self
.parentItem().attrs
1556 xp
= attrs
.XToPixel(x
)
1563 sz
= self
.max_mark_sz
1565 painter
.drawLine(xp
, at_y
, xp
, at_y
+ sz
)
1569 def paint(self
, painter
, option
, widget
):
1570 # Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
1571 painter
.drawLine(0, 0, self
.width
- 1, 0)
1573 attrs
= self
.parentItem().attrs
1574 subrange
= attrs
.subrange
.x
1576 x_offset
= n
- (subrange
.lo
% n
)
1579 x
= subrange
.lo
+ x_offset
1581 self
.PaintMarks(painter
, 0, x
, subrange
.hi
, n
, i
)
1583 def ScaleDimensions(self
):
1585 attrs
= self
.parentItem().attrs
1586 lo
= attrs
.subrange
.x
.lo
1587 hi
= (n
* 10.0) + lo
1588 width
= attrs
.XToPixel(hi
)
1591 return (n
, lo
, hi
, width
)
1593 def PaintScale(self
, painter
, at_x
, at_y
):
1594 n
, lo
, hi
, width
= self
.ScaleDimensions()
1597 painter
.drawLine(at_x
, at_y
, at_x
+ width
, at_y
)
1598 self
.PaintMarks(painter
, at_y
, lo
, hi
, n
, 0)
1600 def ScaleWidth(self
):
1601 n
, lo
, hi
, width
= self
.ScaleDimensions()
1604 def ScaleHeight(self
):
1607 def ScaleUnit(self
):
1608 return self
.Step() * 10
1610 # Scale graphics item base class
1612 class ScaleGraphicsItem(QGraphicsItem
):
1614 def __init__(self
, axis
, parent
=None):
1615 super(ScaleGraphicsItem
, self
).__init
__(parent
)
1618 def boundingRect(self
):
1619 scale_width
= self
.axis
.ScaleWidth()
1622 return QRectF(0, 0, self
.axis
.ScaleWidth() + 100, self
.axis
.ScaleHeight())
1624 def paint(self
, painter
, option
, widget
):
1625 scale_width
= self
.axis
.ScaleWidth()
1628 self
.axis
.PaintScale(painter
, 0, 5)
1630 painter
.drawText(QPointF(x
, 10), self
.Text())
1633 return self
.axis
.ScaleUnit()
1638 # Switch graph scale graphics item
1640 class SwitchScaleGraphicsItem(ScaleGraphicsItem
):
1642 def __init__(self
, axis
, parent
=None):
1643 super(SwitchScaleGraphicsItem
, self
).__init
__(axis
, parent
)
1647 if unit
>= 1000000000:
1648 unit
= int(unit
/ 1000000000)
1650 elif unit
>= 1000000:
1651 unit
= int(unit
/ 1000000)
1654 unit
= int(unit
/ 1000)
1659 return " = " + str(unit
) + " " + us
1661 # Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
1663 class SwitchGraphGraphicsItem(QGraphicsItem
):
1665 def __init__(self
, collection
, data
, attrs
, event_handler
, first
, parent
=None):
1666 super(SwitchGraphGraphicsItem
, self
).__init
__(parent
)
1667 self
.collection
= collection
1670 self
.event_handler
= event_handler
1675 self
.title_graphics
= QGraphicsSimpleTextItem(data
.title
, self
)
1677 self
.title_graphics
.setPos(margin
, margin
)
1678 graph_width
= attrs
.XToPixel(attrs
.subrange
.x
.hi
) + 1
1679 graph_height
= attrs
.YToPixel(attrs
.subrange
.y
.hi
) + 1
1681 self
.graph_origin_x
= margin
+ title_width
+ margin
1682 self
.graph_origin_y
= graph_height
+ margin
1686 self
.yline
= QGraphicsLineItem(0, 0, 0, graph_height
, self
)
1688 self
.x_axis
= XAxisGraphicsItem(graph_width
, self
)
1689 self
.x_axis
.setPos(self
.graph_origin_x
, self
.graph_origin_y
+ 1)
1692 self
.scale_item
= SwitchScaleGraphicsItem(self
.x_axis
, self
)
1693 self
.scale_item
.setPos(self
.graph_origin_x
, self
.graph_origin_y
+ 10)
1695 self
.yline
.setPos(self
.graph_origin_x
- y_axis_size
, self
.graph_origin_y
- graph_height
)
1697 self
.axis_point
= QGraphicsLineItem(0, 0, 0, 0, self
)
1698 self
.axis_point
.setPos(self
.graph_origin_x
- 1, self
.graph_origin_y
+1)
1700 self
.width
= self
.graph_origin_x
+ graph_width
+ margin
1701 self
.height
= self
.graph_origin_y
+ margin
1703 self
.graph
= SwitchGraphDataGraphicsItem(data
, graph_width
, graph_height
, attrs
, event_handler
, self
)
1704 self
.graph
.setPos(self
.graph_origin_x
, self
.graph_origin_y
- graph_height
)
1706 if parent
and 'EnableRubberBand' in dir(parent
):
1707 parent
.EnableRubberBand(self
.graph_origin_x
, self
.graph_origin_x
+ graph_width
- 1, self
)
1709 def boundingRect(self
):
1710 return QRectF(0, 0, self
.width
, self
.height
)
1712 def paint(self
, painter
, option
, widget
):
1715 def RBXToPixel(self
, x
):
1716 return self
.attrs
.PixelToX(x
- self
.graph_origin_x
)
1718 def RBXRangeToPixel(self
, x0
, x1
):
1719 return (self
.RBXToPixel(x0
), self
.RBXToPixel(x1
+ 1))
1721 def RBPixelToTime(self
, x
):
1722 if x
< self
.data
.points
[0].x
:
1723 return self
.data
.XToData(0)
1724 return self
.data
.XToData(x
)
1726 def RBEventTimes(self
, x0
, x1
):
1727 x0
, x1
= self
.RBXRangeToPixel(x0
, x1
)
1728 time_from
= self
.RBPixelToTime(x0
)
1729 time_to
= self
.RBPixelToTime(x1
)
1730 return (time_from
, time_to
)
1732 def RBEvent(self
, x0
, x1
):
1733 time_from
, time_to
= self
.RBEventTimes(x0
, x1
)
1734 self
.event_handler
.RangeEvent(time_from
, time_to
)
1736 def RBMoveEvent(self
, x0
, x1
):
1739 self
.RBEvent(x0
, x1
)
1741 def RBReleaseEvent(self
, x0
, x1
, selection_state
):
1744 x0
, x1
= self
.RBXRangeToPixel(x0
, x1
)
1745 self
.event_handler
.SelectEvent(x0
, x1
, selection_state
)
1747 # Graphics item to draw a vertical bracket (used to highlight "forward" sub-range)
1749 class VerticalBracketGraphicsItem(QGraphicsItem
):
1751 def __init__(self
, parent
=None):
1752 super(VerticalBracketGraphicsItem
, self
).__init
__(parent
)
1758 def SetSize(self
, width
, height
):
1759 self
.width
= width
+ 1
1760 self
.height
= height
+ 1
1762 def boundingRect(self
):
1763 return QRectF(0, 0, self
.width
, self
.height
)
1765 def paint(self
, painter
, option
, widget
):
1766 colour
= QColor(255, 255, 0, 32)
1767 painter
.fillRect(0, 0, self
.width
, self
.height
, colour
)
1769 y1
= self
.height
- 1
1770 painter
.drawLine(0, 0, x1
, 0)
1771 painter
.drawLine(0, 0, 0, 3)
1772 painter
.drawLine(x1
, 0, x1
, 3)
1773 painter
.drawLine(0, y1
, x1
, y1
)
1774 painter
.drawLine(0, y1
, 0, y1
- 3)
1775 painter
.drawLine(x1
, y1
, x1
, y1
- 3)
1777 # Graphics item to contain graphs arranged vertically
1779 class VertcalGraphSetGraphicsItem(QGraphicsItem
):
1781 def __init__(self
, collection
, attrs
, event_handler
, child_class
, parent
=None):
1782 super(VertcalGraphSetGraphicsItem
, self
).__init
__(parent
)
1784 self
.collection
= collection
1789 self
.height
= self
.top
1791 self
.rubber_band
= None
1792 self
.rb_enabled
= False
1795 for data
in collection
.data
:
1796 child
= child_class(collection
, data
, attrs
, event_handler
, first
, self
)
1797 child
.setPos(0, self
.height
+ 1)
1798 rect
= child
.boundingRect()
1799 if rect
.right() > self
.width
:
1800 self
.width
= rect
.right()
1801 self
.height
= self
.height
+ rect
.bottom() + 1
1804 self
.bracket
= VerticalBracketGraphicsItem(self
)
1806 def EnableRubberBand(self
, xlo
, xhi
, rb_event_handler
):
1809 self
.rb_enabled
= True
1810 self
.rb_in_view
= False
1811 self
.setAcceptedMouseButtons(Qt
.LeftButton
)
1814 self
.rb_event_handler
= rb_event_handler
1815 self
.mousePressEvent
= self
.MousePressEvent
1816 self
.mouseMoveEvent
= self
.MouseMoveEvent
1817 self
.mouseReleaseEvent
= self
.MouseReleaseEvent
1819 def boundingRect(self
):
1820 return QRectF(0, 0, self
.width
, self
.height
)
1822 def paint(self
, painter
, option
, widget
):
1825 def RubberBandParent(self
):
1826 scene
= self
.scene()
1827 view
= scene
.views()[0]
1828 viewport
= view
.viewport()
1831 def RubberBandSetGeometry(self
, rect
):
1832 scene_rectf
= self
.mapRectToScene(QRectF(rect
))
1833 scene
= self
.scene()
1834 view
= scene
.views()[0]
1835 poly
= view
.mapFromScene(scene_rectf
)
1836 self
.rubber_band
.setGeometry(poly
.boundingRect())
1838 def SetSelection(self
, selection_state
):
1839 if self
.rubber_band
:
1841 self
.RubberBandSetGeometry(selection_state
)
1842 self
.rubber_band
.show()
1844 self
.rubber_band
.hide()
1846 def SetBracket(self
, rect
):
1848 x
, y
, width
, height
= rect
.x(), rect
.y(), rect
.width(), rect
.height()
1849 self
.bracket
.setPos(x
, y
)
1850 self
.bracket
.SetSize(width
, height
)
1855 def RubberBandX(self
, event
):
1856 x
= event
.pos().toPoint().x()
1859 elif x
> self
.rb_xhi
:
1862 self
.rb_in_view
= True
1865 def RubberBandRect(self
, x
):
1866 if self
.rb_origin
.x() <= x
:
1867 width
= x
- self
.rb_origin
.x()
1868 rect
= QRect(self
.rb_origin
, QSize(width
, self
.height
))
1870 width
= self
.rb_origin
.x() - x
1871 top_left
= QPoint(self
.rb_origin
.x() - width
, self
.rb_origin
.y())
1872 rect
= QRect(top_left
, QSize(width
, self
.height
))
1875 def MousePressEvent(self
, event
):
1876 self
.rb_in_view
= False
1877 x
= self
.RubberBandX(event
)
1878 self
.rb_origin
= QPoint(x
, self
.top
)
1879 if self
.rubber_band
is None:
1880 self
.rubber_band
= QRubberBand(QRubberBand
.Rectangle
, self
.RubberBandParent())
1881 self
.RubberBandSetGeometry(QRect(self
.rb_origin
, QSize(0, self
.height
)))
1883 self
.rubber_band
.show()
1884 self
.rb_event_handler
.RBMoveEvent(x
, x
)
1886 self
.rubber_band
.hide()
1888 def MouseMoveEvent(self
, event
):
1889 x
= self
.RubberBandX(event
)
1890 rect
= self
.RubberBandRect(x
)
1891 self
.RubberBandSetGeometry(rect
)
1893 self
.rubber_band
.show()
1894 self
.rb_event_handler
.RBMoveEvent(self
.rb_origin
.x(), x
)
1896 def MouseReleaseEvent(self
, event
):
1897 x
= self
.RubberBandX(event
)
1899 selection_state
= self
.RubberBandRect(x
)
1901 selection_state
= None
1902 self
.rb_event_handler
.RBReleaseEvent(self
.rb_origin
.x(), x
, selection_state
)
1904 # Switch graph legend data model
1906 class SwitchGraphLegendModel(QAbstractTableModel
):
1908 def __init__(self
, collection
, region_attributes
, parent
=None):
1909 super(SwitchGraphLegendModel
, self
).__init
__(parent
)
1911 self
.region_attributes
= region_attributes
1913 self
.child_items
= sorted(collection
.hregions
.values(), key
=GraphDataRegionOrdinal
)
1914 self
.child_count
= len(self
.child_items
)
1916 self
.highlight_set
= set()
1918 self
.column_headers
= ("pid", "tid", "comm")
1920 def rowCount(self
, parent
):
1921 return self
.child_count
1923 def headerData(self
, section
, orientation
, role
):
1924 if role
!= Qt
.DisplayRole
:
1926 if orientation
!= Qt
.Horizontal
:
1928 return self
.columnHeader(section
)
1930 def index(self
, row
, column
, parent
):
1931 return self
.createIndex(row
, column
, self
.child_items
[row
])
1933 def columnCount(self
, parent
=None):
1934 return len(self
.column_headers
)
1936 def columnHeader(self
, column
):
1937 return self
.column_headers
[column
]
1939 def data(self
, index
, role
):
1940 if role
== Qt
.BackgroundRole
:
1941 child
= self
.child_items
[index
.row()]
1942 if child
in self
.highlight_set
:
1943 return self
.region_attributes
[child
.key
].colour
1945 if role
== Qt
.ForegroundRole
:
1946 child
= self
.child_items
[index
.row()]
1947 if child
in self
.highlight_set
:
1948 return QColor(255, 255, 255)
1949 return self
.region_attributes
[child
.key
].colour
1950 if role
!= Qt
.DisplayRole
:
1952 hregion
= self
.child_items
[index
.row()]
1953 col
= index
.column()
1962 def SetHighlight(self
, row
, set_highlight
):
1963 child
= self
.child_items
[row
]
1964 top_left
= self
.createIndex(row
, 0, child
)
1965 bottom_right
= self
.createIndex(row
, len(self
.column_headers
) - 1, child
)
1966 self
.dataChanged
.emit(top_left
, bottom_right
)
1968 def Highlight(self
, highlight_set
):
1969 for row
in xrange(self
.child_count
):
1970 child
= self
.child_items
[row
]
1971 if child
in self
.highlight_set
:
1972 if child
not in highlight_set
:
1973 self
.SetHighlight(row
, False)
1974 elif child
in highlight_set
:
1975 self
.SetHighlight(row
, True)
1976 self
.highlight_set
= highlight_set
1978 # Switch graph legend is a table
1980 class SwitchGraphLegend(QWidget
):
1982 def __init__(self
, collection
, region_attributes
, parent
=None):
1983 super(SwitchGraphLegend
, self
).__init
__(parent
)
1985 self
.data_model
= SwitchGraphLegendModel(collection
, region_attributes
)
1987 self
.model
= QSortFilterProxyModel()
1988 self
.model
.setSourceModel(self
.data_model
)
1990 self
.view
= QTableView()
1991 self
.view
.setModel(self
.model
)
1992 self
.view
.setEditTriggers(QAbstractItemView
.NoEditTriggers
)
1993 self
.view
.verticalHeader().setVisible(False)
1994 self
.view
.sortByColumn(-1, Qt
.AscendingOrder
)
1995 self
.view
.setSortingEnabled(True)
1996 self
.view
.resizeColumnsToContents()
1997 self
.view
.resizeRowsToContents()
1999 self
.vbox
= VBoxLayout(self
.view
)
2000 self
.setLayout(self
.vbox
)
2002 sz1
= self
.view
.columnWidth(0) + self
.view
.columnWidth(1) + self
.view
.columnWidth(2) + 2
2003 sz1
= sz1
+ self
.view
.verticalScrollBar().sizeHint().width()
2004 self
.saved_size
= sz1
2006 def resizeEvent(self
, event
):
2007 self
.saved_size
= self
.size().width()
2008 super(SwitchGraphLegend
, self
).resizeEvent(event
)
2010 def Highlight(self
, highlight_set
):
2011 self
.data_model
.Highlight(highlight_set
)
2014 def changeEvent(self
, event
):
2015 if event
.type() == QEvent
.FontChange
:
2016 self
.view
.resizeRowsToContents()
2017 self
.view
.resizeColumnsToContents()
2018 # Need to resize rows again after column resize
2019 self
.view
.resizeRowsToContents()
2020 super(SwitchGraphLegend
, self
).changeEvent(event
)
2022 # Random colour generation
2024 def RGBColourTooLight(r
, g
, b
):
2029 if r
<= 180 and g
<= 180:
2035 def GenerateColours(x
):
2037 for i
in xrange(1, x
):
2038 cs
.append(int((255.0 / i
) + 0.5))
2043 # Exclude black and colours that look too light against a white background
2044 if (r
, g
, b
) == (0, 0, 0) or RGBColourTooLight(r
, g
, b
):
2046 colours
.append(QColor(r
, g
, b
))
2049 def GenerateNColours(n
):
2050 for x
in xrange(2, n
+ 2):
2051 colours
= GenerateColours(x
)
2052 if len(colours
) >= n
:
2056 def GenerateNRandomColours(n
, seed
):
2057 colours
= GenerateNColours(n
)
2059 random
.shuffle(colours
)
2062 # Graph attributes, in particular the scale and subrange that change when zooming
2064 class GraphAttributes():
2066 def __init__(self
, scale
, subrange
, region_attributes
, dp
):
2068 self
.subrange
= subrange
2069 self
.region_attributes
= region_attributes
2070 # Rounding avoids errors due to finite floating point precision
2071 self
.dp
= dp
# data decimal places
2074 def XToPixel(self
, x
):
2075 return int(round((x
- self
.subrange
.x
.lo
) * self
.scale
.x
, self
.pdp
.x
))
2077 def YToPixel(self
, y
):
2078 return int(round((y
- self
.subrange
.y
.lo
) * self
.scale
.y
, self
.pdp
.y
))
2080 def PixelToXRounded(self
, px
):
2081 return round((round(px
, 0) / self
.scale
.x
), self
.dp
.x
) + self
.subrange
.x
.lo
2083 def PixelToYRounded(self
, py
):
2084 return round((round(py
, 0) / self
.scale
.y
), self
.dp
.y
) + self
.subrange
.y
.lo
2086 def PixelToX(self
, px
):
2087 x
= self
.PixelToXRounded(px
)
2089 rt
= self
.XToPixel(x
)
2094 def PixelToY(self
, py
):
2095 y
= self
.PixelToYRounded(py
)
2097 rt
= self
.YToPixel(y
)
2102 def ToPDP(self
, dp
, scale
):
2103 # Calculate pixel decimal places:
2104 # (10 ** dp) is the minimum delta in the data
2105 # scale it to get the minimum delta in pixels
2106 # log10 gives the number of decimals places negatively
2107 # subtrace 1 to divide by 10
2108 # round to the lower negative number
2109 # change the sign to get the number of decimals positively
2110 x
= math
.log10((10 ** dp
) * scale
)
2113 x
= -int(math
.floor(x
) - 0.1)
2119 x
= self
.ToPDP(self
.dp
.x
, self
.scale
.x
)
2120 y
= self
.ToPDP(self
.dp
.y
, self
.scale
.y
)
2121 self
.pdp
= XY(x
, y
) # pixel decimal places
2123 # Switch graph splitter which divides the CPU graphs from the legend
2125 class SwitchGraphSplitter(QSplitter
):
2127 def __init__(self
, parent
=None):
2128 super(SwitchGraphSplitter
, self
).__init
__(parent
)
2130 self
.first_time
= False
2132 def resizeEvent(self
, ev
):
2134 self
.first_time
= False
2135 sz1
= self
.widget(1).view
.columnWidth(0) + self
.widget(1).view
.columnWidth(1) + self
.widget(1).view
.columnWidth(2) + 2
2136 sz1
= sz1
+ self
.widget(1).view
.verticalScrollBar().sizeHint().width()
2137 sz0
= self
.size().width() - self
.handleWidth() - sz1
2138 self
.setSizes([sz0
, sz1
])
2139 elif not(self
.widget(1).saved_size
is None):
2140 sz1
= self
.widget(1).saved_size
2141 sz0
= self
.size().width() - self
.handleWidth() - sz1
2142 self
.setSizes([sz0
, sz1
])
2143 super(SwitchGraphSplitter
, self
).resizeEvent(ev
)
2145 # Graph widget base class
2147 class GraphWidget(QWidget
):
2149 graph_title_changed
= Signal(object)
2151 def __init__(self
, parent
=None):
2152 super(GraphWidget
, self
).__init
__(parent
)
2154 def GraphTitleChanged(self
, title
):
2155 self
.graph_title_changed
.emit(title
)
2160 # Display time in s, ms, us or ns
2164 if val
>= 1000000000:
2165 return "{} s".format((val
/ 1000000000).quantize(Decimal("0.000000001")))
2167 return "{} ms".format((val
/ 1000000).quantize(Decimal("0.000001")))
2169 return "{} us".format((val
/ 1000).quantize(Decimal("0.001")))
2170 return "{} ns".format(val
.quantize(Decimal("1")))
2172 # Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons
2174 class SwitchGraphWidget(GraphWidget
):
2176 def __init__(self
, glb
, collection
, parent
=None):
2177 super(SwitchGraphWidget
, self
).__init
__(parent
)
2180 self
.collection
= collection
2182 self
.back_state
= []
2183 self
.forward_state
= []
2184 self
.selection_state
= (None, None)
2185 self
.fwd_rect
= None
2186 self
.start_time
= self
.glb
.StartTime(collection
.machine_id
)
2189 hregions
= collection
.hregions
.values()
2190 colours
= GenerateNRandomColours(len(hregions
), 1013)
2191 region_attributes
= {}
2192 for hregion
in hregions
:
2193 if hregion
.pid
== 0 and hregion
.tid
== 0:
2194 region_attributes
[hregion
.key
] = GraphRegionAttribute(QColor(0, 0, 0))
2196 region_attributes
[hregion
.key
] = GraphRegionAttribute(colours
[i
])
2199 # Default to entire range
2200 xsubrange
= Subrange(0.0, float(collection
.xrangehi
- collection
.xrangelo
) + 1.0)
2201 ysubrange
= Subrange(0.0, float(collection
.yrangehi
- collection
.yrangelo
) + 1.0)
2202 subrange
= XY(xsubrange
, ysubrange
)
2204 scale
= self
.GetScaleForRange(subrange
)
2206 self
.attrs
= GraphAttributes(scale
, subrange
, region_attributes
, collection
.dp
)
2208 self
.item
= VertcalGraphSetGraphicsItem(collection
, self
.attrs
, self
, SwitchGraphGraphicsItem
)
2210 self
.scene
= QGraphicsScene()
2211 self
.scene
.addItem(self
.item
)
2213 self
.view
= QGraphicsView(self
.scene
)
2214 self
.view
.centerOn(0, 0)
2215 self
.view
.setAlignment(Qt
.AlignLeft | Qt
.AlignTop
)
2217 self
.legend
= SwitchGraphLegend(collection
, region_attributes
)
2219 self
.splitter
= SwitchGraphSplitter()
2220 self
.splitter
.addWidget(self
.view
)
2221 self
.splitter
.addWidget(self
.legend
)
2223 self
.point_label
= QLabel("")
2224 self
.point_label
.setSizePolicy(QSizePolicy
.Preferred
, QSizePolicy
.Fixed
)
2226 self
.back_button
= QToolButton()
2227 self
.back_button
.setIcon(self
.style().standardIcon(QStyle
.SP_ArrowLeft
))
2228 self
.back_button
.setDisabled(True)
2229 self
.back_button
.released
.connect(lambda: self
.Back())
2231 self
.forward_button
= QToolButton()
2232 self
.forward_button
.setIcon(self
.style().standardIcon(QStyle
.SP_ArrowRight
))
2233 self
.forward_button
.setDisabled(True)
2234 self
.forward_button
.released
.connect(lambda: self
.Forward())
2236 self
.zoom_button
= QToolButton()
2237 self
.zoom_button
.setText("Zoom")
2238 self
.zoom_button
.setDisabled(True)
2239 self
.zoom_button
.released
.connect(lambda: self
.Zoom())
2241 self
.hbox
= HBoxLayout(self
.back_button
, self
.forward_button
, self
.zoom_button
, self
.point_label
)
2243 self
.vbox
= VBoxLayout(self
.splitter
, self
.hbox
)
2245 self
.setLayout(self
.vbox
)
2247 def GetScaleForRangeX(self
, xsubrange
):
2248 # Default graph 1000 pixels wide
2250 r
= xsubrange
.hi
- xsubrange
.lo
2253 def GetScaleForRangeY(self
, ysubrange
):
2254 # Default graph 50 pixels high
2256 r
= ysubrange
.hi
- ysubrange
.lo
2259 def GetScaleForRange(self
, subrange
):
2260 # Default graph 1000 pixels wide, 50 pixels high
2261 xscale
= self
.GetScaleForRangeX(subrange
.x
)
2262 yscale
= self
.GetScaleForRangeY(subrange
.y
)
2263 return XY(xscale
, yscale
)
2265 def PointEvent(self
, cpu
, time_from
, time_to
, hregions
):
2266 text
= "CPU: " + str(cpu
)
2267 time_from
= time_from
.quantize(Decimal(1))
2268 rel_time_from
= time_from
- self
.glb
.StartTime(self
.collection
.machine_id
)
2269 text
= text
+ " Time: " + str(time_from
) + " (+" + ToTimeStr(rel_time_from
) + ")"
2270 self
.point_label
.setText(text
)
2271 self
.legend
.Highlight(hregions
)
2273 def RightClickEvent(self
, cpu
, hregion_times
, pos
):
2274 if not IsSelectable(self
.glb
.db
, "calls", "WHERE parent_id >= 0"):
2276 menu
= QMenu(self
.view
)
2277 for hregion
, time
in hregion_times
:
2278 thread_at_time
= (hregion
.exec_comm_id
, hregion
.thread_id
, time
)
2279 menu_text
= "Show Call Tree for {} {}:{} at {}".format(hregion
.comm
, hregion
.pid
, hregion
.tid
, time
)
2280 menu
.addAction(CreateAction(menu_text
, "Show Call Tree", lambda a
=None, args
=thread_at_time
: self
.RightClickSelect(args
), self
.view
))
2283 def RightClickSelect(self
, args
):
2284 CallTreeWindow(self
.glb
, self
.glb
.mainwindow
, thread_at_time
=args
)
2286 def NoPointEvent(self
):
2287 self
.point_label
.setText("")
2288 self
.legend
.Highlight({})
2290 def RangeEvent(self
, time_from
, time_to
):
2291 time_from
= time_from
.quantize(Decimal(1))
2292 time_to
= time_to
.quantize(Decimal(1))
2293 if time_to
<= time_from
:
2294 self
.point_label
.setText("")
2296 rel_time_from
= time_from
- self
.start_time
2297 rel_time_to
= time_to
- self
.start_time
2298 text
= " Time: " + str(time_from
) + " (+" + ToTimeStr(rel_time_from
) + ") to: " + str(time_to
) + " (+" + ToTimeStr(rel_time_to
) + ")"
2299 text
= text
+ " duration: " + ToTimeStr(time_to
- time_from
)
2300 self
.point_label
.setText(text
)
2302 def BackState(self
):
2303 return (self
.attrs
.subrange
, self
.attrs
.scale
, self
.selection_state
, self
.fwd_rect
)
2305 def PushBackState(self
):
2306 state
= copy
.deepcopy(self
.BackState())
2307 self
.back_state
.append(state
)
2308 self
.back_button
.setEnabled(True)
2310 def PopBackState(self
):
2311 self
.attrs
.subrange
, self
.attrs
.scale
, self
.selection_state
, self
.fwd_rect
= self
.back_state
.pop()
2313 if not self
.back_state
:
2314 self
.back_button
.setDisabled(True)
2316 def PushForwardState(self
):
2317 state
= copy
.deepcopy(self
.BackState())
2318 self
.forward_state
.append(state
)
2319 self
.forward_button
.setEnabled(True)
2321 def PopForwardState(self
):
2322 self
.attrs
.subrange
, self
.attrs
.scale
, self
.selection_state
, self
.fwd_rect
= self
.forward_state
.pop()
2324 if not self
.forward_state
:
2325 self
.forward_button
.setDisabled(True)
2328 time_from
= self
.collection
.xrangelo
+ Decimal(self
.attrs
.subrange
.x
.lo
)
2329 time_to
= self
.collection
.xrangelo
+ Decimal(self
.attrs
.subrange
.x
.hi
)
2330 rel_time_from
= time_from
- self
.start_time
2331 rel_time_to
= time_to
- self
.start_time
2332 title
= "+" + ToTimeStr(rel_time_from
) + " to +" + ToTimeStr(rel_time_to
)
2333 title
= title
+ " (" + ToTimeStr(time_to
- time_from
) + ")"
2337 selected_subrange
, selection_state
= self
.selection_state
2338 self
.item
.SetSelection(selection_state
)
2339 self
.item
.SetBracket(self
.fwd_rect
)
2340 self
.zoom_button
.setDisabled(selected_subrange
is None)
2341 self
.GraphTitleChanged(self
.Title())
2342 self
.item
.update(self
.item
.boundingRect())
2345 if not self
.back_state
:
2347 self
.PushForwardState()
2352 if not self
.forward_state
:
2354 self
.PushBackState()
2355 self
.PopForwardState()
2358 def SelectEvent(self
, x0
, x1
, selection_state
):
2359 if selection_state
is None:
2360 selected_subrange
= None
2364 selected_subrange
= Subrange(x0
, x1
)
2365 self
.selection_state
= (selected_subrange
, selection_state
)
2366 self
.zoom_button
.setDisabled(selected_subrange
is None)
2369 selected_subrange
, selection_state
= self
.selection_state
2370 if selected_subrange
is None:
2372 self
.fwd_rect
= selection_state
2373 self
.item
.SetSelection(None)
2374 self
.PushBackState()
2375 self
.attrs
.subrange
.x
= selected_subrange
2376 self
.forward_state
= []
2377 self
.forward_button
.setDisabled(True)
2378 self
.selection_state
= (None, None)
2379 self
.fwd_rect
= None
2380 self
.attrs
.scale
.x
= self
.GetScaleForRangeX(self
.attrs
.subrange
.x
)
2384 # Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting
2386 class SlowInitClass():
2388 def __init__(self
, glb
, title
, init_fn
):
2389 self
.init_fn
= init_fn
2393 self
.msg_box
= QMessageBox(glb
.mainwindow
)
2394 self
.msg_box
.setText("Initializing " + title
+ ". Please wait.")
2395 self
.msg_box
.setWindowTitle("Initializing " + title
)
2396 self
.msg_box
.setWindowIcon(glb
.mainwindow
.style().standardIcon(QStyle
.SP_MessageBoxInformation
))
2398 self
.init_thread
= Thread(self
.ThreadFn
, glb
)
2399 self
.init_thread
.done
.connect(lambda: self
.Done(), Qt
.QueuedConnection
)
2401 self
.init_thread
.start()
2404 self
.msg_box
.done(0)
2406 def ThreadFn(self
, glb
):
2407 conn_name
= "SlowInitClass" + str(os
.getpid())
2408 db
, dbname
= glb
.dbref
.Open(conn_name
)
2409 self
.result
= self
.init_fn(db
)
2414 while not self
.done
:
2415 self
.msg_box
.exec_()
2416 self
.init_thread
.wait()
2419 def SlowInit(glb
, title
, init_fn
):
2420 init
= SlowInitClass(glb
, title
, init_fn
)
2421 return init
.Result()
2423 # Time chart by CPU window
2425 class TimeChartByCPUWindow(QMdiSubWindow
):
2427 def __init__(self
, glb
, parent
=None):
2428 super(TimeChartByCPUWindow
, self
).__init
__(parent
)
2431 self
.machine_id
= glb
.HostMachineId()
2432 self
.collection_name
= "SwitchGraphDataCollection " + str(self
.machine_id
)
2434 collection
= LookupModel(self
.collection_name
)
2435 if collection
is None:
2436 collection
= SlowInit(glb
, "Time Chart", self
.Init
)
2438 self
.widget
= SwitchGraphWidget(glb
, collection
, self
)
2439 self
.view
= self
.widget
2441 self
.base_title
= "Time Chart by CPU"
2442 self
.setWindowTitle(self
.base_title
+ self
.widget
.Title())
2443 self
.widget
.graph_title_changed
.connect(self
.GraphTitleChanged
)
2445 self
.setWidget(self
.widget
)
2447 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, self
.windowTitle())
2450 return LookupCreateModel(self
.collection_name
, lambda : SwitchGraphDataCollection(self
.glb
, db
, self
.machine_id
))
2452 def GraphTitleChanged(self
, title
):
2453 self
.setWindowTitle(self
.base_title
+ " : " + title
)
2455 # Child data item finder
2457 class ChildDataItemFinder():
2459 def __init__(self
, root
):
2461 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= (None,) * 5
2465 def FindSelect(self
):
2468 pattern
= re
.compile(self
.value
)
2469 for child
in self
.root
.child_items
:
2470 for column_data
in child
.data
:
2471 if re
.search(pattern
, str(column_data
)) is not None:
2472 self
.rows
.append(child
.row
)
2475 for child
in self
.root
.child_items
:
2476 for column_data
in child
.data
:
2477 if self
.value
in str(column_data
):
2478 self
.rows
.append(child
.row
)
2481 def FindValue(self
):
2483 if self
.last_value
!= self
.value
or self
.pattern
!= self
.last_pattern
:
2485 if not len(self
.rows
):
2487 return self
.rows
[self
.pos
]
2489 def FindThread(self
):
2490 if self
.direction
== 0 or self
.value
!= self
.last_value
or self
.pattern
!= self
.last_pattern
:
2491 row
= self
.FindValue()
2492 elif len(self
.rows
):
2493 if self
.direction
> 0:
2495 if self
.pos
>= len(self
.rows
):
2500 self
.pos
= len(self
.rows
) - 1
2501 row
= self
.rows
[self
.pos
]
2506 def Find(self
, value
, direction
, pattern
, context
, callback
):
2507 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= (value
, direction
,pattern
, self
.value
, self
.pattern
)
2508 # Use a thread so the UI is not blocked
2509 thread
= Thread(self
.FindThread
)
2510 thread
.done
.connect(lambda row
, t
=thread
, c
=callback
: self
.FindDone(t
, c
, row
), Qt
.QueuedConnection
)
2513 def FindDone(self
, thread
, callback
, row
):
2516 # Number of database records to fetch in one go
2518 glb_chunk_sz
= 10000
2520 # Background process for SQL data fetcher
2522 class SQLFetcherProcess():
2524 def __init__(self
, dbref
, sql
, buffer, head
, tail
, fetch_count
, fetching_done
, process_target
, wait_event
, fetched_event
, prep
):
2525 # Need a unique connection name
2526 conn_name
= "SQLFetcher" + str(os
.getpid())
2527 self
.db
, dbname
= dbref
.Open(conn_name
)
2529 self
.buffer = buffer
2532 self
.fetch_count
= fetch_count
2533 self
.fetching_done
= fetching_done
2534 self
.process_target
= process_target
2535 self
.wait_event
= wait_event
2536 self
.fetched_event
= fetched_event
2538 self
.query
= QSqlQuery(self
.db
)
2539 self
.query_limit
= 0 if "$$last_id$$" in sql
else 2
2543 self
.local_head
= self
.head
.value
2544 self
.local_tail
= self
.tail
.value
2547 if self
.query_limit
:
2548 if self
.query_limit
== 1:
2550 self
.query_limit
-= 1
2551 stmt
= self
.sql
.replace("$$last_id$$", str(self
.last_id
))
2552 QueryExec(self
.query
, stmt
)
2555 if not self
.query
.next():
2557 if not self
.query
.next():
2559 self
.last_id
= self
.query
.value(0)
2560 return self
.prep(self
.query
)
2562 def WaitForTarget(self
):
2564 self
.wait_event
.clear()
2565 target
= self
.process_target
.value
2566 if target
> self
.fetched
or target
< 0:
2568 self
.wait_event
.wait()
2571 def HasSpace(self
, sz
):
2572 if self
.local_tail
<= self
.local_head
:
2573 space
= len(self
.buffer) - self
.local_head
2576 if space
>= glb_nsz
:
2577 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
2578 nd
= pickle
.dumps(0, pickle
.HIGHEST_PROTOCOL
)
2579 self
.buffer[self
.local_head
: self
.local_head
+ len(nd
)] = nd
2581 if self
.local_tail
- self
.local_head
> sz
:
2585 def WaitForSpace(self
, sz
):
2586 if self
.HasSpace(sz
):
2589 self
.wait_event
.clear()
2590 self
.local_tail
= self
.tail
.value
2591 if self
.HasSpace(sz
):
2593 self
.wait_event
.wait()
2595 def AddToBuffer(self
, obj
):
2596 d
= pickle
.dumps(obj
, pickle
.HIGHEST_PROTOCOL
)
2598 nd
= pickle
.dumps(n
, pickle
.HIGHEST_PROTOCOL
)
2600 self
.WaitForSpace(sz
)
2601 pos
= self
.local_head
2602 self
.buffer[pos
: pos
+ len(nd
)] = nd
2603 self
.buffer[pos
+ glb_nsz
: pos
+ sz
] = d
2604 self
.local_head
+= sz
2606 def FetchBatch(self
, batch_size
):
2608 while batch_size
> fetched
:
2613 self
.AddToBuffer(obj
)
2616 self
.fetched
+= fetched
2617 with self
.fetch_count
.get_lock():
2618 self
.fetch_count
.value
+= fetched
2619 self
.head
.value
= self
.local_head
2620 self
.fetched_event
.set()
2624 target
= self
.WaitForTarget()
2627 batch_size
= min(glb_chunk_sz
, target
- self
.fetched
)
2628 self
.FetchBatch(batch_size
)
2629 self
.fetching_done
.value
= True
2630 self
.fetched_event
.set()
2632 def SQLFetcherFn(*x
):
2633 process
= SQLFetcherProcess(*x
)
2638 class SQLFetcher(QObject
):
2640 done
= Signal(object)
2642 def __init__(self
, glb
, sql
, prep
, process_data
, parent
=None):
2643 super(SQLFetcher
, self
).__init
__(parent
)
2644 self
.process_data
= process_data
2647 self
.last_target
= 0
2649 self
.buffer_size
= 16 * 1024 * 1024
2650 self
.buffer = Array(c_char
, self
.buffer_size
, lock
=False)
2651 self
.head
= Value(c_longlong
)
2652 self
.tail
= Value(c_longlong
)
2654 self
.fetch_count
= Value(c_longlong
)
2655 self
.fetching_done
= Value(c_bool
)
2657 self
.process_target
= Value(c_longlong
)
2658 self
.wait_event
= Event()
2659 self
.fetched_event
= Event()
2660 glb
.AddInstanceToShutdownOnExit(self
)
2661 self
.process
= Process(target
=SQLFetcherFn
, args
=(glb
.dbref
, sql
, self
.buffer, self
.head
, self
.tail
, self
.fetch_count
, self
.fetching_done
, self
.process_target
, self
.wait_event
, self
.fetched_event
, prep
))
2662 self
.process
.start()
2663 self
.thread
= Thread(self
.Thread
)
2664 self
.thread
.done
.connect(self
.ProcessData
, Qt
.QueuedConnection
)
2668 # Tell the thread and process to exit
2669 self
.process_target
.value
= -1
2670 self
.wait_event
.set()
2672 self
.fetching_done
.value
= True
2673 self
.fetched_event
.set()
2679 self
.fetched_event
.clear()
2680 fetch_count
= self
.fetch_count
.value
2681 if fetch_count
!= self
.last_count
:
2683 if self
.fetching_done
.value
:
2686 self
.fetched_event
.wait()
2687 count
= fetch_count
- self
.last_count
2688 self
.last_count
= fetch_count
2689 self
.fetched
+= count
2692 def Fetch(self
, nr
):
2694 # -1 inidcates there are no more
2696 result
= self
.fetched
2697 extra
= result
+ nr
- self
.target
2699 self
.target
+= extra
2700 # process_target < 0 indicates shutting down
2701 if self
.process_target
.value
>= 0:
2702 self
.process_target
.value
= self
.target
2703 self
.wait_event
.set()
2706 def RemoveFromBuffer(self
):
2707 pos
= self
.local_tail
2708 if len(self
.buffer) - pos
< glb_nsz
:
2710 n
= pickle
.loads(self
.buffer[pos
: pos
+ glb_nsz
])
2713 n
= pickle
.loads(self
.buffer[0 : glb_nsz
])
2715 obj
= pickle
.loads(self
.buffer[pos
: pos
+ n
])
2716 self
.local_tail
= pos
+ n
2719 def ProcessData(self
, count
):
2720 for i
in xrange(count
):
2721 obj
= self
.RemoveFromBuffer()
2722 self
.process_data(obj
)
2723 self
.tail
.value
= self
.local_tail
2724 self
.wait_event
.set()
2725 self
.done
.emit(count
)
2727 # Fetch more records bar
2729 class FetchMoreRecordsBar():
2731 def __init__(self
, model
, parent
):
2734 self
.label
= QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz
) + ") to fetch:")
2735 self
.label
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
2737 self
.fetch_count
= QSpinBox()
2738 self
.fetch_count
.setRange(1, 1000000)
2739 self
.fetch_count
.setValue(10)
2740 self
.fetch_count
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
2742 self
.fetch
= QPushButton("Go!")
2743 self
.fetch
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
2744 self
.fetch
.released
.connect(self
.FetchMoreRecords
)
2746 self
.progress
= QProgressBar()
2747 self
.progress
.setRange(0, 100)
2748 self
.progress
.hide()
2750 self
.done_label
= QLabel("All records fetched")
2751 self
.done_label
.hide()
2753 self
.spacer
= QLabel("")
2755 self
.close_button
= QToolButton()
2756 self
.close_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_DockWidgetCloseButton
))
2757 self
.close_button
.released
.connect(self
.Deactivate
)
2759 self
.hbox
= QHBoxLayout()
2760 self
.hbox
.setContentsMargins(0, 0, 0, 0)
2762 self
.hbox
.addWidget(self
.label
)
2763 self
.hbox
.addWidget(self
.fetch_count
)
2764 self
.hbox
.addWidget(self
.fetch
)
2765 self
.hbox
.addWidget(self
.spacer
)
2766 self
.hbox
.addWidget(self
.progress
)
2767 self
.hbox
.addWidget(self
.done_label
)
2768 self
.hbox
.addWidget(self
.close_button
)
2770 self
.bar
= QWidget()
2771 self
.bar
.setLayout(self
.hbox
)
2774 self
.in_progress
= False
2775 self
.model
.progress
.connect(self
.Progress
)
2779 if not model
.HasMoreRecords():
2787 self
.fetch
.setFocus()
2789 def Deactivate(self
):
2792 def Enable(self
, enable
):
2793 self
.fetch
.setEnabled(enable
)
2794 self
.fetch_count
.setEnabled(enable
)
2800 self
.progress
.show()
2803 self
.in_progress
= False
2805 self
.progress
.hide()
2810 return self
.fetch_count
.value() * glb_chunk_sz
2816 self
.fetch_count
.hide()
2819 self
.done_label
.show()
2821 def Progress(self
, count
):
2822 if self
.in_progress
:
2824 percent
= ((count
- self
.start
) * 100) / self
.Target()
2828 self
.progress
.setValue(percent
)
2830 # Count value of zero means no more records
2833 def FetchMoreRecords(self
):
2836 self
.progress
.setValue(0)
2838 self
.in_progress
= True
2839 self
.start
= self
.model
.FetchMoreRecords(self
.Target())
2841 # Brance data model level two item
2843 class BranchLevelTwoItem():
2845 def __init__(self
, row
, col
, text
, parent_item
):
2847 self
.parent_item
= parent_item
2848 self
.data
= [""] * (col
+ 1)
2849 self
.data
[col
] = text
2852 def getParentItem(self
):
2853 return self
.parent_item
2858 def childCount(self
):
2861 def hasChildren(self
):
2864 def getData(self
, column
):
2865 return self
.data
[column
]
2867 # Brance data model level one item
2869 class BranchLevelOneItem():
2871 def __init__(self
, glb
, row
, data
, parent_item
):
2874 self
.parent_item
= parent_item
2875 self
.child_count
= 0
2876 self
.child_items
= []
2877 self
.data
= data
[1:]
2880 self
.query_done
= False
2881 self
.br_col
= len(self
.data
) - 1
2883 def getChildItem(self
, row
):
2884 return self
.child_items
[row
]
2886 def getParentItem(self
):
2887 return self
.parent_item
2893 self
.query_done
= True
2895 if not self
.glb
.have_disassembler
:
2898 query
= QSqlQuery(self
.glb
.db
)
2900 QueryExec(query
, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
2902 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
2903 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
2904 " WHERE samples.id = " + str(self
.dbid
))
2905 if not query
.next():
2907 cpu
= query
.value(0)
2908 dso
= query
.value(1)
2909 sym
= query
.value(2)
2910 if dso
== 0 or sym
== 0:
2912 off
= query
.value(3)
2913 short_name
= query
.value(4)
2914 long_name
= query
.value(5)
2915 build_id
= query
.value(6)
2916 sym_start
= query
.value(7)
2919 QueryExec(query
, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
2921 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
2922 " WHERE samples.id > " + str(self
.dbid
) + " AND cpu = " + str(cpu
) +
2923 " ORDER BY samples.id"
2925 if not query
.next():
2927 if query
.value(0) != dso
:
2928 # Cannot disassemble from one dso to another
2930 bsym
= query
.value(1)
2931 boff
= query
.value(2)
2932 bsym_start
= query
.value(3)
2935 tot
= bsym_start
+ boff
+ 1 - sym_start
- off
2936 if tot
<= 0 or tot
> 16384:
2939 inst
= self
.glb
.disassembler
.Instruction()
2940 f
= self
.glb
.FileFromNamesAndBuildId(short_name
, long_name
, build_id
)
2943 mode
= 0 if Is64Bit(f
) else 1
2944 self
.glb
.disassembler
.SetMode(inst
, mode
)
2947 buf
= create_string_buffer(tot
+ 16)
2948 f
.seek(sym_start
+ off
)
2949 buf
.value
= f
.read(buf_sz
)
2950 buf_ptr
= addressof(buf
)
2953 cnt
, text
= self
.glb
.disassembler
.DisassembleOne(inst
, buf_ptr
, buf_sz
, ip
)
2955 byte_str
= tohex(ip
).rjust(16)
2956 for k
in xrange(cnt
):
2957 byte_str
+= " %02x" % ord(buf
[i
])
2962 self
.child_items
.append(BranchLevelTwoItem(0, self
.br_col
, byte_str
+ " " + text
, self
))
2963 self
.child_count
+= 1
2971 def childCount(self
):
2972 if not self
.query_done
:
2974 if not self
.child_count
:
2976 return self
.child_count
2978 def hasChildren(self
):
2979 if not self
.query_done
:
2981 return self
.child_count
> 0
2983 def getData(self
, column
):
2984 return self
.data
[column
]
2986 # Brance data model root item
2988 class BranchRootItem():
2991 self
.child_count
= 0
2992 self
.child_items
= []
2995 def getChildItem(self
, row
):
2996 return self
.child_items
[row
]
2998 def getParentItem(self
):
3004 def childCount(self
):
3005 return self
.child_count
3007 def hasChildren(self
):
3008 return self
.child_count
> 0
3010 def getData(self
, column
):
3013 # Calculate instructions per cycle
3015 def CalcIPC(cyc_cnt
, insn_cnt
):
3016 if cyc_cnt
and insn_cnt
:
3017 ipc
= Decimal(float(insn_cnt
) / cyc_cnt
)
3018 ipc
= str(ipc
.quantize(Decimal(".01"), rounding
=ROUND_HALF_UP
))
3023 # Branch data preparation
3025 def BranchDataPrepBr(query
, data
):
3026 data
.append(tohex(query
.value(8)).rjust(16) + " " + query
.value(9) + offstr(query
.value(10)) +
3027 " (" + dsoname(query
.value(11)) + ")" + " -> " +
3028 tohex(query
.value(12)) + " " + query
.value(13) + offstr(query
.value(14)) +
3029 " (" + dsoname(query
.value(15)) + ")")
3031 def BranchDataPrepIPC(query
, data
):
3032 insn_cnt
= query
.value(16)
3033 cyc_cnt
= query
.value(17)
3034 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
3035 data
.append(insn_cnt
)
3036 data
.append(cyc_cnt
)
3039 def BranchDataPrep(query
):
3041 for i
in xrange(0, 8):
3042 data
.append(query
.value(i
))
3043 BranchDataPrepBr(query
, data
)
3046 def BranchDataPrepWA(query
):
3048 data
.append(query
.value(0))
3049 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3050 data
.append("{:>19}".format(query
.value(1)))
3051 for i
in xrange(2, 8):
3052 data
.append(query
.value(i
))
3053 BranchDataPrepBr(query
, data
)
3056 def BranchDataWithIPCPrep(query
):
3058 for i
in xrange(0, 8):
3059 data
.append(query
.value(i
))
3060 BranchDataPrepIPC(query
, data
)
3061 BranchDataPrepBr(query
, data
)
3064 def BranchDataWithIPCPrepWA(query
):
3066 data
.append(query
.value(0))
3067 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3068 data
.append("{:>19}".format(query
.value(1)))
3069 for i
in xrange(2, 8):
3070 data
.append(query
.value(i
))
3071 BranchDataPrepIPC(query
, data
)
3072 BranchDataPrepBr(query
, data
)
3077 class BranchModel(TreeModel
):
3079 progress
= Signal(object)
3081 def __init__(self
, glb
, event_id
, where_clause
, parent
=None):
3082 super(BranchModel
, self
).__init
__(glb
, None, parent
)
3083 self
.event_id
= event_id
3086 self
.have_ipc
= IsSelectable(glb
.db
, "samples", columns
= "insn_count, cyc_count")
3088 select_ipc
= ", insn_count, cyc_count"
3089 prep_fn
= BranchDataWithIPCPrep
3090 prep_wa_fn
= BranchDataWithIPCPrepWA
3093 prep_fn
= BranchDataPrep
3094 prep_wa_fn
= BranchDataPrepWA
3095 sql
= ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
3096 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
3097 " ip, symbols.name, sym_offset, dsos.short_name,"
3098 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
3101 " INNER JOIN comms ON comm_id = comms.id"
3102 " INNER JOIN threads ON thread_id = threads.id"
3103 " INNER JOIN branch_types ON branch_type = branch_types.id"
3104 " INNER JOIN symbols ON symbol_id = symbols.id"
3105 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
3106 " INNER JOIN dsos ON samples.dso_id = dsos.id"
3107 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
3108 " WHERE samples.id > $$last_id$$" + where_clause
+
3109 " AND evsel_id = " + str(self
.event_id
) +
3110 " ORDER BY samples.id"
3111 " LIMIT " + str(glb_chunk_sz
))
3112 if pyside_version_1
and sys
.version_info
[0] == 3:
3116 self
.fetcher
= SQLFetcher(glb
, sql
, prep
, self
.AddSample
)
3117 self
.fetcher
.done
.connect(self
.Update
)
3118 self
.fetcher
.Fetch(glb_chunk_sz
)
3121 return BranchRootItem()
3123 def columnCount(self
, parent
=None):
3129 def columnHeader(self
, column
):
3131 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column
]
3133 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column
]
3135 def columnFont(self
, column
):
3140 if column
!= br_col
:
3142 return QFont("Monospace")
3144 def DisplayData(self
, item
, index
):
3146 self
.FetchIfNeeded(item
.row
)
3147 return item
.getData(index
.column())
3149 def AddSample(self
, data
):
3150 child
= BranchLevelOneItem(self
.glb
, self
.populated
, data
, self
.root
)
3151 self
.root
.child_items
.append(child
)
3154 def Update(self
, fetched
):
3157 self
.progress
.emit(0)
3158 child_count
= self
.root
.child_count
3159 count
= self
.populated
- child_count
3161 parent
= QModelIndex()
3162 self
.beginInsertRows(parent
, child_count
, child_count
+ count
- 1)
3163 self
.insertRows(child_count
, count
, parent
)
3164 self
.root
.child_count
+= count
3165 self
.endInsertRows()
3166 self
.progress
.emit(self
.root
.child_count
)
3168 def FetchMoreRecords(self
, count
):
3169 current
= self
.root
.child_count
3171 self
.fetcher
.Fetch(count
)
3173 self
.progress
.emit(0)
3176 def HasMoreRecords(self
):
3183 def __init__(self
, name
= "", where_clause
= "", limit
= ""):
3185 self
.where_clause
= where_clause
3189 return str(self
.where_clause
+ ";" + self
.limit
)
3193 class BranchWindow(QMdiSubWindow
):
3195 def __init__(self
, glb
, event_id
, report_vars
, parent
=None):
3196 super(BranchWindow
, self
).__init
__(parent
)
3198 model_name
= "Branch Events " + str(event_id
) + " " + report_vars
.UniqueId()
3200 self
.model
= LookupCreateModel(model_name
, lambda: BranchModel(glb
, event_id
, report_vars
.where_clause
))
3202 self
.view
= QTreeView()
3203 self
.view
.setUniformRowHeights(True)
3204 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
3205 self
.view
.CopyCellsToClipboard
= CopyTreeCellsToClipboard
3206 self
.view
.setModel(self
.model
)
3208 self
.ResizeColumnsToContents()
3210 self
.context_menu
= TreeContextMenu(self
.view
)
3212 self
.find_bar
= FindBar(self
, self
, True)
3214 self
.finder
= ChildDataItemFinder(self
.model
.root
)
3216 self
.fetch_bar
= FetchMoreRecordsBar(self
.model
, self
)
3218 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
3220 self
.setWidget(self
.vbox
.Widget())
3222 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, report_vars
.name
+ " Branch Events")
3224 def ResizeColumnToContents(self
, column
, n
):
3225 # Using the view's resizeColumnToContents() here is extrememly slow
3226 # so implement a crude alternative
3227 mm
= "MM" if column
else "MMMM"
3228 font
= self
.view
.font()
3229 metrics
= QFontMetrics(font
)
3231 for row
in xrange(n
):
3232 val
= self
.model
.root
.child_items
[row
].data
[column
]
3233 len = metrics
.width(str(val
) + mm
)
3234 max = len if len > max else max
3235 val
= self
.model
.columnHeader(column
)
3236 len = metrics
.width(str(val
) + mm
)
3237 max = len if len > max else max
3238 self
.view
.setColumnWidth(column
, max)
3240 def ResizeColumnsToContents(self
):
3241 n
= min(self
.model
.root
.child_count
, 100)
3243 # No data yet, so connect a signal to notify when there is
3244 self
.model
.rowsInserted
.connect(self
.UpdateColumnWidths
)
3246 columns
= self
.model
.columnCount()
3247 for i
in xrange(columns
):
3248 self
.ResizeColumnToContents(i
, n
)
3250 def UpdateColumnWidths(self
, *x
):
3251 # This only needs to be done once, so disconnect the signal now
3252 self
.model
.rowsInserted
.disconnect(self
.UpdateColumnWidths
)
3253 self
.ResizeColumnsToContents()
3255 def Find(self
, value
, direction
, pattern
, context
):
3256 self
.view
.setFocus()
3257 self
.find_bar
.Busy()
3258 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
3260 def FindDone(self
, row
):
3261 self
.find_bar
.Idle()
3263 self
.view
.setCurrentIndex(self
.model
.index(row
, 0, QModelIndex()))
3265 self
.find_bar
.NotFound()
3267 # Line edit data item
3269 class LineEditDataItem(object):
3271 def __init__(self
, glb
, label
, placeholder_text
, parent
, id = "", default
= ""):
3274 self
.placeholder_text
= placeholder_text
3275 self
.parent
= parent
3278 self
.value
= default
3280 self
.widget
= QLineEdit(default
)
3281 self
.widget
.editingFinished
.connect(self
.Validate
)
3282 self
.widget
.textChanged
.connect(self
.Invalidate
)
3285 self
.validated
= True
3287 if placeholder_text
:
3288 self
.widget
.setPlaceholderText(placeholder_text
)
3290 def TurnTextRed(self
):
3292 palette
= QPalette()
3293 palette
.setColor(QPalette
.Text
,Qt
.red
)
3294 self
.widget
.setPalette(palette
)
3297 def TurnTextNormal(self
):
3299 palette
= QPalette()
3300 self
.widget
.setPalette(palette
)
3303 def InvalidValue(self
, value
):
3306 self
.error
= self
.label
+ " invalid value '" + value
+ "'"
3307 self
.parent
.ShowMessage(self
.error
)
3309 def Invalidate(self
):
3310 self
.validated
= False
3312 def DoValidate(self
, input_string
):
3313 self
.value
= input_string
.strip()
3316 self
.validated
= True
3318 self
.TurnTextNormal()
3319 self
.parent
.ClearMessage()
3320 input_string
= self
.widget
.text()
3321 if not len(input_string
.strip()):
3324 self
.DoValidate(input_string
)
3327 if not self
.validated
:
3330 self
.parent
.ShowMessage(self
.error
)
3334 def IsNumber(self
, value
):
3339 return str(x
) == value
3341 # Non-negative integer ranges dialog data item
3343 class NonNegativeIntegerRangesDataItem(LineEditDataItem
):
3345 def __init__(self
, glb
, label
, placeholder_text
, column_name
, parent
):
3346 super(NonNegativeIntegerRangesDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
3348 self
.column_name
= column_name
3350 def DoValidate(self
, input_string
):
3353 for value
in [x
.strip() for x
in input_string
.split(",")]:
3355 vrange
= value
.split("-")
3356 if len(vrange
) != 2 or not self
.IsNumber(vrange
[0]) or not self
.IsNumber(vrange
[1]):
3357 return self
.InvalidValue(value
)
3358 ranges
.append(vrange
)
3360 if not self
.IsNumber(value
):
3361 return self
.InvalidValue(value
)
3362 singles
.append(value
)
3363 ranges
= [("(" + self
.column_name
+ " >= " + r
[0] + " AND " + self
.column_name
+ " <= " + r
[1] + ")") for r
in ranges
]
3365 ranges
.append(self
.column_name
+ " IN (" + ",".join(singles
) + ")")
3366 self
.value
= " OR ".join(ranges
)
3368 # Positive integer dialog data item
3370 class PositiveIntegerDataItem(LineEditDataItem
):
3372 def __init__(self
, glb
, label
, placeholder_text
, parent
, id = "", default
= ""):
3373 super(PositiveIntegerDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
, id, default
)
3375 def DoValidate(self
, input_string
):
3376 if not self
.IsNumber(input_string
.strip()):
3377 return self
.InvalidValue(input_string
)
3378 value
= int(input_string
.strip())
3380 return self
.InvalidValue(input_string
)
3381 self
.value
= str(value
)
3383 # Dialog data item converted and validated using a SQL table
3385 class SQLTableDataItem(LineEditDataItem
):
3387 def __init__(self
, glb
, label
, placeholder_text
, table_name
, match_column
, column_name1
, column_name2
, parent
):
3388 super(SQLTableDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
3390 self
.table_name
= table_name
3391 self
.match_column
= match_column
3392 self
.column_name1
= column_name1
3393 self
.column_name2
= column_name2
3395 def ValueToIds(self
, value
):
3397 query
= QSqlQuery(self
.glb
.db
)
3398 stmt
= "SELECT id FROM " + self
.table_name
+ " WHERE " + self
.match_column
+ " = '" + value
+ "'"
3399 ret
= query
.exec_(stmt
)
3402 ids
.append(str(query
.value(0)))
3405 def DoValidate(self
, input_string
):
3407 for value
in [x
.strip() for x
in input_string
.split(",")]:
3408 ids
= self
.ValueToIds(value
)
3412 return self
.InvalidValue(value
)
3413 self
.value
= self
.column_name1
+ " IN (" + ",".join(all_ids
) + ")"
3414 if self
.column_name2
:
3415 self
.value
= "( " + self
.value
+ " OR " + self
.column_name2
+ " IN (" + ",".join(all_ids
) + ") )"
3417 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
3419 class SampleTimeRangesDataItem(LineEditDataItem
):
3421 def __init__(self
, glb
, label
, placeholder_text
, column_name
, parent
):
3422 self
.column_name
= column_name
3426 self
.last_time
= 2 ** 64
3428 query
= QSqlQuery(glb
.db
)
3429 QueryExec(query
, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
3431 self
.last_id
= int(query
.value(0))
3432 self
.first_time
= int(glb
.HostStartTime())
3433 self
.last_time
= int(glb
.HostFinishTime())
3434 if placeholder_text
:
3435 placeholder_text
+= ", between " + str(self
.first_time
) + " and " + str(self
.last_time
)
3437 super(SampleTimeRangesDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
3439 def IdBetween(self
, query
, lower_id
, higher_id
, order
):
3440 QueryExec(query
, "SELECT id FROM samples WHERE id > " + str(lower_id
) + " AND id < " + str(higher_id
) + " ORDER BY id " + order
+ " LIMIT 1")
3442 return True, int(query
.value(0))
3446 def BinarySearchTime(self
, lower_id
, higher_id
, target_time
, get_floor
):
3447 query
= QSqlQuery(self
.glb
.db
)
3449 next_id
= int((lower_id
+ higher_id
) / 2)
3450 QueryExec(query
, "SELECT time FROM samples WHERE id = " + str(next_id
))
3451 if not query
.next():
3452 ok
, dbid
= self
.IdBetween(query
, lower_id
, next_id
, "DESC")
3454 ok
, dbid
= self
.IdBetween(query
, next_id
, higher_id
, "")
3456 return str(higher_id
)
3458 QueryExec(query
, "SELECT time FROM samples WHERE id = " + str(next_id
))
3459 next_time
= int(query
.value(0))
3461 if target_time
> next_time
:
3465 if higher_id
<= lower_id
+ 1:
3466 return str(higher_id
)
3468 if target_time
>= next_time
:
3472 if higher_id
<= lower_id
+ 1:
3473 return str(lower_id
)
3475 def ConvertRelativeTime(self
, val
):
3480 elif suffix
== "us":
3482 elif suffix
== "ns":
3486 val
= val
[:-2].strip()
3487 if not self
.IsNumber(val
):
3489 val
= int(val
) * mult
3491 val
+= self
.first_time
3493 val
+= self
.last_time
3496 def ConvertTimeRange(self
, vrange
):
3498 vrange
[0] = str(self
.first_time
)
3500 vrange
[1] = str(self
.last_time
)
3501 vrange
[0] = self
.ConvertRelativeTime(vrange
[0])
3502 vrange
[1] = self
.ConvertRelativeTime(vrange
[1])
3503 if not self
.IsNumber(vrange
[0]) or not self
.IsNumber(vrange
[1]):
3505 beg_range
= max(int(vrange
[0]), self
.first_time
)
3506 end_range
= min(int(vrange
[1]), self
.last_time
)
3507 if beg_range
> self
.last_time
or end_range
< self
.first_time
:
3509 vrange
[0] = self
.BinarySearchTime(0, self
.last_id
, beg_range
, True)
3510 vrange
[1] = self
.BinarySearchTime(1, self
.last_id
+ 1, end_range
, False)
3513 def AddTimeRange(self
, value
, ranges
):
3514 n
= value
.count("-")
3518 if value
.split("-")[1].strip() == "":
3524 pos
= findnth(value
, "-", n
)
3525 vrange
= [value
[:pos
].strip() ,value
[pos
+1:].strip()]
3526 if self
.ConvertTimeRange(vrange
):
3527 ranges
.append(vrange
)
3531 def DoValidate(self
, input_string
):
3533 for value
in [x
.strip() for x
in input_string
.split(",")]:
3534 if not self
.AddTimeRange(value
, ranges
):
3535 return self
.InvalidValue(value
)
3536 ranges
= [("(" + self
.column_name
+ " >= " + r
[0] + " AND " + self
.column_name
+ " <= " + r
[1] + ")") for r
in ranges
]
3537 self
.value
= " OR ".join(ranges
)
3539 # Report Dialog Base
3541 class ReportDialogBase(QDialog
):
3543 def __init__(self
, glb
, title
, items
, partial
, parent
=None):
3544 super(ReportDialogBase
, self
).__init
__(parent
)
3548 self
.report_vars
= ReportVars()
3550 self
.setWindowTitle(title
)
3551 self
.setMinimumWidth(600)
3553 self
.data_items
= [x(glb
, self
) for x
in items
]
3555 self
.partial
= partial
3557 self
.grid
= QGridLayout()
3559 for row
in xrange(len(self
.data_items
)):
3560 self
.grid
.addWidget(QLabel(self
.data_items
[row
].label
), row
, 0)
3561 self
.grid
.addWidget(self
.data_items
[row
].widget
, row
, 1)
3563 self
.status
= QLabel()
3565 self
.ok_button
= QPushButton("Ok", self
)
3566 self
.ok_button
.setDefault(True)
3567 self
.ok_button
.released
.connect(self
.Ok
)
3568 self
.ok_button
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
3570 self
.cancel_button
= QPushButton("Cancel", self
)
3571 self
.cancel_button
.released
.connect(self
.reject
)
3572 self
.cancel_button
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
3574 self
.hbox
= QHBoxLayout()
3575 #self.hbox.addStretch()
3576 self
.hbox
.addWidget(self
.status
)
3577 self
.hbox
.addWidget(self
.ok_button
)
3578 self
.hbox
.addWidget(self
.cancel_button
)
3580 self
.vbox
= QVBoxLayout()
3581 self
.vbox
.addLayout(self
.grid
)
3582 self
.vbox
.addLayout(self
.hbox
)
3584 self
.setLayout(self
.vbox
)
3587 vars = self
.report_vars
3588 for d
in self
.data_items
:
3589 if d
.id == "REPORTNAME":
3592 self
.ShowMessage("Report name is required")
3594 for d
in self
.data_items
:
3597 for d
in self
.data_items
[1:]:
3599 vars.limit
= d
.value
3601 if len(vars.where_clause
):
3602 vars.where_clause
+= " AND "
3603 vars.where_clause
+= d
.value
3604 if len(vars.where_clause
):
3606 vars.where_clause
= " AND ( " + vars.where_clause
+ " ) "
3608 vars.where_clause
= " WHERE " + vars.where_clause
+ " "
3611 def ShowMessage(self
, msg
):
3612 self
.status
.setText("<font color=#FF0000>" + msg
)
3614 def ClearMessage(self
):
3615 self
.status
.setText("")
3617 # Selected branch report creation dialog
3619 class SelectedBranchDialog(ReportDialogBase
):
3621 def __init__(self
, glb
, parent
=None):
3622 title
= "Selected Branches"
3623 items
= (lambda g
, p
: LineEditDataItem(g
, "Report name:", "Enter a name to appear in the window title bar", p
, "REPORTNAME"),
3624 lambda g
, p
: SampleTimeRangesDataItem(g
, "Time ranges:", "Enter time ranges", "samples.id", p
),
3625 lambda g
, p
: NonNegativeIntegerRangesDataItem(g
, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p
),
3626 lambda g
, p
: SQLTableDataItem(g
, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p
),
3627 lambda g
, p
: SQLTableDataItem(g
, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p
),
3628 lambda g
, p
: SQLTableDataItem(g
, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p
),
3629 lambda g
, p
: SQLTableDataItem(g
, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p
),
3630 lambda g
, p
: SQLTableDataItem(g
, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p
),
3631 lambda g
, p
: LineEditDataItem(g
, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p
))
3632 super(SelectedBranchDialog
, self
).__init
__(glb
, title
, items
, True, parent
)
3636 def GetEventList(db
):
3638 query
= QSqlQuery(db
)
3639 QueryExec(query
, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
3641 events
.append(query
.value(0))
3644 # Is a table selectable
3646 def IsSelectable(db
, table
, sql
= "", columns
= "*"):
3647 query
= QSqlQuery(db
)
3649 QueryExec(query
, "SELECT " + columns
+ " FROM " + table
+ " " + sql
+ " LIMIT 1")
3654 # SQL table data model item
3656 class SQLTableItem():
3658 def __init__(self
, row
, data
):
3662 def getData(self
, column
):
3663 return self
.data
[column
]
3665 # SQL table data model
3667 class SQLTableModel(TableModel
):
3669 progress
= Signal(object)
3671 def __init__(self
, glb
, sql
, column_headers
, parent
=None):
3672 super(SQLTableModel
, self
).__init
__(parent
)
3676 self
.column_headers
= column_headers
3677 self
.fetcher
= SQLFetcher(glb
, sql
, lambda x
, y
=len(column_headers
): self
.SQLTableDataPrep(x
, y
), self
.AddSample
)
3678 self
.fetcher
.done
.connect(self
.Update
)
3679 self
.fetcher
.Fetch(glb_chunk_sz
)
3681 def DisplayData(self
, item
, index
):
3682 self
.FetchIfNeeded(item
.row
)
3683 return item
.getData(index
.column())
3685 def AddSample(self
, data
):
3686 child
= SQLTableItem(self
.populated
, data
)
3687 self
.child_items
.append(child
)
3690 def Update(self
, fetched
):
3693 self
.progress
.emit(0)
3694 child_count
= self
.child_count
3695 count
= self
.populated
- child_count
3697 parent
= QModelIndex()
3698 self
.beginInsertRows(parent
, child_count
, child_count
+ count
- 1)
3699 self
.insertRows(child_count
, count
, parent
)
3700 self
.child_count
+= count
3701 self
.endInsertRows()
3702 self
.progress
.emit(self
.child_count
)
3704 def FetchMoreRecords(self
, count
):
3705 current
= self
.child_count
3707 self
.fetcher
.Fetch(count
)
3709 self
.progress
.emit(0)
3712 def HasMoreRecords(self
):
3715 def columnCount(self
, parent
=None):
3716 return len(self
.column_headers
)
3718 def columnHeader(self
, column
):
3719 return self
.column_headers
[column
]
3721 def SQLTableDataPrep(self
, query
, count
):
3723 for i
in xrange(count
):
3724 data
.append(query
.value(i
))
3727 # SQL automatic table data model
3729 class SQLAutoTableModel(SQLTableModel
):
3731 def __init__(self
, glb
, table_name
, parent
=None):
3732 sql
= "SELECT * FROM " + table_name
+ " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz
)
3733 if table_name
== "comm_threads_view":
3734 # For now, comm_threads_view has no id column
3735 sql
= "SELECT * FROM " + table_name
+ " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz
)
3737 query
= QSqlQuery(glb
.db
)
3738 if glb
.dbref
.is_sqlite3
:
3739 QueryExec(query
, "PRAGMA table_info(" + table_name
+ ")")
3741 column_headers
.append(query
.value(1))
3742 if table_name
== "sqlite_master":
3743 sql
= "SELECT * FROM " + table_name
3745 if table_name
[:19] == "information_schema.":
3746 sql
= "SELECT * FROM " + table_name
3747 select_table_name
= table_name
[19:]
3748 schema
= "information_schema"
3750 select_table_name
= table_name
3752 QueryExec(query
, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema
+ "' and table_name = '" + select_table_name
+ "'")
3754 column_headers
.append(query
.value(0))
3755 if pyside_version_1
and sys
.version_info
[0] == 3:
3756 if table_name
== "samples_view":
3757 self
.SQLTableDataPrep
= self
.samples_view_DataPrep
3758 if table_name
== "samples":
3759 self
.SQLTableDataPrep
= self
.samples_DataPrep
3760 super(SQLAutoTableModel
, self
).__init
__(glb
, sql
, column_headers
, parent
)
3762 def samples_view_DataPrep(self
, query
, count
):
3764 data
.append(query
.value(0))
3765 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3766 data
.append("{:>19}".format(query
.value(1)))
3767 for i
in xrange(2, count
):
3768 data
.append(query
.value(i
))
3771 def samples_DataPrep(self
, query
, count
):
3774 data
.append(query
.value(i
))
3775 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3776 data
.append("{:>19}".format(query
.value(9)))
3777 for i
in xrange(10, count
):
3778 data
.append(query
.value(i
))
3781 # Base class for custom ResizeColumnsToContents
3783 class ResizeColumnsToContentsBase(QObject
):
3785 def __init__(self
, parent
=None):
3786 super(ResizeColumnsToContentsBase
, self
).__init
__(parent
)
3788 def ResizeColumnToContents(self
, column
, n
):
3789 # Using the view's resizeColumnToContents() here is extrememly slow
3790 # so implement a crude alternative
3791 font
= self
.view
.font()
3792 metrics
= QFontMetrics(font
)
3794 for row
in xrange(n
):
3795 val
= self
.data_model
.child_items
[row
].data
[column
]
3796 len = metrics
.width(str(val
) + "MM")
3797 max = len if len > max else max
3798 val
= self
.data_model
.columnHeader(column
)
3799 len = metrics
.width(str(val
) + "MM")
3800 max = len if len > max else max
3801 self
.view
.setColumnWidth(column
, max)
3803 def ResizeColumnsToContents(self
):
3804 n
= min(self
.data_model
.child_count
, 100)
3806 # No data yet, so connect a signal to notify when there is
3807 self
.data_model
.rowsInserted
.connect(self
.UpdateColumnWidths
)
3809 columns
= self
.data_model
.columnCount()
3810 for i
in xrange(columns
):
3811 self
.ResizeColumnToContents(i
, n
)
3813 def UpdateColumnWidths(self
, *x
):
3814 # This only needs to be done once, so disconnect the signal now
3815 self
.data_model
.rowsInserted
.disconnect(self
.UpdateColumnWidths
)
3816 self
.ResizeColumnsToContents()
3818 # Convert value to CSV
3822 val
= val
.replace('"', '""')
3823 if "," in val
or '"' in val
:
3824 val
= '"' + val
+ '"'
3827 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
3831 def RowColumnKey(a
):
3832 return a
.row() * glb_max_cols
+ a
.column()
3834 # Copy selected table cells to clipboard
3836 def CopyTableCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
3837 indexes
= sorted(view
.selectedIndexes(), key
=RowColumnKey
)
3838 idx_cnt
= len(indexes
)
3843 min_row
= indexes
[0].row()
3844 max_row
= indexes
[0].row()
3845 min_col
= indexes
[0].column()
3846 max_col
= indexes
[0].column()
3848 min_row
= min(min_row
, i
.row())
3849 max_row
= max(max_row
, i
.row())
3850 min_col
= min(min_col
, i
.column())
3851 max_col
= max(max_col
, i
.column())
3852 if max_col
> glb_max_cols
:
3853 raise RuntimeError("glb_max_cols is too low")
3854 max_width
= [0] * (1 + max_col
- min_col
)
3856 c
= i
.column() - min_col
3857 max_width
[c
] = max(max_width
[c
], len(str(i
.data())))
3862 model
= indexes
[0].model()
3863 for col
in range(min_col
, max_col
+ 1):
3864 val
= model
.headerData(col
, Qt
.Horizontal
)
3866 text
+= sep
+ ToCSValue(val
)
3870 max_width
[c
] = max(max_width
[c
], len(val
))
3871 width
= max_width
[c
]
3872 align
= model
.headerData(col
, Qt
.Horizontal
, Qt
.TextAlignmentRole
)
3873 if align
& Qt
.AlignRight
:
3874 val
= val
.rjust(width
)
3875 text
+= pad
+ sep
+ val
3876 pad
= " " * (width
- len(val
))
3883 if i
.row() > last_row
:
3889 text
+= sep
+ ToCSValue(str(i
.data()))
3892 width
= max_width
[i
.column() - min_col
]
3893 if i
.data(Qt
.TextAlignmentRole
) & Qt
.AlignRight
:
3894 val
= str(i
.data()).rjust(width
)
3897 text
+= pad
+ sep
+ val
3898 pad
= " " * (width
- len(val
))
3900 QApplication
.clipboard().setText(text
)
3902 def CopyTreeCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
3903 indexes
= view
.selectedIndexes()
3904 if not len(indexes
):
3907 selection
= view
.selectionModel()
3911 above
= view
.indexAbove(i
)
3912 if not selection
.isSelected(above
):
3917 raise RuntimeError("CopyTreeCellsToClipboard internal error")
3919 model
= first
.model()
3921 col_cnt
= model
.columnCount(first
)
3922 max_width
= [0] * col_cnt
3925 indent_str
= " " * indent_sz
3927 expanded_mark_sz
= 2
3928 if sys
.version_info
[0] == 3:
3929 expanded_mark
= "\u25BC "
3930 not_expanded_mark
= "\u25B6 "
3932 expanded_mark
= unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
3933 not_expanded_mark
= unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
3941 for c
in range(col_cnt
):
3942 i
= pos
.sibling(row
, c
)
3944 n
= len(str(i
.data()))
3946 n
= len(str(i
.data()).strip())
3947 n
+= (i
.internalPointer().level
- 1) * indent_sz
3948 n
+= expanded_mark_sz
3949 max_width
[c
] = max(max_width
[c
], n
)
3950 pos
= view
.indexBelow(pos
)
3951 if not selection
.isSelected(pos
):
3958 for c
in range(col_cnt
):
3959 val
= model
.headerData(c
, Qt
.Horizontal
, Qt
.DisplayRole
).strip()
3961 text
+= sep
+ ToCSValue(val
)
3964 max_width
[c
] = max(max_width
[c
], len(val
))
3965 width
= max_width
[c
]
3966 align
= model
.headerData(c
, Qt
.Horizontal
, Qt
.TextAlignmentRole
)
3967 if align
& Qt
.AlignRight
:
3968 val
= val
.rjust(width
)
3969 text
+= pad
+ sep
+ val
3970 pad
= " " * (width
- len(val
))
3979 for c
in range(col_cnt
):
3980 i
= pos
.sibling(row
, c
)
3983 if model
.hasChildren(i
):
3984 if view
.isExpanded(i
):
3985 mark
= expanded_mark
3987 mark
= not_expanded_mark
3990 val
= indent_str
* (i
.internalPointer().level
- 1) + mark
+ val
.strip()
3992 text
+= sep
+ ToCSValue(val
)
3995 width
= max_width
[c
]
3996 if c
and i
.data(Qt
.TextAlignmentRole
) & Qt
.AlignRight
:
3997 val
= val
.rjust(width
)
3998 text
+= pad
+ sep
+ val
3999 pad
= " " * (width
- len(val
))
4001 pos
= view
.indexBelow(pos
)
4002 if not selection
.isSelected(pos
):
4004 text
= text
.rstrip() + "\n"
4008 QApplication
.clipboard().setText(text
)
4010 def CopyCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
4011 view
.CopyCellsToClipboard(view
, as_csv
, with_hdr
)
4013 def CopyCellsToClipboardHdr(view
):
4014 CopyCellsToClipboard(view
, False, True)
4016 def CopyCellsToClipboardCSV(view
):
4017 CopyCellsToClipboard(view
, True, True)
4021 class ContextMenu(object):
4023 def __init__(self
, view
):
4025 self
.view
.setContextMenuPolicy(Qt
.CustomContextMenu
)
4026 self
.view
.customContextMenuRequested
.connect(self
.ShowContextMenu
)
4028 def ShowContextMenu(self
, pos
):
4029 menu
= QMenu(self
.view
)
4030 self
.AddActions(menu
)
4031 menu
.exec_(self
.view
.mapToGlobal(pos
))
4033 def AddCopy(self
, menu
):
4034 menu
.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self
.view
), self
.view
))
4035 menu
.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self
.view
), self
.view
))
4037 def AddActions(self
, menu
):
4040 class TreeContextMenu(ContextMenu
):
4042 def __init__(self
, view
):
4043 super(TreeContextMenu
, self
).__init
__(view
)
4045 def AddActions(self
, menu
):
4046 i
= self
.view
.currentIndex()
4047 text
= str(i
.data()).strip()
4049 menu
.addAction(CreateAction('Copy "' + text
+ '"', "Copy to clipboard", lambda: QApplication
.clipboard().setText(text
), self
.view
))
4054 class TableWindow(QMdiSubWindow
, ResizeColumnsToContentsBase
):
4056 def __init__(self
, glb
, table_name
, parent
=None):
4057 super(TableWindow
, self
).__init
__(parent
)
4059 self
.data_model
= LookupCreateModel(table_name
+ " Table", lambda: SQLAutoTableModel(glb
, table_name
))
4061 self
.model
= QSortFilterProxyModel()
4062 self
.model
.setSourceModel(self
.data_model
)
4064 self
.view
= QTableView()
4065 self
.view
.setModel(self
.model
)
4066 self
.view
.setEditTriggers(QAbstractItemView
.NoEditTriggers
)
4067 self
.view
.verticalHeader().setVisible(False)
4068 self
.view
.sortByColumn(-1, Qt
.AscendingOrder
)
4069 self
.view
.setSortingEnabled(True)
4070 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
4071 self
.view
.CopyCellsToClipboard
= CopyTableCellsToClipboard
4073 self
.ResizeColumnsToContents()
4075 self
.context_menu
= ContextMenu(self
.view
)
4077 self
.find_bar
= FindBar(self
, self
, True)
4079 self
.finder
= ChildDataItemFinder(self
.data_model
)
4081 self
.fetch_bar
= FetchMoreRecordsBar(self
.data_model
, self
)
4083 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
4085 self
.setWidget(self
.vbox
.Widget())
4087 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, table_name
+ " Table")
4089 def Find(self
, value
, direction
, pattern
, context
):
4090 self
.view
.setFocus()
4091 self
.find_bar
.Busy()
4092 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
4094 def FindDone(self
, row
):
4095 self
.find_bar
.Idle()
4097 self
.view
.setCurrentIndex(self
.model
.mapFromSource(self
.data_model
.index(row
, 0, QModelIndex())))
4099 self
.find_bar
.NotFound()
4103 def GetTableList(glb
):
4105 query
= QSqlQuery(glb
.db
)
4106 if glb
.dbref
.is_sqlite3
:
4107 QueryExec(query
, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
4109 QueryExec(query
, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
4111 tables
.append(query
.value(0))
4112 if glb
.dbref
.is_sqlite3
:
4113 tables
.append("sqlite_master")
4115 tables
.append("information_schema.tables")
4116 tables
.append("information_schema.views")
4117 tables
.append("information_schema.columns")
4120 # Top Calls data model
4122 class TopCallsModel(SQLTableModel
):
4124 def __init__(self
, glb
, report_vars
, parent
=None):
4126 if not glb
.dbref
.is_sqlite3
:
4129 if len(report_vars
.limit
):
4130 limit
= " LIMIT " + report_vars
.limit
4131 sql
= ("SELECT comm, pid, tid, name,"
4133 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text
+
4136 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
4138 " WHEN (calls.flags = 1) THEN 'no call'" + text
+
4139 " WHEN (calls.flags = 2) THEN 'no return'" + text
+
4140 " WHEN (calls.flags = 3) THEN 'no call/return'" + text
+
4144 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
4145 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
4146 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
4147 " INNER JOIN comms ON calls.comm_id = comms.id"
4148 " INNER JOIN threads ON calls.thread_id = threads.id" +
4149 report_vars
.where_clause
+
4150 " ORDER BY elapsed_time DESC" +
4153 column_headers
= ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
4154 self
.alignment
= (Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignLeft
)
4155 super(TopCallsModel
, self
).__init
__(glb
, sql
, column_headers
, parent
)
4157 def columnAlignment(self
, column
):
4158 return self
.alignment
[column
]
4160 # Top Calls report creation dialog
4162 class TopCallsDialog(ReportDialogBase
):
4164 def __init__(self
, glb
, parent
=None):
4165 title
= "Top Calls by Elapsed Time"
4166 items
= (lambda g
, p
: LineEditDataItem(g
, "Report name:", "Enter a name to appear in the window title bar", p
, "REPORTNAME"),
4167 lambda g
, p
: SQLTableDataItem(g
, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p
),
4168 lambda g
, p
: SQLTableDataItem(g
, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p
),
4169 lambda g
, p
: SQLTableDataItem(g
, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p
),
4170 lambda g
, p
: SQLTableDataItem(g
, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p
),
4171 lambda g
, p
: SQLTableDataItem(g
, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p
),
4172 lambda g
, p
: LineEditDataItem(g
, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p
),
4173 lambda g
, p
: PositiveIntegerDataItem(g
, "Record limit:", "Limit selection to this number of records", p
, "LIMIT", "100"))
4174 super(TopCallsDialog
, self
).__init
__(glb
, title
, items
, False, parent
)
4178 class TopCallsWindow(QMdiSubWindow
, ResizeColumnsToContentsBase
):
4180 def __init__(self
, glb
, report_vars
, parent
=None):
4181 super(TopCallsWindow
, self
).__init
__(parent
)
4183 self
.data_model
= LookupCreateModel("Top Calls " + report_vars
.UniqueId(), lambda: TopCallsModel(glb
, report_vars
))
4184 self
.model
= self
.data_model
4186 self
.view
= QTableView()
4187 self
.view
.setModel(self
.model
)
4188 self
.view
.setEditTriggers(QAbstractItemView
.NoEditTriggers
)
4189 self
.view
.verticalHeader().setVisible(False)
4190 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
4191 self
.view
.CopyCellsToClipboard
= CopyTableCellsToClipboard
4193 self
.context_menu
= ContextMenu(self
.view
)
4195 self
.ResizeColumnsToContents()
4197 self
.find_bar
= FindBar(self
, self
, True)
4199 self
.finder
= ChildDataItemFinder(self
.model
)
4201 self
.fetch_bar
= FetchMoreRecordsBar(self
.data_model
, self
)
4203 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
4205 self
.setWidget(self
.vbox
.Widget())
4207 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, report_vars
.name
)
4209 def Find(self
, value
, direction
, pattern
, context
):
4210 self
.view
.setFocus()
4211 self
.find_bar
.Busy()
4212 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
4214 def FindDone(self
, row
):
4215 self
.find_bar
.Idle()
4217 self
.view
.setCurrentIndex(self
.model
.index(row
, 0, QModelIndex()))
4219 self
.find_bar
.NotFound()
4223 def CreateAction(label
, tip
, callback
, parent
=None, shortcut
=None):
4224 action
= QAction(label
, parent
)
4225 if shortcut
!= None:
4226 action
.setShortcuts(shortcut
)
4227 action
.setStatusTip(tip
)
4228 action
.triggered
.connect(callback
)
4231 # Typical application actions
4233 def CreateExitAction(app
, parent
=None):
4234 return CreateAction("&Quit", "Exit the application", app
.closeAllWindows
, parent
, QKeySequence
.Quit
)
4236 # Typical MDI actions
4238 def CreateCloseActiveWindowAction(mdi_area
):
4239 return CreateAction("Cl&ose", "Close the active window", mdi_area
.closeActiveSubWindow
, mdi_area
)
4241 def CreateCloseAllWindowsAction(mdi_area
):
4242 return CreateAction("Close &All", "Close all the windows", mdi_area
.closeAllSubWindows
, mdi_area
)
4244 def CreateTileWindowsAction(mdi_area
):
4245 return CreateAction("&Tile", "Tile the windows", mdi_area
.tileSubWindows
, mdi_area
)
4247 def CreateCascadeWindowsAction(mdi_area
):
4248 return CreateAction("&Cascade", "Cascade the windows", mdi_area
.cascadeSubWindows
, mdi_area
)
4250 def CreateNextWindowAction(mdi_area
):
4251 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area
.activateNextSubWindow
, mdi_area
, QKeySequence
.NextChild
)
4253 def CreatePreviousWindowAction(mdi_area
):
4254 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area
.activatePreviousSubWindow
, mdi_area
, QKeySequence
.PreviousChild
)
4256 # Typical MDI window menu
4260 def __init__(self
, mdi_area
, menu
):
4261 self
.mdi_area
= mdi_area
4262 self
.window_menu
= menu
.addMenu("&Windows")
4263 self
.close_active_window
= CreateCloseActiveWindowAction(mdi_area
)
4264 self
.close_all_windows
= CreateCloseAllWindowsAction(mdi_area
)
4265 self
.tile_windows
= CreateTileWindowsAction(mdi_area
)
4266 self
.cascade_windows
= CreateCascadeWindowsAction(mdi_area
)
4267 self
.next_window
= CreateNextWindowAction(mdi_area
)
4268 self
.previous_window
= CreatePreviousWindowAction(mdi_area
)
4269 self
.window_menu
.aboutToShow
.connect(self
.Update
)
4272 self
.window_menu
.clear()
4273 sub_window_count
= len(self
.mdi_area
.subWindowList())
4274 have_sub_windows
= sub_window_count
!= 0
4275 self
.close_active_window
.setEnabled(have_sub_windows
)
4276 self
.close_all_windows
.setEnabled(have_sub_windows
)
4277 self
.tile_windows
.setEnabled(have_sub_windows
)
4278 self
.cascade_windows
.setEnabled(have_sub_windows
)
4279 self
.next_window
.setEnabled(have_sub_windows
)
4280 self
.previous_window
.setEnabled(have_sub_windows
)
4281 self
.window_menu
.addAction(self
.close_active_window
)
4282 self
.window_menu
.addAction(self
.close_all_windows
)
4283 self
.window_menu
.addSeparator()
4284 self
.window_menu
.addAction(self
.tile_windows
)
4285 self
.window_menu
.addAction(self
.cascade_windows
)
4286 self
.window_menu
.addSeparator()
4287 self
.window_menu
.addAction(self
.next_window
)
4288 self
.window_menu
.addAction(self
.previous_window
)
4289 if sub_window_count
== 0:
4291 self
.window_menu
.addSeparator()
4293 for sub_window
in self
.mdi_area
.subWindowList():
4294 label
= str(nr
) + " " + sub_window
.name
4297 action
= self
.window_menu
.addAction(label
)
4298 action
.setCheckable(True)
4299 action
.setChecked(sub_window
== self
.mdi_area
.activeSubWindow())
4300 action
.triggered
.connect(lambda a
=None,x
=nr
: self
.setActiveSubWindow(x
))
4301 self
.window_menu
.addAction(action
)
4304 def setActiveSubWindow(self
, nr
):
4305 self
.mdi_area
.setActiveSubWindow(self
.mdi_area
.subWindowList()[nr
- 1])
4320 <p class=c1><a href=#reports>1. Reports</a></p>
4321 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
4322 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
4323 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
4324 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
4325 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
4326 <p class=c1><a href=#charts>2. Charts</a></p>
4327 <p class=c2><a href=#timechartbycpu>2.1 Time chart by CPU</a></p>
4328 <p class=c1><a href=#tables>3. Tables</a></p>
4329 <h1 id=reports>1. Reports</h1>
4330 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
4331 The result is a GUI window with a tree representing a context-sensitive
4332 call-graph. Expanding a couple of levels of the tree and adjusting column
4333 widths to suit will display something like:
4335 Call Graph: pt_example
4336 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
4339 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
4340 |- unknown unknown 1 13198 0.1 1 0.0
4341 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
4342 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
4343 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
4344 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
4345 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
4346 >- __libc_csu_init ls 1 10354 0.1 10 0.0
4347 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
4348 v- main ls 1 8182043 99.6 180254 99.9
4350 <h3>Points to note:</h3>
4352 <li>The top level is a command name (comm)</li>
4353 <li>The next level is a thread (pid:tid)</li>
4354 <li>Subsequent levels are functions</li>
4355 <li>'Count' is the number of calls</li>
4356 <li>'Time' is the elapsed time until the function returns</li>
4357 <li>Percentages are relative to the level above</li>
4358 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
4361 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
4362 The pattern matching symbols are ? for any character and * for zero or more characters.
4363 <h2 id=calltree>1.2 Call Tree</h2>
4364 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
4365 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
4366 <h2 id=allbranches>1.3 All branches</h2>
4367 The All branches report displays all branches in chronological order.
4368 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
4369 <h3>Disassembly</h3>
4370 Open a branch to display disassembly. This only works if:
4372 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
4373 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
4374 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
4375 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
4376 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
4378 <h4 id=xed>Intel XED Setup</h4>
4379 To use Intel XED, libxed.so must be present. To build and install libxed.so:
4381 git clone https://github.com/intelxed/mbuild.git mbuild
4382 git clone https://github.com/intelxed/xed
4385 sudo ./mfile.py --prefix=/usr/local install
4388 <h3>Instructions per Cycle (IPC)</h3>
4389 If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
4390 <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
4391 Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
4392 In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
4393 since the previous displayed 'IPC'.
4395 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
4396 Refer to Python documentation for the regular expression syntax.
4397 All columns are searched, but only currently fetched rows are searched.
4398 <h2 id=selectedbranches>1.4 Selected branches</h2>
4399 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
4400 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
4401 <h3>1.4.1 Time ranges</h3>
4402 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
4403 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
4405 81073085947329-81073085958238 From 81073085947329 to 81073085958238
4406 100us-200us From 100us to 200us
4407 10ms- From 10ms to the end
4408 -100ns The first 100ns
4409 -10ms- The last 10ms
4411 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
4412 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
4413 The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
4414 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
4415 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
4416 <h1 id=charts>2. Charts</h1>
4417 <h2 id=timechartbycpu>2.1 Time chart by CPU</h2>
4418 This chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu.
4421 <li>Mouse over to highight the task and show the time</li>
4422 <li>Drag the mouse to select a region and zoom by pushing the Zoom button</li>
4423 <li>Go back and forward by pressing the arrow buttons</li>
4424 <li>If call information is available, right-click to show a call tree opened to that task and time.
4425 Note, the call tree may take some time to appear, and there may not be call information for the task or time selected.
4429 The graph can be misleading in the following respects:
4431 <li>The graph shows the first task on each CPU as running from the beginning of the time range.
4432 Because tracing might start on different CPUs at different times, that is not necessarily the case.
4433 Refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4434 <li>Similarly, the last task on each CPU can be showing running longer than it really was.
4435 Again, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4436 <li>When the mouse is over a task, the highlighted task might not be visible on the legend without scrolling if the legend does not fit fully in the window</li>
4438 <h1 id=tables>3. Tables</h1>
4439 The Tables menu shows all tables and views in the database. Most tables have an associated view
4440 which displays the information in a more friendly way. Not all data for large tables is fetched
4441 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
4442 but that can be slow for large tables.
4443 <p>There are also tables of database meta-information.
4444 For SQLite3 databases, the sqlite_master table is included.
4445 For PostgreSQL databases, information_schema.tables/views/columns are included.
4447 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
4448 Refer to Python documentation for the regular expression syntax.
4449 All columns are searched, but only currently fetched rows are searched.
4450 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
4451 will go to the next/previous result in id order, instead of display order.
4456 class HelpWindow(QMdiSubWindow
):
4458 def __init__(self
, glb
, parent
=None):
4459 super(HelpWindow
, self
).__init
__(parent
)
4461 self
.text
= QTextBrowser()
4462 self
.text
.setHtml(glb_help_text
)
4463 self
.text
.setReadOnly(True)
4464 self
.text
.setOpenExternalLinks(True)
4466 self
.setWidget(self
.text
)
4468 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Exported SQL Viewer Help")
4470 # Main window that only displays the help text
4472 class HelpOnlyWindow(QMainWindow
):
4474 def __init__(self
, parent
=None):
4475 super(HelpOnlyWindow
, self
).__init
__(parent
)
4477 self
.setMinimumSize(200, 100)
4478 self
.resize(800, 600)
4479 self
.setWindowTitle("Exported SQL Viewer Help")
4480 self
.setWindowIcon(self
.style().standardIcon(QStyle
.SP_MessageBoxInformation
))
4482 self
.text
= QTextBrowser()
4483 self
.text
.setHtml(glb_help_text
)
4484 self
.text
.setReadOnly(True)
4485 self
.text
.setOpenExternalLinks(True)
4487 self
.setCentralWidget(self
.text
)
4489 # PostqreSQL server version
4491 def PostqreSQLServerVersion(db
):
4492 query
= QSqlQuery(db
)
4493 QueryExec(query
, "SELECT VERSION()")
4495 v_str
= query
.value(0)
4496 v_list
= v_str
.strip().split(" ")
4497 if v_list
[0] == "PostgreSQL" and v_list
[2] == "on":
4504 def SQLiteVersion(db
):
4505 query
= QSqlQuery(db
)
4506 QueryExec(query
, "SELECT sqlite_version()")
4508 return query
.value(0)
4513 class AboutDialog(QDialog
):
4515 def __init__(self
, glb
, parent
=None):
4516 super(AboutDialog
, self
).__init
__(parent
)
4518 self
.setWindowTitle("About Exported SQL Viewer")
4519 self
.setMinimumWidth(300)
4521 pyside_version
= "1" if pyside_version_1
else "2"
4524 text
+= "Python version: " + sys
.version
.split(" ")[0] + "\n"
4525 text
+= "PySide version: " + pyside_version
+ "\n"
4526 text
+= "Qt version: " + qVersion() + "\n"
4527 if glb
.dbref
.is_sqlite3
:
4528 text
+= "SQLite version: " + SQLiteVersion(glb
.db
) + "\n"
4530 text
+= "PostqreSQL version: " + PostqreSQLServerVersion(glb
.db
) + "\n"
4533 self
.text
= QTextBrowser()
4534 self
.text
.setHtml(text
)
4535 self
.text
.setReadOnly(True)
4536 self
.text
.setOpenExternalLinks(True)
4538 self
.vbox
= QVBoxLayout()
4539 self
.vbox
.addWidget(self
.text
)
4541 self
.setLayout(self
.vbox
)
4545 def ResizeFont(widget
, diff
):
4546 font
= widget
.font()
4547 sz
= font
.pointSize()
4548 font
.setPointSize(sz
+ diff
)
4549 widget
.setFont(font
)
4551 def ShrinkFont(widget
):
4552 ResizeFont(widget
, -1)
4554 def EnlargeFont(widget
):
4555 ResizeFont(widget
, 1)
4557 # Unique name for sub-windows
4559 def NumberedWindowName(name
, nr
):
4561 name
+= " <" + str(nr
) + ">"
4564 def UniqueSubWindowName(mdi_area
, name
):
4567 unique_name
= NumberedWindowName(name
, nr
)
4569 for sub_window
in mdi_area
.subWindowList():
4570 if sub_window
.name
== unique_name
:
4579 def AddSubWindow(mdi_area
, sub_window
, name
):
4580 unique_name
= UniqueSubWindowName(mdi_area
, name
)
4581 sub_window
.setMinimumSize(200, 100)
4582 sub_window
.resize(800, 600)
4583 sub_window
.setWindowTitle(unique_name
)
4584 sub_window
.setAttribute(Qt
.WA_DeleteOnClose
)
4585 sub_window
.setWindowIcon(sub_window
.style().standardIcon(QStyle
.SP_FileIcon
))
4586 sub_window
.name
= unique_name
4587 mdi_area
.addSubWindow(sub_window
)
4592 class MainWindow(QMainWindow
):
4594 def __init__(self
, glb
, parent
=None):
4595 super(MainWindow
, self
).__init
__(parent
)
4599 self
.setWindowTitle("Exported SQL Viewer: " + glb
.dbname
)
4600 self
.setWindowIcon(self
.style().standardIcon(QStyle
.SP_ComputerIcon
))
4601 self
.setMinimumSize(200, 100)
4603 self
.mdi_area
= QMdiArea()
4604 self
.mdi_area
.setHorizontalScrollBarPolicy(Qt
.ScrollBarAsNeeded
)
4605 self
.mdi_area
.setVerticalScrollBarPolicy(Qt
.ScrollBarAsNeeded
)
4607 self
.setCentralWidget(self
.mdi_area
)
4609 menu
= self
.menuBar()
4611 file_menu
= menu
.addMenu("&File")
4612 file_menu
.addAction(CreateExitAction(glb
.app
, self
))
4614 edit_menu
= menu
.addMenu("&Edit")
4615 edit_menu
.addAction(CreateAction("&Copy", "Copy to clipboard", self
.CopyToClipboard
, self
, QKeySequence
.Copy
))
4616 edit_menu
.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self
.CopyToClipboardCSV
, self
))
4617 edit_menu
.addAction(CreateAction("&Find...", "Find items", self
.Find
, self
, QKeySequence
.Find
))
4618 edit_menu
.addAction(CreateAction("Fetch &more records...", "Fetch more records", self
.FetchMoreRecords
, self
, [QKeySequence(Qt
.Key_F8
)]))
4619 edit_menu
.addAction(CreateAction("&Shrink Font", "Make text smaller", self
.ShrinkFont
, self
, [QKeySequence("Ctrl+-")]))
4620 edit_menu
.addAction(CreateAction("&Enlarge Font", "Make text bigger", self
.EnlargeFont
, self
, [QKeySequence("Ctrl++")]))
4622 reports_menu
= menu
.addMenu("&Reports")
4623 if IsSelectable(glb
.db
, "calls"):
4624 reports_menu
.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self
.NewCallGraph
, self
))
4626 if IsSelectable(glb
.db
, "calls", "WHERE parent_id >= 0"):
4627 reports_menu
.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self
.NewCallTree
, self
))
4629 self
.EventMenu(GetEventList(glb
.db
), reports_menu
)
4631 if IsSelectable(glb
.db
, "calls"):
4632 reports_menu
.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self
.NewTopCalls
, self
))
4634 if IsSelectable(glb
.db
, "context_switches"):
4635 charts_menu
= menu
.addMenu("&Charts")
4636 charts_menu
.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self
.TimeChartByCPU
, self
))
4638 self
.TableMenu(GetTableList(glb
), menu
)
4640 self
.window_menu
= WindowMenu(self
.mdi_area
, menu
)
4642 help_menu
= menu
.addMenu("&Help")
4643 help_menu
.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self
.Help
, self
, QKeySequence
.HelpContents
))
4644 help_menu
.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self
.About
, self
))
4647 win
= self
.mdi_area
.activeSubWindow()
4654 def CopyToClipboard(self
):
4655 self
.Try(CopyCellsToClipboardHdr
)
4657 def CopyToClipboardCSV(self
):
4658 self
.Try(CopyCellsToClipboardCSV
)
4661 win
= self
.mdi_area
.activeSubWindow()
4664 win
.find_bar
.Activate()
4668 def FetchMoreRecords(self
):
4669 win
= self
.mdi_area
.activeSubWindow()
4672 win
.fetch_bar
.Activate()
4676 def ShrinkFont(self
):
4677 self
.Try(ShrinkFont
)
4679 def EnlargeFont(self
):
4680 self
.Try(EnlargeFont
)
4682 def EventMenu(self
, events
, reports_menu
):
4684 for event
in events
:
4685 event
= event
.split(":")[0]
4686 if event
== "branches":
4687 branches_events
+= 1
4689 for event
in events
:
4691 event
= event
.split(":")[0]
4692 if event
== "branches":
4693 label
= "All branches" if branches_events
== 1 else "All branches " + "(id=" + dbid
+ ")"
4694 reports_menu
.addAction(CreateAction(label
, "Create a new window displaying branch events", lambda a
=None,x
=dbid
: self
.NewBranchView(x
), self
))
4695 label
= "Selected branches" if branches_events
== 1 else "Selected branches " + "(id=" + dbid
+ ")"
4696 reports_menu
.addAction(CreateAction(label
, "Create a new window displaying branch events", lambda a
=None,x
=dbid
: self
.NewSelectedBranchView(x
), self
))
4698 def TimeChartByCPU(self
):
4699 TimeChartByCPUWindow(self
.glb
, self
)
4701 def TableMenu(self
, tables
, menu
):
4702 table_menu
= menu
.addMenu("&Tables")
4703 for table
in tables
:
4704 table_menu
.addAction(CreateAction(table
, "Create a new window containing a table view", lambda a
=None,t
=table
: self
.NewTableView(t
), self
))
4706 def NewCallGraph(self
):
4707 CallGraphWindow(self
.glb
, self
)
4709 def NewCallTree(self
):
4710 CallTreeWindow(self
.glb
, self
)
4712 def NewTopCalls(self
):
4713 dialog
= TopCallsDialog(self
.glb
, self
)
4714 ret
= dialog
.exec_()
4716 TopCallsWindow(self
.glb
, dialog
.report_vars
, self
)
4718 def NewBranchView(self
, event_id
):
4719 BranchWindow(self
.glb
, event_id
, ReportVars(), self
)
4721 def NewSelectedBranchView(self
, event_id
):
4722 dialog
= SelectedBranchDialog(self
.glb
, self
)
4723 ret
= dialog
.exec_()
4725 BranchWindow(self
.glb
, event_id
, dialog
.report_vars
, self
)
4727 def NewTableView(self
, table_name
):
4728 TableWindow(self
.glb
, table_name
, self
)
4731 HelpWindow(self
.glb
, self
)
4734 dialog
= AboutDialog(self
.glb
, self
)
4739 class xed_state_t(Structure
):
4746 class XEDInstruction():
4748 def __init__(self
, libxed
):
4749 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
4750 xedd_t
= c_byte
* 512
4751 self
.xedd
= xedd_t()
4752 self
.xedp
= addressof(self
.xedd
)
4753 libxed
.xed_decoded_inst_zero(self
.xedp
)
4754 self
.state
= xed_state_t()
4755 self
.statep
= addressof(self
.state
)
4756 # Buffer for disassembled instruction text
4757 self
.buffer = create_string_buffer(256)
4758 self
.bufferp
= addressof(self
.buffer)
4764 self
.libxed
= CDLL("libxed.so")
4768 self
.libxed
= CDLL("/usr/local/lib/libxed.so")
4770 self
.xed_tables_init
= self
.libxed
.xed_tables_init
4771 self
.xed_tables_init
.restype
= None
4772 self
.xed_tables_init
.argtypes
= []
4774 self
.xed_decoded_inst_zero
= self
.libxed
.xed_decoded_inst_zero
4775 self
.xed_decoded_inst_zero
.restype
= None
4776 self
.xed_decoded_inst_zero
.argtypes
= [ c_void_p
]
4778 self
.xed_operand_values_set_mode
= self
.libxed
.xed_operand_values_set_mode
4779 self
.xed_operand_values_set_mode
.restype
= None
4780 self
.xed_operand_values_set_mode
.argtypes
= [ c_void_p
, c_void_p
]
4782 self
.xed_decoded_inst_zero_keep_mode
= self
.libxed
.xed_decoded_inst_zero_keep_mode
4783 self
.xed_decoded_inst_zero_keep_mode
.restype
= None
4784 self
.xed_decoded_inst_zero_keep_mode
.argtypes
= [ c_void_p
]
4786 self
.xed_decode
= self
.libxed
.xed_decode
4787 self
.xed_decode
.restype
= c_int
4788 self
.xed_decode
.argtypes
= [ c_void_p
, c_void_p
, c_uint
]
4790 self
.xed_format_context
= self
.libxed
.xed_format_context
4791 self
.xed_format_context
.restype
= c_uint
4792 self
.xed_format_context
.argtypes
= [ c_int
, c_void_p
, c_void_p
, c_int
, c_ulonglong
, c_void_p
, c_void_p
]
4794 self
.xed_tables_init()
4796 def Instruction(self
):
4797 return XEDInstruction(self
)
4799 def SetMode(self
, inst
, mode
):
4801 inst
.state
.mode
= 4 # 32-bit
4802 inst
.state
.width
= 4 # 4 bytes
4804 inst
.state
.mode
= 1 # 64-bit
4805 inst
.state
.width
= 8 # 8 bytes
4806 self
.xed_operand_values_set_mode(inst
.xedp
, inst
.statep
)
4808 def DisassembleOne(self
, inst
, bytes_ptr
, bytes_cnt
, ip
):
4809 self
.xed_decoded_inst_zero_keep_mode(inst
.xedp
)
4810 err
= self
.xed_decode(inst
.xedp
, bytes_ptr
, bytes_cnt
)
4813 # Use AT&T mode (2), alternative is Intel (3)
4814 ok
= self
.xed_format_context(2, inst
.xedp
, inst
.bufferp
, sizeof(inst
.buffer), ip
, 0, 0)
4817 if sys
.version_info
[0] == 2:
4818 result
= inst
.buffer.value
4820 result
= inst
.buffer.value
.decode()
4821 # Return instruction length and the disassembled instruction text
4822 # For now, assume the length is in byte 166
4823 return inst
.xedd
[166], result
4825 def TryOpen(file_name
):
4827 return open(file_name
, "rb")
4832 result
= sizeof(c_void_p
)
4839 if sys
.version_info
[0] == 2:
4840 eclass
= ord(header
[4])
4841 encoding
= ord(header
[5])
4842 version
= ord(header
[6])
4845 encoding
= header
[5]
4847 if magic
== chr(127) + "ELF" and eclass
> 0 and eclass
< 3 and encoding
> 0 and encoding
< 3 and version
== 1:
4848 result
= True if eclass
== 2 else False
4855 def __init__(self
, dbref
, db
, dbname
):
4858 self
.dbname
= dbname
4859 self
.home_dir
= os
.path
.expanduser("~")
4860 self
.buildid_dir
= os
.getenv("PERF_BUILDID_DIR")
4861 if self
.buildid_dir
:
4862 self
.buildid_dir
+= "/.build-id/"
4864 self
.buildid_dir
= self
.home_dir
+ "/.debug/.build-id/"
4866 self
.mainwindow
= None
4867 self
.instances_to_shutdown_on_exit
= weakref
.WeakSet()
4869 self
.disassembler
= LibXED()
4870 self
.have_disassembler
= True
4872 self
.have_disassembler
= False
4873 self
.host_machine_id
= 0
4874 self
.host_start_time
= 0
4875 self
.host_finish_time
= 0
4877 def FileFromBuildId(self
, build_id
):
4878 file_name
= self
.buildid_dir
+ build_id
[0:2] + "/" + build_id
[2:] + "/elf"
4879 return TryOpen(file_name
)
4881 def FileFromNamesAndBuildId(self
, short_name
, long_name
, build_id
):
4882 # Assume current machine i.e. no support for virtualization
4883 if short_name
[0:7] == "[kernel" and os
.path
.basename(long_name
) == "kcore":
4884 file_name
= os
.getenv("PERF_KCORE")
4885 f
= TryOpen(file_name
) if file_name
else None
4888 # For now, no special handling if long_name is /proc/kcore
4889 f
= TryOpen(long_name
)
4892 f
= self
.FileFromBuildId(build_id
)
4897 def AddInstanceToShutdownOnExit(self
, instance
):
4898 self
.instances_to_shutdown_on_exit
.add(instance
)
4900 # Shutdown any background processes or threads
4901 def ShutdownInstances(self
):
4902 for x
in self
.instances_to_shutdown_on_exit
:
4908 def GetHostMachineId(self
):
4909 query
= QSqlQuery(self
.db
)
4910 QueryExec(query
, "SELECT id FROM machines WHERE pid = -1")
4912 self
.host_machine_id
= query
.value(0)
4914 self
.host_machine_id
= 0
4915 return self
.host_machine_id
4917 def HostMachineId(self
):
4918 if self
.host_machine_id
:
4919 return self
.host_machine_id
4920 return self
.GetHostMachineId()
4922 def SelectValue(self
, sql
):
4923 query
= QSqlQuery(self
.db
)
4925 QueryExec(query
, sql
)
4929 return Decimal(query
.value(0))
4932 def SwitchesMinTime(self
, machine_id
):
4933 return self
.SelectValue("SELECT time"
4934 " FROM context_switches"
4935 " WHERE time != 0 AND machine_id = " + str(machine_id
) +
4936 " ORDER BY id LIMIT 1")
4938 def SwitchesMaxTime(self
, machine_id
):
4939 return self
.SelectValue("SELECT time"
4940 " FROM context_switches"
4941 " WHERE time != 0 AND machine_id = " + str(machine_id
) +
4942 " ORDER BY id DESC LIMIT 1")
4944 def SamplesMinTime(self
, machine_id
):
4945 return self
.SelectValue("SELECT time"
4947 " WHERE time != 0 AND machine_id = " + str(machine_id
) +
4948 " ORDER BY id LIMIT 1")
4950 def SamplesMaxTime(self
, machine_id
):
4951 return self
.SelectValue("SELECT time"
4953 " WHERE time != 0 AND machine_id = " + str(machine_id
) +
4954 " ORDER BY id DESC LIMIT 1")
4956 def CallsMinTime(self
, machine_id
):
4957 return self
.SelectValue("SELECT calls.call_time"
4959 " INNER JOIN threads ON threads.thread_id = calls.thread_id"
4960 " WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id
) +
4961 " ORDER BY calls.id LIMIT 1")
4963 def CallsMaxTime(self
, machine_id
):
4964 return self
.SelectValue("SELECT calls.return_time"
4966 " INNER JOIN threads ON threads.thread_id = calls.thread_id"
4967 " WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id
) +
4968 " ORDER BY calls.return_time DESC LIMIT 1")
4970 def GetStartTime(self
, machine_id
):
4971 t0
= self
.SwitchesMinTime(machine_id
)
4972 t1
= self
.SamplesMinTime(machine_id
)
4973 t2
= self
.CallsMinTime(machine_id
)
4974 if t0
is None or (not(t1
is None) and t1
< t0
):
4976 if t0
is None or (not(t2
is None) and t2
< t0
):
4980 def GetFinishTime(self
, machine_id
):
4981 t0
= self
.SwitchesMaxTime(machine_id
)
4982 t1
= self
.SamplesMaxTime(machine_id
)
4983 t2
= self
.CallsMaxTime(machine_id
)
4984 if t0
is None or (not(t1
is None) and t1
> t0
):
4986 if t0
is None or (not(t2
is None) and t2
> t0
):
4990 def HostStartTime(self
):
4991 if self
.host_start_time
:
4992 return self
.host_start_time
4993 self
.host_start_time
= self
.GetStartTime(self
.HostMachineId())
4994 return self
.host_start_time
4996 def HostFinishTime(self
):
4997 if self
.host_finish_time
:
4998 return self
.host_finish_time
4999 self
.host_finish_time
= self
.GetFinishTime(self
.HostMachineId())
5000 return self
.host_finish_time
5002 def StartTime(self
, machine_id
):
5003 if machine_id
== self
.HostMachineId():
5004 return self
.HostStartTime()
5005 return self
.GetStartTime(machine_id
)
5007 def FinishTime(self
, machine_id
):
5008 if machine_id
== self
.HostMachineId():
5009 return self
.HostFinishTime()
5010 return self
.GetFinishTime(machine_id
)
5012 # Database reference
5016 def __init__(self
, is_sqlite3
, dbname
):
5017 self
.is_sqlite3
= is_sqlite3
5018 self
.dbname
= dbname
5020 self
.FALSE
= "FALSE"
5021 # SQLite prior to version 3.23 does not support TRUE and FALSE
5026 def Open(self
, connection_name
):
5027 dbname
= self
.dbname
5029 db
= QSqlDatabase
.addDatabase("QSQLITE", connection_name
)
5031 db
= QSqlDatabase
.addDatabase("QPSQL", connection_name
)
5032 opts
= dbname
.split()
5035 opt
= opt
.split("=")
5036 if opt
[0] == "hostname":
5037 db
.setHostName(opt
[1])
5038 elif opt
[0] == "port":
5039 db
.setPort(int(opt
[1]))
5040 elif opt
[0] == "username":
5041 db
.setUserName(opt
[1])
5042 elif opt
[0] == "password":
5043 db
.setPassword(opt
[1])
5044 elif opt
[0] == "dbname":
5049 db
.setDatabaseName(dbname
)
5051 raise Exception("Failed to open database " + dbname
+ " error: " + db
.lastError().text())
5057 usage_str
= "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
5058 " or: exported-sql-viewer.py --help-only"
5059 ap
= argparse
.ArgumentParser(usage
= usage_str
, add_help
= False)
5060 ap
.add_argument("--pyside-version-1", action
='store_true')
5061 ap
.add_argument("dbname", nargs
="?")
5062 ap
.add_argument("--help-only", action
='store_true')
5063 args
= ap
.parse_args()
5066 app
= QApplication(sys
.argv
)
5067 mainwindow
= HelpOnlyWindow()
5072 dbname
= args
.dbname
5075 print("Too few arguments")
5080 f
= open(dbname
, "rb")
5081 if f
.read(15) == b
'SQLite format 3':
5087 dbref
= DBRef(is_sqlite3
, dbname
)
5088 db
, dbname
= dbref
.Open("main")
5089 glb
= Glb(dbref
, db
, dbname
)
5090 app
= QApplication(sys
.argv
)
5092 mainwindow
= MainWindow(glb
)
5093 glb
.mainwindow
= mainwindow
5096 glb
.ShutdownInstances()
5100 if __name__
== "__main__":