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
94 # Only change warnings if the python -W option was not used
95 if not sys
.warnoptions
:
97 # PySide2 causes deprecation warnings, ignore them.
98 warnings
.filterwarnings("ignore", category
=DeprecationWarning)
105 import cPickle
as pickle
106 # size of pickled integer big enough for record size
116 from libxed
import LibXED
118 pyside_version_1
= True
119 if not "--pyside-version-1" in sys
.argv
:
121 from PySide2
.QtCore
import *
122 from PySide2
.QtGui
import *
123 from PySide2
.QtSql
import *
124 from PySide2
.QtWidgets
import *
125 pyside_version_1
= False
130 from PySide
.QtCore
import *
131 from PySide
.QtGui
import *
132 from PySide
.QtSql
import *
134 from decimal
import Decimal
, ROUND_HALF_UP
135 from ctypes
import CDLL
, Structure
, create_string_buffer
, addressof
, sizeof
, \
136 c_void_p
, c_bool
, c_byte
, c_char
, c_int
, c_uint
, c_longlong
, c_ulonglong
137 from multiprocessing
import Process
, Array
, Value
, Event
139 # xrange is range in Python3
145 def printerr(*args
, **keyword_args
):
146 print(*args
, file=sys
.stderr
, **keyword_args
)
148 # Data formatting helpers
157 return "+0x%x" % offset
161 if name
== "[kernel.kallsyms]":
165 def findnth(s
, sub
, n
, offs
=0):
171 return findnth(s
[pos
+ 1:], sub
, n
- 1, offs
+ pos
+ 1)
173 # Percent to one decimal place
175 def PercentToOneDP(n
, d
):
178 x
= (n
* Decimal(100)) / d
179 return str(x
.quantize(Decimal(".1"), rounding
=ROUND_HALF_UP
))
181 # Helper for queries that must not fail
183 def QueryExec(query
, stmt
):
184 ret
= query
.exec_(stmt
)
186 raise Exception("Query failed: " + query
.lastError().text())
190 class Thread(QThread
):
192 done
= Signal(object)
194 def __init__(self
, task
, param
=None, parent
=None):
195 super(Thread
, self
).__init
__(parent
)
201 if self
.param
is None:
202 done
, result
= self
.task()
204 done
, result
= self
.task(self
.param
)
205 self
.done
.emit(result
)
211 class TreeModel(QAbstractItemModel
):
213 def __init__(self
, glb
, params
, parent
=None):
214 super(TreeModel
, self
).__init
__(parent
)
217 self
.root
= self
.GetRoot()
218 self
.last_row_read
= 0
220 def Item(self
, parent
):
222 return parent
.internalPointer()
226 def rowCount(self
, parent
):
227 result
= self
.Item(parent
).childCount()
230 self
.dataChanged
.emit(parent
, parent
)
233 def hasChildren(self
, parent
):
234 return self
.Item(parent
).hasChildren()
236 def headerData(self
, section
, orientation
, role
):
237 if role
== Qt
.TextAlignmentRole
:
238 return self
.columnAlignment(section
)
239 if role
!= Qt
.DisplayRole
:
241 if orientation
!= Qt
.Horizontal
:
243 return self
.columnHeader(section
)
245 def parent(self
, child
):
246 child_item
= child
.internalPointer()
247 if child_item
is self
.root
:
249 parent_item
= child_item
.getParentItem()
250 return self
.createIndex(parent_item
.getRow(), 0, parent_item
)
252 def index(self
, row
, column
, parent
):
253 child_item
= self
.Item(parent
).getChildItem(row
)
254 return self
.createIndex(row
, column
, child_item
)
256 def DisplayData(self
, item
, index
):
257 return item
.getData(index
.column())
259 def FetchIfNeeded(self
, row
):
260 if row
> self
.last_row_read
:
261 self
.last_row_read
= row
262 if row
+ 10 >= self
.root
.child_count
:
263 self
.fetcher
.Fetch(glb_chunk_sz
)
265 def columnAlignment(self
, column
):
268 def columnFont(self
, column
):
271 def data(self
, index
, role
):
272 if role
== Qt
.TextAlignmentRole
:
273 return self
.columnAlignment(index
.column())
274 if role
== Qt
.FontRole
:
275 return self
.columnFont(index
.column())
276 if role
!= Qt
.DisplayRole
:
278 item
= index
.internalPointer()
279 return self
.DisplayData(item
, index
)
283 class TableModel(QAbstractTableModel
):
285 def __init__(self
, parent
=None):
286 super(TableModel
, self
).__init
__(parent
)
288 self
.child_items
= []
289 self
.last_row_read
= 0
291 def Item(self
, parent
):
293 return parent
.internalPointer()
297 def rowCount(self
, parent
):
298 return self
.child_count
300 def headerData(self
, section
, orientation
, role
):
301 if role
== Qt
.TextAlignmentRole
:
302 return self
.columnAlignment(section
)
303 if role
!= Qt
.DisplayRole
:
305 if orientation
!= Qt
.Horizontal
:
307 return self
.columnHeader(section
)
309 def index(self
, row
, column
, parent
):
310 return self
.createIndex(row
, column
, self
.child_items
[row
])
312 def DisplayData(self
, item
, index
):
313 return item
.getData(index
.column())
315 def FetchIfNeeded(self
, row
):
316 if row
> self
.last_row_read
:
317 self
.last_row_read
= row
318 if row
+ 10 >= self
.child_count
:
319 self
.fetcher
.Fetch(glb_chunk_sz
)
321 def columnAlignment(self
, column
):
324 def columnFont(self
, column
):
327 def data(self
, index
, role
):
328 if role
== Qt
.TextAlignmentRole
:
329 return self
.columnAlignment(index
.column())
330 if role
== Qt
.FontRole
:
331 return self
.columnFont(index
.column())
332 if role
!= Qt
.DisplayRole
:
334 item
= index
.internalPointer()
335 return self
.DisplayData(item
, index
)
339 model_cache
= weakref
.WeakValueDictionary()
340 model_cache_lock
= threading
.Lock()
342 def LookupCreateModel(model_name
, create_fn
):
343 model_cache_lock
.acquire()
345 model
= model_cache
[model_name
]
350 model_cache
[model_name
] = model
351 model_cache_lock
.release()
354 def LookupModel(model_name
):
355 model_cache_lock
.acquire()
357 model
= model_cache
[model_name
]
360 model_cache_lock
.release()
367 def __init__(self
, parent
, finder
, is_reg_expr
=False):
370 self
.last_value
= None
371 self
.last_pattern
= None
373 label
= QLabel("Find:")
374 label
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
376 self
.textbox
= QComboBox()
377 self
.textbox
.setEditable(True)
378 self
.textbox
.currentIndexChanged
.connect(self
.ValueChanged
)
380 self
.progress
= QProgressBar()
381 self
.progress
.setRange(0, 0)
385 self
.pattern
= QCheckBox("Regular Expression")
387 self
.pattern
= QCheckBox("Pattern")
388 self
.pattern
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
390 self
.next_button
= QToolButton()
391 self
.next_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_ArrowDown
))
392 self
.next_button
.released
.connect(lambda: self
.NextPrev(1))
394 self
.prev_button
= QToolButton()
395 self
.prev_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_ArrowUp
))
396 self
.prev_button
.released
.connect(lambda: self
.NextPrev(-1))
398 self
.close_button
= QToolButton()
399 self
.close_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_DockWidgetCloseButton
))
400 self
.close_button
.released
.connect(self
.Deactivate
)
402 self
.hbox
= QHBoxLayout()
403 self
.hbox
.setContentsMargins(0, 0, 0, 0)
405 self
.hbox
.addWidget(label
)
406 self
.hbox
.addWidget(self
.textbox
)
407 self
.hbox
.addWidget(self
.progress
)
408 self
.hbox
.addWidget(self
.pattern
)
409 self
.hbox
.addWidget(self
.next_button
)
410 self
.hbox
.addWidget(self
.prev_button
)
411 self
.hbox
.addWidget(self
.close_button
)
414 self
.bar
.setLayout(self
.hbox
)
422 self
.textbox
.lineEdit().selectAll()
423 self
.textbox
.setFocus()
425 def Deactivate(self
):
429 self
.textbox
.setEnabled(False)
431 self
.next_button
.hide()
432 self
.prev_button
.hide()
436 self
.textbox
.setEnabled(True)
439 self
.next_button
.show()
440 self
.prev_button
.show()
442 def Find(self
, direction
):
443 value
= self
.textbox
.currentText()
444 pattern
= self
.pattern
.isChecked()
445 self
.last_value
= value
446 self
.last_pattern
= pattern
447 self
.finder
.Find(value
, direction
, pattern
, self
.context
)
449 def ValueChanged(self
):
450 value
= self
.textbox
.currentText()
451 pattern
= self
.pattern
.isChecked()
452 index
= self
.textbox
.currentIndex()
453 data
= self
.textbox
.itemData(index
)
454 # Store the pattern in the combo box to keep it with the text value
456 self
.textbox
.setItemData(index
, pattern
)
458 self
.pattern
.setChecked(data
)
461 def NextPrev(self
, direction
):
462 value
= self
.textbox
.currentText()
463 pattern
= self
.pattern
.isChecked()
464 if value
!= self
.last_value
:
465 index
= self
.textbox
.findText(value
)
466 # Allow for a button press before the value has been added to the combo box
468 index
= self
.textbox
.count()
469 self
.textbox
.addItem(value
, pattern
)
470 self
.textbox
.setCurrentIndex(index
)
473 self
.textbox
.setItemData(index
, pattern
)
474 elif pattern
!= self
.last_pattern
:
475 # Keep the pattern recorded in the combo box up to date
476 index
= self
.textbox
.currentIndex()
477 self
.textbox
.setItemData(index
, pattern
)
481 QMessageBox
.information(self
.bar
, "Find", "'" + self
.textbox
.currentText() + "' not found")
483 # Context-sensitive call graph data model item base
485 class CallGraphLevelItemBase(object):
487 def __init__(self
, glb
, params
, row
, parent_item
):
491 self
.parent_item
= parent_item
492 self
.query_done
= False
494 self
.child_items
= []
496 self
.level
= parent_item
.level
+ 1
500 def getChildItem(self
, row
):
501 return self
.child_items
[row
]
503 def getParentItem(self
):
504 return self
.parent_item
509 def childCount(self
):
510 if not self
.query_done
:
512 if not self
.child_count
:
514 return self
.child_count
516 def hasChildren(self
):
517 if not self
.query_done
:
519 return self
.child_count
> 0
521 def getData(self
, column
):
522 return self
.data
[column
]
524 # Context-sensitive call graph data model level 2+ item base
526 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase
):
528 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, call_path_id
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
529 super(CallGraphLevelTwoPlusItemBase
, self
).__init
__(glb
, params
, row
, parent_item
)
530 self
.comm_id
= comm_id
531 self
.thread_id
= thread_id
532 self
.call_path_id
= call_path_id
533 self
.insn_cnt
= insn_cnt
534 self
.cyc_cnt
= cyc_cnt
535 self
.branch_count
= branch_count
539 self
.query_done
= True
540 query
= QSqlQuery(self
.glb
.db
)
541 if self
.params
.have_ipc
:
542 ipc_str
= ", SUM(insn_count), SUM(cyc_count)"
545 QueryExec(query
, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str
+ ", SUM(branch_count)"
547 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
548 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
549 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
550 " WHERE parent_call_path_id = " + str(self
.call_path_id
) +
551 " AND comm_id = " + str(self
.comm_id
) +
552 " AND thread_id = " + str(self
.thread_id
) +
553 " GROUP BY call_path_id, name, short_name"
554 " ORDER BY call_path_id")
556 if self
.params
.have_ipc
:
557 insn_cnt
= int(query
.value(5))
558 cyc_cnt
= int(query
.value(6))
559 branch_count
= int(query
.value(7))
563 branch_count
= int(query
.value(5))
564 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
)
565 self
.child_items
.append(child_item
)
566 self
.child_count
+= 1
568 # Context-sensitive call graph data model level three item
570 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase
):
572 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
):
573 super(CallGraphLevelThreeItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, call_path_id
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
)
575 if self
.params
.have_ipc
:
576 insn_pcnt
= PercentToOneDP(insn_cnt
, parent_item
.insn_cnt
)
577 cyc_pcnt
= PercentToOneDP(cyc_cnt
, parent_item
.cyc_cnt
)
578 br_pcnt
= PercentToOneDP(branch_count
, parent_item
.branch_count
)
579 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
580 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
]
582 self
.data
= [ name
, dso
, str(count
), str(time
), PercentToOneDP(time
, parent_item
.time
), str(branch_count
), PercentToOneDP(branch_count
, parent_item
.branch_count
) ]
583 self
.dbid
= call_path_id
585 # Context-sensitive call graph data model level two item
587 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase
):
589 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, pid
, tid
, parent_item
):
590 super(CallGraphLevelTwoItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, 1, 0, 0, 0, 0, parent_item
)
591 if self
.params
.have_ipc
:
592 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", "", "", "", "", "", ""]
594 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", ""]
595 self
.dbid
= thread_id
598 super(CallGraphLevelTwoItem
, self
).Select()
599 for child_item
in self
.child_items
:
600 self
.time
+= child_item
.time
601 self
.insn_cnt
+= child_item
.insn_cnt
602 self
.cyc_cnt
+= child_item
.cyc_cnt
603 self
.branch_count
+= child_item
.branch_count
604 for child_item
in self
.child_items
:
605 child_item
.data
[4] = PercentToOneDP(child_item
.time
, self
.time
)
606 if self
.params
.have_ipc
:
607 child_item
.data
[6] = PercentToOneDP(child_item
.insn_cnt
, self
.insn_cnt
)
608 child_item
.data
[8] = PercentToOneDP(child_item
.cyc_cnt
, self
.cyc_cnt
)
609 child_item
.data
[11] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
611 child_item
.data
[6] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
613 # Context-sensitive call graph data model level one item
615 class CallGraphLevelOneItem(CallGraphLevelItemBase
):
617 def __init__(self
, glb
, params
, row
, comm_id
, comm
, parent_item
):
618 super(CallGraphLevelOneItem
, self
).__init
__(glb
, params
, row
, parent_item
)
619 if self
.params
.have_ipc
:
620 self
.data
= [comm
, "", "", "", "", "", "", "", "", "", "", ""]
622 self
.data
= [comm
, "", "", "", "", "", ""]
626 self
.query_done
= True
627 query
= QSqlQuery(self
.glb
.db
)
628 QueryExec(query
, "SELECT thread_id, pid, tid"
630 " INNER JOIN threads ON thread_id = threads.id"
631 " WHERE comm_id = " + str(self
.dbid
))
633 child_item
= CallGraphLevelTwoItem(self
.glb
, self
.params
, self
.child_count
, self
.dbid
, query
.value(0), query
.value(1), query
.value(2), self
)
634 self
.child_items
.append(child_item
)
635 self
.child_count
+= 1
637 # Context-sensitive call graph data model root item
639 class CallGraphRootItem(CallGraphLevelItemBase
):
641 def __init__(self
, glb
, params
):
642 super(CallGraphRootItem
, self
).__init
__(glb
, params
, 0, None)
644 self
.query_done
= True
646 if IsSelectable(glb
.db
, "comms", columns
= "has_calls"):
647 if_has_calls
= " WHERE has_calls = " + glb
.dbref
.TRUE
648 query
= QSqlQuery(glb
.db
)
649 QueryExec(query
, "SELECT id, comm FROM comms" + if_has_calls
)
651 if not query
.value(0):
653 child_item
= CallGraphLevelOneItem(glb
, params
, self
.child_count
, query
.value(0), query
.value(1), self
)
654 self
.child_items
.append(child_item
)
655 self
.child_count
+= 1
657 # Call graph model parameters
659 class CallGraphModelParams():
661 def __init__(self
, glb
, parent
=None):
662 self
.have_ipc
= IsSelectable(glb
.db
, "calls", columns
= "insn_count, cyc_count")
664 # Context-sensitive call graph data model base
666 class CallGraphModelBase(TreeModel
):
668 def __init__(self
, glb
, parent
=None):
669 super(CallGraphModelBase
, self
).__init
__(glb
, CallGraphModelParams(glb
), parent
)
671 def FindSelect(self
, value
, pattern
, query
):
673 # postgresql and sqlite pattern patching differences:
674 # postgresql LIKE is case sensitive but sqlite LIKE is not
675 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
676 # postgresql supports ILIKE which is case insensitive
677 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
678 if not self
.glb
.dbref
.is_sqlite3
:
680 s
= value
.replace("%", "\\%")
681 s
= s
.replace("_", "\\_")
682 # Translate * and ? into SQL LIKE pattern characters % and _
683 trans
= string
.maketrans("*?", "%_")
684 match
= " LIKE '" + str(s
).translate(trans
) + "'"
686 match
= " GLOB '" + str(value
) + "'"
688 match
= " = '" + str(value
) + "'"
689 self
.DoFindSelect(query
, match
)
691 def Found(self
, query
, found
):
693 return self
.FindPath(query
)
696 def FindValue(self
, value
, pattern
, query
, last_value
, last_pattern
):
697 if last_value
== value
and pattern
== last_pattern
:
698 found
= query
.first()
700 self
.FindSelect(value
, pattern
, query
)
702 return self
.Found(query
, found
)
704 def FindNext(self
, query
):
707 found
= query
.first()
708 return self
.Found(query
, found
)
710 def FindPrev(self
, query
):
711 found
= query
.previous()
714 return self
.Found(query
, found
)
716 def FindThread(self
, c
):
717 if c
.direction
== 0 or c
.value
!= c
.last_value
or c
.pattern
!= c
.last_pattern
:
718 ids
= self
.FindValue(c
.value
, c
.pattern
, c
.query
, c
.last_value
, c
.last_pattern
)
719 elif c
.direction
> 0:
720 ids
= self
.FindNext(c
.query
)
722 ids
= self
.FindPrev(c
.query
)
725 def Find(self
, value
, direction
, pattern
, context
, callback
):
727 def __init__(self
, *x
):
728 self
.value
, self
.direction
, self
.pattern
, self
.query
, self
.last_value
, self
.last_pattern
= x
729 def Update(self
, *x
):
730 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= x
+ (self
.value
, self
.pattern
)
732 context
[0].Update(value
, direction
, pattern
)
734 context
.append(Context(value
, direction
, pattern
, QSqlQuery(self
.glb
.db
), None, None))
735 # Use a thread so the UI is not blocked during the SELECT
736 thread
= Thread(self
.FindThread
, context
[0])
737 thread
.done
.connect(lambda ids
, t
=thread
, c
=callback
: self
.FindDone(t
, c
, ids
), Qt
.QueuedConnection
)
740 def FindDone(self
, thread
, callback
, ids
):
743 # Context-sensitive call graph data model
745 class CallGraphModel(CallGraphModelBase
):
747 def __init__(self
, glb
, parent
=None):
748 super(CallGraphModel
, self
).__init
__(glb
, parent
)
751 return CallGraphRootItem(self
.glb
, self
.params
)
753 def columnCount(self
, parent
=None):
754 if self
.params
.have_ipc
:
759 def columnHeader(self
, column
):
760 if self
.params
.have_ipc
:
761 headers
= ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
763 headers
= ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
764 return headers
[column
]
766 def columnAlignment(self
, column
):
767 if self
.params
.have_ipc
:
768 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
]
770 alignment
= [ Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
]
771 return alignment
[column
]
773 def DoFindSelect(self
, query
, match
):
774 QueryExec(query
, "SELECT call_path_id, comm_id, thread_id"
776 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
777 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
778 " WHERE calls.id <> 0"
779 " AND symbols.name" + match
+
780 " GROUP BY comm_id, thread_id, call_path_id"
781 " ORDER BY comm_id, thread_id, call_path_id")
783 def FindPath(self
, query
):
784 # Turn the query result into a list of ids that the tree view can walk
785 # to open the tree at the right place.
787 parent_id
= query
.value(0)
789 ids
.insert(0, parent_id
)
790 q2
= QSqlQuery(self
.glb
.db
)
791 QueryExec(q2
, "SELECT parent_id"
793 " WHERE id = " + str(parent_id
))
796 parent_id
= q2
.value(0)
797 # The call path root is not used
800 ids
.insert(0, query
.value(2))
801 ids
.insert(0, query
.value(1))
804 # Call tree data model level 2+ item base
806 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase
):
808 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, calls_id
, call_time
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
809 super(CallTreeLevelTwoPlusItemBase
, self
).__init
__(glb
, params
, row
, parent_item
)
810 self
.comm_id
= comm_id
811 self
.thread_id
= thread_id
812 self
.calls_id
= calls_id
813 self
.call_time
= call_time
815 self
.insn_cnt
= insn_cnt
816 self
.cyc_cnt
= cyc_cnt
817 self
.branch_count
= branch_count
820 self
.query_done
= True
821 if self
.calls_id
== 0:
822 comm_thread
= " AND comm_id = " + str(self
.comm_id
) + " AND thread_id = " + str(self
.thread_id
)
825 if self
.params
.have_ipc
:
826 ipc_str
= ", insn_count, cyc_count"
829 query
= QSqlQuery(self
.glb
.db
)
830 QueryExec(query
, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str
+ ", branch_count"
832 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
833 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
834 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
835 " WHERE calls.parent_id = " + str(self
.calls_id
) + comm_thread
+
836 " ORDER BY call_time, calls.id")
838 if self
.params
.have_ipc
:
839 insn_cnt
= int(query
.value(5))
840 cyc_cnt
= int(query
.value(6))
841 branch_count
= int(query
.value(7))
845 branch_count
= int(query
.value(5))
846 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
)
847 self
.child_items
.append(child_item
)
848 self
.child_count
+= 1
850 # Call tree data model level three item
852 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase
):
854 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
):
855 super(CallTreeLevelThreeItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, calls_id
, call_time
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
)
857 if self
.params
.have_ipc
:
858 insn_pcnt
= PercentToOneDP(insn_cnt
, parent_item
.insn_cnt
)
859 cyc_pcnt
= PercentToOneDP(cyc_cnt
, parent_item
.cyc_cnt
)
860 br_pcnt
= PercentToOneDP(branch_count
, parent_item
.branch_count
)
861 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
862 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
]
864 self
.data
= [ name
, dso
, str(call_time
), str(time
), PercentToOneDP(time
, parent_item
.time
), str(branch_count
), PercentToOneDP(branch_count
, parent_item
.branch_count
) ]
867 # Call tree data model level two item
869 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase
):
871 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, pid
, tid
, parent_item
):
872 super(CallTreeLevelTwoItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, 0, 0, 0, 0, 0, 0, parent_item
)
873 if self
.params
.have_ipc
:
874 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", "", "", "", "", "", ""]
876 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", ""]
877 self
.dbid
= thread_id
880 super(CallTreeLevelTwoItem
, self
).Select()
881 for child_item
in self
.child_items
:
882 self
.time
+= child_item
.time
883 self
.insn_cnt
+= child_item
.insn_cnt
884 self
.cyc_cnt
+= child_item
.cyc_cnt
885 self
.branch_count
+= child_item
.branch_count
886 for child_item
in self
.child_items
:
887 child_item
.data
[4] = PercentToOneDP(child_item
.time
, self
.time
)
888 if self
.params
.have_ipc
:
889 child_item
.data
[6] = PercentToOneDP(child_item
.insn_cnt
, self
.insn_cnt
)
890 child_item
.data
[8] = PercentToOneDP(child_item
.cyc_cnt
, self
.cyc_cnt
)
891 child_item
.data
[11] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
893 child_item
.data
[6] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
895 # Call tree data model level one item
897 class CallTreeLevelOneItem(CallGraphLevelItemBase
):
899 def __init__(self
, glb
, params
, row
, comm_id
, comm
, parent_item
):
900 super(CallTreeLevelOneItem
, self
).__init
__(glb
, params
, row
, parent_item
)
901 if self
.params
.have_ipc
:
902 self
.data
= [comm
, "", "", "", "", "", "", "", "", "", "", ""]
904 self
.data
= [comm
, "", "", "", "", "", ""]
908 self
.query_done
= True
909 query
= QSqlQuery(self
.glb
.db
)
910 QueryExec(query
, "SELECT thread_id, pid, tid"
912 " INNER JOIN threads ON thread_id = threads.id"
913 " WHERE comm_id = " + str(self
.dbid
))
915 child_item
= CallTreeLevelTwoItem(self
.glb
, self
.params
, self
.child_count
, self
.dbid
, query
.value(0), query
.value(1), query
.value(2), self
)
916 self
.child_items
.append(child_item
)
917 self
.child_count
+= 1
919 # Call tree data model root item
921 class CallTreeRootItem(CallGraphLevelItemBase
):
923 def __init__(self
, glb
, params
):
924 super(CallTreeRootItem
, self
).__init
__(glb
, params
, 0, None)
926 self
.query_done
= True
928 if IsSelectable(glb
.db
, "comms", columns
= "has_calls"):
929 if_has_calls
= " WHERE has_calls = " + glb
.dbref
.TRUE
930 query
= QSqlQuery(glb
.db
)
931 QueryExec(query
, "SELECT id, comm FROM comms" + if_has_calls
)
933 if not query
.value(0):
935 child_item
= CallTreeLevelOneItem(glb
, params
, self
.child_count
, query
.value(0), query
.value(1), self
)
936 self
.child_items
.append(child_item
)
937 self
.child_count
+= 1
939 # Call Tree data model
941 class CallTreeModel(CallGraphModelBase
):
943 def __init__(self
, glb
, parent
=None):
944 super(CallTreeModel
, self
).__init
__(glb
, parent
)
947 return CallTreeRootItem(self
.glb
, self
.params
)
949 def columnCount(self
, parent
=None):
950 if self
.params
.have_ipc
:
955 def columnHeader(self
, column
):
956 if self
.params
.have_ipc
:
957 headers
= ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
959 headers
= ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
960 return headers
[column
]
962 def columnAlignment(self
, column
):
963 if self
.params
.have_ipc
:
964 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
]
966 alignment
= [ Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
]
967 return alignment
[column
]
969 def DoFindSelect(self
, query
, match
):
970 QueryExec(query
, "SELECT calls.id, comm_id, thread_id"
972 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
973 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
974 " WHERE calls.id <> 0"
975 " AND symbols.name" + match
+
976 " ORDER BY comm_id, thread_id, call_time, calls.id")
978 def FindPath(self
, query
):
979 # Turn the query result into a list of ids that the tree view can walk
980 # to open the tree at the right place.
982 parent_id
= query
.value(0)
984 ids
.insert(0, parent_id
)
985 q2
= QSqlQuery(self
.glb
.db
)
986 QueryExec(q2
, "SELECT parent_id"
988 " WHERE id = " + str(parent_id
))
991 parent_id
= q2
.value(0)
992 ids
.insert(0, query
.value(2))
993 ids
.insert(0, query
.value(1))
998 class HBoxLayout(QHBoxLayout
):
1000 def __init__(self
, *children
):
1001 super(HBoxLayout
, self
).__init
__()
1003 self
.layout().setContentsMargins(0, 0, 0, 0)
1004 for child
in children
:
1005 if child
.isWidgetType():
1006 self
.layout().addWidget(child
)
1008 self
.layout().addLayout(child
)
1012 class VBoxLayout(QVBoxLayout
):
1014 def __init__(self
, *children
):
1015 super(VBoxLayout
, self
).__init
__()
1017 self
.layout().setContentsMargins(0, 0, 0, 0)
1018 for child
in children
:
1019 if child
.isWidgetType():
1020 self
.layout().addWidget(child
)
1022 self
.layout().addLayout(child
)
1024 # Vertical layout widget
1028 def __init__(self
, *children
):
1029 self
.vbox
= QWidget()
1030 self
.vbox
.setLayout(VBoxLayout(*children
))
1037 class TreeWindowBase(QMdiSubWindow
):
1039 def __init__(self
, parent
=None):
1040 super(TreeWindowBase
, self
).__init
__(parent
)
1043 self
.find_bar
= None
1045 self
.view
= QTreeView()
1046 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
1047 self
.view
.CopyCellsToClipboard
= CopyTreeCellsToClipboard
1049 self
.context_menu
= TreeContextMenu(self
.view
)
1051 def DisplayFound(self
, ids
):
1054 parent
= QModelIndex()
1057 n
= self
.model
.rowCount(parent
)
1058 for row
in xrange(n
):
1059 child
= self
.model
.index(row
, 0, parent
)
1060 if child
.internalPointer().dbid
== dbid
:
1062 self
.view
.setExpanded(parent
, True)
1063 self
.view
.setCurrentIndex(child
)
1070 def Find(self
, value
, direction
, pattern
, context
):
1071 self
.view
.setFocus()
1072 self
.find_bar
.Busy()
1073 self
.model
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
1075 def FindDone(self
, ids
):
1077 if not self
.DisplayFound(ids
):
1079 self
.find_bar
.Idle()
1081 self
.find_bar
.NotFound()
1084 # Context-sensitive call graph window
1086 class CallGraphWindow(TreeWindowBase
):
1088 def __init__(self
, glb
, parent
=None):
1089 super(CallGraphWindow
, self
).__init
__(parent
)
1091 self
.model
= LookupCreateModel("Context-Sensitive Call Graph", lambda x
=glb
: CallGraphModel(x
))
1093 self
.view
.setModel(self
.model
)
1095 for c
, w
in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1096 self
.view
.setColumnWidth(c
, w
)
1098 self
.find_bar
= FindBar(self
, self
)
1100 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget())
1102 self
.setWidget(self
.vbox
.Widget())
1104 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Context-Sensitive Call Graph")
1108 class CallTreeWindow(TreeWindowBase
):
1110 def __init__(self
, glb
, parent
=None, thread_at_time
=None):
1111 super(CallTreeWindow
, self
).__init
__(parent
)
1113 self
.model
= LookupCreateModel("Call Tree", lambda x
=glb
: CallTreeModel(x
))
1115 self
.view
.setModel(self
.model
)
1117 for c
, w
in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1118 self
.view
.setColumnWidth(c
, w
)
1120 self
.find_bar
= FindBar(self
, self
)
1122 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget())
1124 self
.setWidget(self
.vbox
.Widget())
1126 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Call Tree")
1129 self
.DisplayThreadAtTime(*thread_at_time
)
1131 def DisplayThreadAtTime(self
, comm_id
, thread_id
, time
):
1132 parent
= QModelIndex()
1133 for dbid
in (comm_id
, thread_id
):
1135 n
= self
.model
.rowCount(parent
)
1136 for row
in xrange(n
):
1137 child
= self
.model
.index(row
, 0, parent
)
1138 if child
.internalPointer().dbid
== dbid
:
1140 self
.view
.setExpanded(parent
, True)
1141 self
.view
.setCurrentIndex(child
)
1148 n
= self
.model
.rowCount(parent
)
1152 for row
in xrange(n
):
1153 self
.view
.setExpanded(parent
, True)
1154 child
= self
.model
.index(row
, 0, parent
)
1155 child_call_time
= child
.internalPointer().call_time
1156 if child_call_time
< time
:
1158 elif child_call_time
== time
:
1159 self
.view
.setCurrentIndex(child
)
1161 elif child_call_time
> time
:
1165 child
= self
.model
.index(0, 0, parent
)
1166 self
.view
.setExpanded(parent
, True)
1167 self
.view
.setCurrentIndex(child
)
1170 self
.view
.setExpanded(parent
, True)
1171 self
.view
.setCurrentIndex(last_child
)
1174 # ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
1176 def ExecComm(db
, thread_id
, time
):
1177 query
= QSqlQuery(db
)
1178 QueryExec(query
, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag"
1179 " FROM comm_threads"
1180 " INNER JOIN comms ON comms.id = comm_threads.comm_id"
1181 " WHERE comm_threads.thread_id = " + str(thread_id
) +
1182 " ORDER BY comms.c_time, comms.id")
1187 first
= query
.value(0)
1188 if query
.value(2) and Decimal(query
.value(1)) <= Decimal(time
):
1189 last
= query
.value(0)
1190 if not(last
is None):
1194 # Container for (x, y) data
1197 def __init__(self
, x
=0, y
=0):
1202 return "XY({}, {})".format(str(self
.x
), str(self
.y
))
1204 # Container for sub-range data
1207 def __init__(self
, lo
=0, hi
=0):
1212 return "Subrange({}, {})".format(str(self
.lo
), str(self
.hi
))
1214 # Graph data region base class
1216 class GraphDataRegion(object):
1218 def __init__(self
, key
, title
= "", ordinal
= ""):
1221 self
.ordinal
= ordinal
1223 # Function to sort GraphDataRegion
1225 def GraphDataRegionOrdinal(data_region
):
1226 return data_region
.ordinal
1228 # Attributes for a graph region
1230 class GraphRegionAttribute():
1232 def __init__(self
, colour
):
1233 self
.colour
= colour
1235 # Switch graph data region represents a task
1237 class SwitchGraphDataRegion(GraphDataRegion
):
1239 def __init__(self
, key
, exec_comm_id
, pid
, tid
, comm
, thread_id
, comm_id
):
1240 super(SwitchGraphDataRegion
, self
).__init
__(key
)
1242 self
.title
= str(pid
) + " / " + str(tid
) + " " + comm
1243 # Order graph legend within exec comm by pid / tid / time
1244 self
.ordinal
= str(pid
).rjust(16) + str(exec_comm_id
).rjust(8) + str(tid
).rjust(16)
1245 self
.exec_comm_id
= exec_comm_id
1249 self
.thread_id
= thread_id
1250 self
.comm_id
= comm_id
1254 class GraphDataPoint():
1256 def __init__(self
, data
, index
, x
, y
, altx
=None, alty
=None, hregion
=None, vregion
=None):
1263 self
.hregion
= hregion
1264 self
.vregion
= vregion
1266 # Graph data (single graph) base class
1268 class GraphData(object):
1270 def __init__(self
, collection
, xbase
=Decimal(0), ybase
=Decimal(0)):
1271 self
.collection
= collection
1277 def AddPoint(self
, x
, y
, altx
=None, alty
=None, hregion
=None, vregion
=None):
1278 index
= len(self
.points
)
1280 x
= float(Decimal(x
) - self
.xbase
)
1281 y
= float(Decimal(y
) - self
.ybase
)
1283 self
.points
.append(GraphDataPoint(self
, index
, x
, y
, altx
, alty
, hregion
, vregion
))
1285 def XToData(self
, x
):
1286 return Decimal(x
) + self
.xbase
1288 def YToData(self
, y
):
1289 return Decimal(y
) + self
.ybase
1291 # Switch graph data (for one CPU)
1293 class SwitchGraphData(GraphData
):
1295 def __init__(self
, db
, collection
, cpu
, xbase
):
1296 super(SwitchGraphData
, self
).__init
__(collection
, xbase
)
1299 self
.title
= "CPU " + str(cpu
)
1300 self
.SelectSwitches(db
)
1302 def SelectComms(self
, db
, thread_id
, last_comm_id
, start_time
, end_time
):
1303 query
= QSqlQuery(db
)
1304 QueryExec(query
, "SELECT id, c_time"
1306 " WHERE c_thread_id = " + str(thread_id
) +
1307 " AND exec_flag = " + self
.collection
.glb
.dbref
.TRUE
+
1308 " AND c_time >= " + str(start_time
) +
1309 " AND c_time <= " + str(end_time
) +
1310 " ORDER BY c_time, id")
1312 comm_id
= query
.value(0)
1313 if comm_id
== last_comm_id
:
1315 time
= query
.value(1)
1316 hregion
= self
.HRegion(db
, thread_id
, comm_id
, time
)
1317 self
.AddPoint(time
, 1000, None, None, hregion
)
1319 def SelectSwitches(self
, db
):
1322 last_thread_id
= None
1323 query
= QSqlQuery(db
)
1324 QueryExec(query
, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags"
1325 " FROM context_switches"
1326 " WHERE machine_id = " + str(self
.collection
.machine_id
) +
1327 " AND cpu = " + str(self
.cpu
) +
1328 " ORDER BY time, id")
1330 flags
= int(query
.value(5))
1332 # Schedule-out: detect and add exec's
1333 if last_thread_id
== query
.value(1) and last_comm_id
is not None and last_comm_id
!= query
.value(3):
1334 self
.SelectComms(db
, last_thread_id
, last_comm_id
, last_time
, query
.value(0))
1336 # Schedule-in: add data point
1337 if len(self
.points
) == 0:
1338 start_time
= self
.collection
.glb
.StartTime(self
.collection
.machine_id
)
1339 hregion
= self
.HRegion(db
, query
.value(1), query
.value(3), start_time
)
1340 self
.AddPoint(start_time
, 1000, None, None, hregion
)
1341 time
= query
.value(0)
1342 comm_id
= query
.value(4)
1343 thread_id
= query
.value(2)
1344 hregion
= self
.HRegion(db
, thread_id
, comm_id
, time
)
1345 self
.AddPoint(time
, 1000, None, None, hregion
)
1347 last_comm_id
= comm_id
1348 last_thread_id
= thread_id
1350 def NewHRegion(self
, db
, key
, thread_id
, comm_id
, time
):
1351 exec_comm_id
= ExecComm(db
, thread_id
, time
)
1352 query
= QSqlQuery(db
)
1353 QueryExec(query
, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id
))
1355 pid
= query
.value(0)
1356 tid
= query
.value(1)
1360 query
= QSqlQuery(db
)
1361 QueryExec(query
, "SELECT comm FROM comms WHERE id = " + str(comm_id
))
1363 comm
= query
.value(0)
1366 return SwitchGraphDataRegion(key
, exec_comm_id
, pid
, tid
, comm
, thread_id
, comm_id
)
1368 def HRegion(self
, db
, thread_id
, comm_id
, time
):
1369 key
= str(thread_id
) + ":" + str(comm_id
)
1370 hregion
= self
.collection
.LookupHRegion(key
)
1372 hregion
= self
.NewHRegion(db
, key
, thread_id
, comm_id
, time
)
1373 self
.collection
.AddHRegion(key
, hregion
)
1376 # Graph data collection (multiple related graphs) base class
1378 class GraphDataCollection(object):
1380 def __init__(self
, glb
):
1384 self
.xrangelo
= None
1385 self
.xrangehi
= None
1386 self
.yrangelo
= None
1387 self
.yrangehi
= None
1390 def AddGraphData(self
, data
):
1391 self
.data
.append(data
)
1393 def LookupHRegion(self
, key
):
1394 if key
in self
.hregions
:
1395 return self
.hregions
[key
]
1398 def AddHRegion(self
, key
, hregion
):
1399 self
.hregions
[key
] = hregion
1401 # Switch graph data collection (SwitchGraphData for each CPU)
1403 class SwitchGraphDataCollection(GraphDataCollection
):
1405 def __init__(self
, glb
, db
, machine_id
):
1406 super(SwitchGraphDataCollection
, self
).__init
__(glb
)
1408 self
.machine_id
= machine_id
1409 self
.cpus
= self
.SelectCPUs(db
)
1411 self
.xrangelo
= glb
.StartTime(machine_id
)
1412 self
.xrangehi
= glb
.FinishTime(machine_id
)
1414 self
.yrangelo
= Decimal(0)
1415 self
.yrangehi
= Decimal(1000)
1417 for cpu
in self
.cpus
:
1418 self
.AddGraphData(SwitchGraphData(db
, self
, cpu
, self
.xrangelo
))
1420 def SelectCPUs(self
, db
):
1422 query
= QSqlQuery(db
)
1423 QueryExec(query
, "SELECT DISTINCT cpu"
1424 " FROM context_switches"
1425 " WHERE machine_id = " + str(self
.machine_id
))
1427 cpus
.append(int(query
.value(0)))
1430 # Switch graph data graphics item displays the graphed data
1432 class SwitchGraphDataGraphicsItem(QGraphicsItem
):
1434 def __init__(self
, data
, graph_width
, graph_height
, attrs
, event_handler
, parent
=None):
1435 super(SwitchGraphDataGraphicsItem
, self
).__init
__(parent
)
1438 self
.graph_width
= graph_width
1439 self
.graph_height
= graph_height
1441 self
.event_handler
= event_handler
1442 self
.setAcceptHoverEvents(True)
1444 def boundingRect(self
):
1445 return QRectF(0, 0, self
.graph_width
, self
.graph_height
)
1447 def PaintPoint(self
, painter
, last
, x
):
1448 if not(last
is None or last
.hregion
.pid
== 0 or x
< self
.attrs
.subrange
.x
.lo
):
1449 if last
.x
< self
.attrs
.subrange
.x
.lo
:
1450 x0
= self
.attrs
.subrange
.x
.lo
1453 if x
> self
.attrs
.subrange
.x
.hi
:
1454 x1
= self
.attrs
.subrange
.x
.hi
1457 x0
= self
.attrs
.XToPixel(x0
)
1458 x1
= self
.attrs
.XToPixel(x1
)
1460 y0
= self
.attrs
.YToPixel(last
.y
)
1462 colour
= self
.attrs
.region_attributes
[last
.hregion
.key
].colour
1466 painter
.setPen(colour
)
1467 painter
.drawLine(x0
, self
.graph_height
- y0
, x0
, self
.graph_height
)
1469 painter
.fillRect(x0
, self
.graph_height
- y0
, width
, self
.graph_height
- 1, colour
)
1471 def paint(self
, painter
, option
, widget
):
1473 for point
in self
.data
.points
:
1474 self
.PaintPoint(painter
, last
, point
.x
)
1475 if point
.x
> self
.attrs
.subrange
.x
.hi
:
1478 self
.PaintPoint(painter
, last
, self
.attrs
.subrange
.x
.hi
+ 1)
1480 def BinarySearchPoint(self
, target
):
1482 higher_pos
= len(self
.data
.points
)
1484 pos
= int((lower_pos
+ higher_pos
) / 2)
1485 val
= self
.data
.points
[pos
].x
1490 if higher_pos
<= lower_pos
+ 1:
1493 def XPixelToData(self
, x
):
1494 x
= self
.attrs
.PixelToX(x
)
1495 if x
< self
.data
.points
[0].x
:
1500 pos
= self
.BinarySearchPoint(x
)
1502 return (low
, pos
, self
.data
.XToData(x
))
1504 def EventToData(self
, event
):
1505 no_data
= (None,) * 4
1506 if len(self
.data
.points
) < 1:
1511 low0
, pos0
, time_from
= self
.XPixelToData(x
)
1512 low1
, pos1
, time_to
= self
.XPixelToData(x
+ 1)
1516 for i
in xrange(pos0
, pos1
+ 1):
1517 hregion
= self
.data
.points
[i
].hregion
1518 hregions
.add(hregion
)
1522 time
= self
.data
.XToData(self
.data
.points
[i
].x
)
1523 hregion_times
.append((hregion
, time
))
1524 return (time_from
, time_to
, hregions
, hregion_times
)
1526 def hoverMoveEvent(self
, event
):
1527 time_from
, time_to
, hregions
, hregion_times
= self
.EventToData(event
)
1528 if time_from
is not None:
1529 self
.event_handler
.PointEvent(self
.data
.cpu
, time_from
, time_to
, hregions
)
1531 def hoverLeaveEvent(self
, event
):
1532 self
.event_handler
.NoPointEvent()
1534 def mousePressEvent(self
, event
):
1535 if event
.button() != Qt
.RightButton
:
1536 super(SwitchGraphDataGraphicsItem
, self
).mousePressEvent(event
)
1538 time_from
, time_to
, hregions
, hregion_times
= self
.EventToData(event
)
1540 self
.event_handler
.RightClickEvent(self
.data
.cpu
, hregion_times
, event
.screenPos())
1542 # X-axis graphics item
1544 class XAxisGraphicsItem(QGraphicsItem
):
1546 def __init__(self
, width
, parent
=None):
1547 super(XAxisGraphicsItem
, self
).__init
__(parent
)
1550 self
.max_mark_sz
= 4
1551 self
.height
= self
.max_mark_sz
+ 1
1553 def boundingRect(self
):
1554 return QRectF(0, 0, self
.width
, self
.height
)
1557 attrs
= self
.parentItem().attrs
1558 subrange
= attrs
.subrange
.x
1559 t
= subrange
.hi
- subrange
.lo
1560 s
= (3.0 * t
) / self
.width
1566 def PaintMarks(self
, painter
, at_y
, lo
, hi
, step
, i
):
1567 attrs
= self
.parentItem().attrs
1570 xp
= attrs
.XToPixel(x
)
1577 sz
= self
.max_mark_sz
1579 painter
.drawLine(xp
, at_y
, xp
, at_y
+ sz
)
1583 def paint(self
, painter
, option
, widget
):
1584 # Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
1585 painter
.drawLine(0, 0, self
.width
- 1, 0)
1587 attrs
= self
.parentItem().attrs
1588 subrange
= attrs
.subrange
.x
1590 x_offset
= n
- (subrange
.lo
% n
)
1593 x
= subrange
.lo
+ x_offset
1595 self
.PaintMarks(painter
, 0, x
, subrange
.hi
, n
, i
)
1597 def ScaleDimensions(self
):
1599 attrs
= self
.parentItem().attrs
1600 lo
= attrs
.subrange
.x
.lo
1601 hi
= (n
* 10.0) + lo
1602 width
= attrs
.XToPixel(hi
)
1605 return (n
, lo
, hi
, width
)
1607 def PaintScale(self
, painter
, at_x
, at_y
):
1608 n
, lo
, hi
, width
= self
.ScaleDimensions()
1611 painter
.drawLine(at_x
, at_y
, at_x
+ width
, at_y
)
1612 self
.PaintMarks(painter
, at_y
, lo
, hi
, n
, 0)
1614 def ScaleWidth(self
):
1615 n
, lo
, hi
, width
= self
.ScaleDimensions()
1618 def ScaleHeight(self
):
1621 def ScaleUnit(self
):
1622 return self
.Step() * 10
1624 # Scale graphics item base class
1626 class ScaleGraphicsItem(QGraphicsItem
):
1628 def __init__(self
, axis
, parent
=None):
1629 super(ScaleGraphicsItem
, self
).__init
__(parent
)
1632 def boundingRect(self
):
1633 scale_width
= self
.axis
.ScaleWidth()
1636 return QRectF(0, 0, self
.axis
.ScaleWidth() + 100, self
.axis
.ScaleHeight())
1638 def paint(self
, painter
, option
, widget
):
1639 scale_width
= self
.axis
.ScaleWidth()
1642 self
.axis
.PaintScale(painter
, 0, 5)
1644 painter
.drawText(QPointF(x
, 10), self
.Text())
1647 return self
.axis
.ScaleUnit()
1652 # Switch graph scale graphics item
1654 class SwitchScaleGraphicsItem(ScaleGraphicsItem
):
1656 def __init__(self
, axis
, parent
=None):
1657 super(SwitchScaleGraphicsItem
, self
).__init
__(axis
, parent
)
1661 if unit
>= 1000000000:
1662 unit
= int(unit
/ 1000000000)
1664 elif unit
>= 1000000:
1665 unit
= int(unit
/ 1000000)
1668 unit
= int(unit
/ 1000)
1673 return " = " + str(unit
) + " " + us
1675 # Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
1677 class SwitchGraphGraphicsItem(QGraphicsItem
):
1679 def __init__(self
, collection
, data
, attrs
, event_handler
, first
, parent
=None):
1680 super(SwitchGraphGraphicsItem
, self
).__init
__(parent
)
1681 self
.collection
= collection
1684 self
.event_handler
= event_handler
1689 self
.title_graphics
= QGraphicsSimpleTextItem(data
.title
, self
)
1691 self
.title_graphics
.setPos(margin
, margin
)
1692 graph_width
= attrs
.XToPixel(attrs
.subrange
.x
.hi
) + 1
1693 graph_height
= attrs
.YToPixel(attrs
.subrange
.y
.hi
) + 1
1695 self
.graph_origin_x
= margin
+ title_width
+ margin
1696 self
.graph_origin_y
= graph_height
+ margin
1700 self
.yline
= QGraphicsLineItem(0, 0, 0, graph_height
, self
)
1702 self
.x_axis
= XAxisGraphicsItem(graph_width
, self
)
1703 self
.x_axis
.setPos(self
.graph_origin_x
, self
.graph_origin_y
+ 1)
1706 self
.scale_item
= SwitchScaleGraphicsItem(self
.x_axis
, self
)
1707 self
.scale_item
.setPos(self
.graph_origin_x
, self
.graph_origin_y
+ 10)
1709 self
.yline
.setPos(self
.graph_origin_x
- y_axis_size
, self
.graph_origin_y
- graph_height
)
1711 self
.axis_point
= QGraphicsLineItem(0, 0, 0, 0, self
)
1712 self
.axis_point
.setPos(self
.graph_origin_x
- 1, self
.graph_origin_y
+1)
1714 self
.width
= self
.graph_origin_x
+ graph_width
+ margin
1715 self
.height
= self
.graph_origin_y
+ margin
1717 self
.graph
= SwitchGraphDataGraphicsItem(data
, graph_width
, graph_height
, attrs
, event_handler
, self
)
1718 self
.graph
.setPos(self
.graph_origin_x
, self
.graph_origin_y
- graph_height
)
1720 if parent
and 'EnableRubberBand' in dir(parent
):
1721 parent
.EnableRubberBand(self
.graph_origin_x
, self
.graph_origin_x
+ graph_width
- 1, self
)
1723 def boundingRect(self
):
1724 return QRectF(0, 0, self
.width
, self
.height
)
1726 def paint(self
, painter
, option
, widget
):
1729 def RBXToPixel(self
, x
):
1730 return self
.attrs
.PixelToX(x
- self
.graph_origin_x
)
1732 def RBXRangeToPixel(self
, x0
, x1
):
1733 return (self
.RBXToPixel(x0
), self
.RBXToPixel(x1
+ 1))
1735 def RBPixelToTime(self
, x
):
1736 if x
< self
.data
.points
[0].x
:
1737 return self
.data
.XToData(0)
1738 return self
.data
.XToData(x
)
1740 def RBEventTimes(self
, x0
, x1
):
1741 x0
, x1
= self
.RBXRangeToPixel(x0
, x1
)
1742 time_from
= self
.RBPixelToTime(x0
)
1743 time_to
= self
.RBPixelToTime(x1
)
1744 return (time_from
, time_to
)
1746 def RBEvent(self
, x0
, x1
):
1747 time_from
, time_to
= self
.RBEventTimes(x0
, x1
)
1748 self
.event_handler
.RangeEvent(time_from
, time_to
)
1750 def RBMoveEvent(self
, x0
, x1
):
1753 self
.RBEvent(x0
, x1
)
1755 def RBReleaseEvent(self
, x0
, x1
, selection_state
):
1758 x0
, x1
= self
.RBXRangeToPixel(x0
, x1
)
1759 self
.event_handler
.SelectEvent(x0
, x1
, selection_state
)
1761 # Graphics item to draw a vertical bracket (used to highlight "forward" sub-range)
1763 class VerticalBracketGraphicsItem(QGraphicsItem
):
1765 def __init__(self
, parent
=None):
1766 super(VerticalBracketGraphicsItem
, self
).__init
__(parent
)
1772 def SetSize(self
, width
, height
):
1773 self
.width
= width
+ 1
1774 self
.height
= height
+ 1
1776 def boundingRect(self
):
1777 return QRectF(0, 0, self
.width
, self
.height
)
1779 def paint(self
, painter
, option
, widget
):
1780 colour
= QColor(255, 255, 0, 32)
1781 painter
.fillRect(0, 0, self
.width
, self
.height
, colour
)
1783 y1
= self
.height
- 1
1784 painter
.drawLine(0, 0, x1
, 0)
1785 painter
.drawLine(0, 0, 0, 3)
1786 painter
.drawLine(x1
, 0, x1
, 3)
1787 painter
.drawLine(0, y1
, x1
, y1
)
1788 painter
.drawLine(0, y1
, 0, y1
- 3)
1789 painter
.drawLine(x1
, y1
, x1
, y1
- 3)
1791 # Graphics item to contain graphs arranged vertically
1793 class VertcalGraphSetGraphicsItem(QGraphicsItem
):
1795 def __init__(self
, collection
, attrs
, event_handler
, child_class
, parent
=None):
1796 super(VertcalGraphSetGraphicsItem
, self
).__init
__(parent
)
1798 self
.collection
= collection
1803 self
.height
= self
.top
1805 self
.rubber_band
= None
1806 self
.rb_enabled
= False
1809 for data
in collection
.data
:
1810 child
= child_class(collection
, data
, attrs
, event_handler
, first
, self
)
1811 child
.setPos(0, self
.height
+ 1)
1812 rect
= child
.boundingRect()
1813 if rect
.right() > self
.width
:
1814 self
.width
= rect
.right()
1815 self
.height
= self
.height
+ rect
.bottom() + 1
1818 self
.bracket
= VerticalBracketGraphicsItem(self
)
1820 def EnableRubberBand(self
, xlo
, xhi
, rb_event_handler
):
1823 self
.rb_enabled
= True
1824 self
.rb_in_view
= False
1825 self
.setAcceptedMouseButtons(Qt
.LeftButton
)
1828 self
.rb_event_handler
= rb_event_handler
1829 self
.mousePressEvent
= self
.MousePressEvent
1830 self
.mouseMoveEvent
= self
.MouseMoveEvent
1831 self
.mouseReleaseEvent
= self
.MouseReleaseEvent
1833 def boundingRect(self
):
1834 return QRectF(0, 0, self
.width
, self
.height
)
1836 def paint(self
, painter
, option
, widget
):
1839 def RubberBandParent(self
):
1840 scene
= self
.scene()
1841 view
= scene
.views()[0]
1842 viewport
= view
.viewport()
1845 def RubberBandSetGeometry(self
, rect
):
1846 scene_rectf
= self
.mapRectToScene(QRectF(rect
))
1847 scene
= self
.scene()
1848 view
= scene
.views()[0]
1849 poly
= view
.mapFromScene(scene_rectf
)
1850 self
.rubber_band
.setGeometry(poly
.boundingRect())
1852 def SetSelection(self
, selection_state
):
1853 if self
.rubber_band
:
1855 self
.RubberBandSetGeometry(selection_state
)
1856 self
.rubber_band
.show()
1858 self
.rubber_band
.hide()
1860 def SetBracket(self
, rect
):
1862 x
, y
, width
, height
= rect
.x(), rect
.y(), rect
.width(), rect
.height()
1863 self
.bracket
.setPos(x
, y
)
1864 self
.bracket
.SetSize(width
, height
)
1869 def RubberBandX(self
, event
):
1870 x
= event
.pos().toPoint().x()
1873 elif x
> self
.rb_xhi
:
1876 self
.rb_in_view
= True
1879 def RubberBandRect(self
, x
):
1880 if self
.rb_origin
.x() <= x
:
1881 width
= x
- self
.rb_origin
.x()
1882 rect
= QRect(self
.rb_origin
, QSize(width
, self
.height
))
1884 width
= self
.rb_origin
.x() - x
1885 top_left
= QPoint(self
.rb_origin
.x() - width
, self
.rb_origin
.y())
1886 rect
= QRect(top_left
, QSize(width
, self
.height
))
1889 def MousePressEvent(self
, event
):
1890 self
.rb_in_view
= False
1891 x
= self
.RubberBandX(event
)
1892 self
.rb_origin
= QPoint(x
, self
.top
)
1893 if self
.rubber_band
is None:
1894 self
.rubber_band
= QRubberBand(QRubberBand
.Rectangle
, self
.RubberBandParent())
1895 self
.RubberBandSetGeometry(QRect(self
.rb_origin
, QSize(0, self
.height
)))
1897 self
.rubber_band
.show()
1898 self
.rb_event_handler
.RBMoveEvent(x
, x
)
1900 self
.rubber_band
.hide()
1902 def MouseMoveEvent(self
, event
):
1903 x
= self
.RubberBandX(event
)
1904 rect
= self
.RubberBandRect(x
)
1905 self
.RubberBandSetGeometry(rect
)
1907 self
.rubber_band
.show()
1908 self
.rb_event_handler
.RBMoveEvent(self
.rb_origin
.x(), x
)
1910 def MouseReleaseEvent(self
, event
):
1911 x
= self
.RubberBandX(event
)
1913 selection_state
= self
.RubberBandRect(x
)
1915 selection_state
= None
1916 self
.rb_event_handler
.RBReleaseEvent(self
.rb_origin
.x(), x
, selection_state
)
1918 # Switch graph legend data model
1920 class SwitchGraphLegendModel(QAbstractTableModel
):
1922 def __init__(self
, collection
, region_attributes
, parent
=None):
1923 super(SwitchGraphLegendModel
, self
).__init
__(parent
)
1925 self
.region_attributes
= region_attributes
1927 self
.child_items
= sorted(collection
.hregions
.values(), key
=GraphDataRegionOrdinal
)
1928 self
.child_count
= len(self
.child_items
)
1930 self
.highlight_set
= set()
1932 self
.column_headers
= ("pid", "tid", "comm")
1934 def rowCount(self
, parent
):
1935 return self
.child_count
1937 def headerData(self
, section
, orientation
, role
):
1938 if role
!= Qt
.DisplayRole
:
1940 if orientation
!= Qt
.Horizontal
:
1942 return self
.columnHeader(section
)
1944 def index(self
, row
, column
, parent
):
1945 return self
.createIndex(row
, column
, self
.child_items
[row
])
1947 def columnCount(self
, parent
=None):
1948 return len(self
.column_headers
)
1950 def columnHeader(self
, column
):
1951 return self
.column_headers
[column
]
1953 def data(self
, index
, role
):
1954 if role
== Qt
.BackgroundRole
:
1955 child
= self
.child_items
[index
.row()]
1956 if child
in self
.highlight_set
:
1957 return self
.region_attributes
[child
.key
].colour
1959 if role
== Qt
.ForegroundRole
:
1960 child
= self
.child_items
[index
.row()]
1961 if child
in self
.highlight_set
:
1962 return QColor(255, 255, 255)
1963 return self
.region_attributes
[child
.key
].colour
1964 if role
!= Qt
.DisplayRole
:
1966 hregion
= self
.child_items
[index
.row()]
1967 col
= index
.column()
1976 def SetHighlight(self
, row
, set_highlight
):
1977 child
= self
.child_items
[row
]
1978 top_left
= self
.createIndex(row
, 0, child
)
1979 bottom_right
= self
.createIndex(row
, len(self
.column_headers
) - 1, child
)
1980 self
.dataChanged
.emit(top_left
, bottom_right
)
1982 def Highlight(self
, highlight_set
):
1983 for row
in xrange(self
.child_count
):
1984 child
= self
.child_items
[row
]
1985 if child
in self
.highlight_set
:
1986 if child
not in highlight_set
:
1987 self
.SetHighlight(row
, False)
1988 elif child
in highlight_set
:
1989 self
.SetHighlight(row
, True)
1990 self
.highlight_set
= highlight_set
1992 # Switch graph legend is a table
1994 class SwitchGraphLegend(QWidget
):
1996 def __init__(self
, collection
, region_attributes
, parent
=None):
1997 super(SwitchGraphLegend
, self
).__init
__(parent
)
1999 self
.data_model
= SwitchGraphLegendModel(collection
, region_attributes
)
2001 self
.model
= QSortFilterProxyModel()
2002 self
.model
.setSourceModel(self
.data_model
)
2004 self
.view
= QTableView()
2005 self
.view
.setModel(self
.model
)
2006 self
.view
.setEditTriggers(QAbstractItemView
.NoEditTriggers
)
2007 self
.view
.verticalHeader().setVisible(False)
2008 self
.view
.sortByColumn(-1, Qt
.AscendingOrder
)
2009 self
.view
.setSortingEnabled(True)
2010 self
.view
.resizeColumnsToContents()
2011 self
.view
.resizeRowsToContents()
2013 self
.vbox
= VBoxLayout(self
.view
)
2014 self
.setLayout(self
.vbox
)
2016 sz1
= self
.view
.columnWidth(0) + self
.view
.columnWidth(1) + self
.view
.columnWidth(2) + 2
2017 sz1
= sz1
+ self
.view
.verticalScrollBar().sizeHint().width()
2018 self
.saved_size
= sz1
2020 def resizeEvent(self
, event
):
2021 self
.saved_size
= self
.size().width()
2022 super(SwitchGraphLegend
, self
).resizeEvent(event
)
2024 def Highlight(self
, highlight_set
):
2025 self
.data_model
.Highlight(highlight_set
)
2028 def changeEvent(self
, event
):
2029 if event
.type() == QEvent
.FontChange
:
2030 self
.view
.resizeRowsToContents()
2031 self
.view
.resizeColumnsToContents()
2032 # Need to resize rows again after column resize
2033 self
.view
.resizeRowsToContents()
2034 super(SwitchGraphLegend
, self
).changeEvent(event
)
2036 # Random colour generation
2038 def RGBColourTooLight(r
, g
, b
):
2043 if r
<= 180 and g
<= 180:
2049 def GenerateColours(x
):
2051 for i
in xrange(1, x
):
2052 cs
.append(int((255.0 / i
) + 0.5))
2057 # Exclude black and colours that look too light against a white background
2058 if (r
, g
, b
) == (0, 0, 0) or RGBColourTooLight(r
, g
, b
):
2060 colours
.append(QColor(r
, g
, b
))
2063 def GenerateNColours(n
):
2064 for x
in xrange(2, n
+ 2):
2065 colours
= GenerateColours(x
)
2066 if len(colours
) >= n
:
2070 def GenerateNRandomColours(n
, seed
):
2071 colours
= GenerateNColours(n
)
2073 random
.shuffle(colours
)
2076 # Graph attributes, in particular the scale and subrange that change when zooming
2078 class GraphAttributes():
2080 def __init__(self
, scale
, subrange
, region_attributes
, dp
):
2082 self
.subrange
= subrange
2083 self
.region_attributes
= region_attributes
2084 # Rounding avoids errors due to finite floating point precision
2085 self
.dp
= dp
# data decimal places
2088 def XToPixel(self
, x
):
2089 return int(round((x
- self
.subrange
.x
.lo
) * self
.scale
.x
, self
.pdp
.x
))
2091 def YToPixel(self
, y
):
2092 return int(round((y
- self
.subrange
.y
.lo
) * self
.scale
.y
, self
.pdp
.y
))
2094 def PixelToXRounded(self
, px
):
2095 return round((round(px
, 0) / self
.scale
.x
), self
.dp
.x
) + self
.subrange
.x
.lo
2097 def PixelToYRounded(self
, py
):
2098 return round((round(py
, 0) / self
.scale
.y
), self
.dp
.y
) + self
.subrange
.y
.lo
2100 def PixelToX(self
, px
):
2101 x
= self
.PixelToXRounded(px
)
2103 rt
= self
.XToPixel(x
)
2108 def PixelToY(self
, py
):
2109 y
= self
.PixelToYRounded(py
)
2111 rt
= self
.YToPixel(y
)
2116 def ToPDP(self
, dp
, scale
):
2117 # Calculate pixel decimal places:
2118 # (10 ** dp) is the minimum delta in the data
2119 # scale it to get the minimum delta in pixels
2120 # log10 gives the number of decimals places negatively
2121 # subtrace 1 to divide by 10
2122 # round to the lower negative number
2123 # change the sign to get the number of decimals positively
2124 x
= math
.log10((10 ** dp
) * scale
)
2127 x
= -int(math
.floor(x
) - 0.1)
2133 x
= self
.ToPDP(self
.dp
.x
, self
.scale
.x
)
2134 y
= self
.ToPDP(self
.dp
.y
, self
.scale
.y
)
2135 self
.pdp
= XY(x
, y
) # pixel decimal places
2137 # Switch graph splitter which divides the CPU graphs from the legend
2139 class SwitchGraphSplitter(QSplitter
):
2141 def __init__(self
, parent
=None):
2142 super(SwitchGraphSplitter
, self
).__init
__(parent
)
2144 self
.first_time
= False
2146 def resizeEvent(self
, ev
):
2148 self
.first_time
= False
2149 sz1
= self
.widget(1).view
.columnWidth(0) + self
.widget(1).view
.columnWidth(1) + self
.widget(1).view
.columnWidth(2) + 2
2150 sz1
= sz1
+ self
.widget(1).view
.verticalScrollBar().sizeHint().width()
2151 sz0
= self
.size().width() - self
.handleWidth() - sz1
2152 self
.setSizes([sz0
, sz1
])
2153 elif not(self
.widget(1).saved_size
is None):
2154 sz1
= self
.widget(1).saved_size
2155 sz0
= self
.size().width() - self
.handleWidth() - sz1
2156 self
.setSizes([sz0
, sz1
])
2157 super(SwitchGraphSplitter
, self
).resizeEvent(ev
)
2159 # Graph widget base class
2161 class GraphWidget(QWidget
):
2163 graph_title_changed
= Signal(object)
2165 def __init__(self
, parent
=None):
2166 super(GraphWidget
, self
).__init
__(parent
)
2168 def GraphTitleChanged(self
, title
):
2169 self
.graph_title_changed
.emit(title
)
2174 # Display time in s, ms, us or ns
2178 if val
>= 1000000000:
2179 return "{} s".format((val
/ 1000000000).quantize(Decimal("0.000000001")))
2181 return "{} ms".format((val
/ 1000000).quantize(Decimal("0.000001")))
2183 return "{} us".format((val
/ 1000).quantize(Decimal("0.001")))
2184 return "{} ns".format(val
.quantize(Decimal("1")))
2186 # Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons
2188 class SwitchGraphWidget(GraphWidget
):
2190 def __init__(self
, glb
, collection
, parent
=None):
2191 super(SwitchGraphWidget
, self
).__init
__(parent
)
2194 self
.collection
= collection
2196 self
.back_state
= []
2197 self
.forward_state
= []
2198 self
.selection_state
= (None, None)
2199 self
.fwd_rect
= None
2200 self
.start_time
= self
.glb
.StartTime(collection
.machine_id
)
2203 hregions
= collection
.hregions
.values()
2204 colours
= GenerateNRandomColours(len(hregions
), 1013)
2205 region_attributes
= {}
2206 for hregion
in hregions
:
2207 if hregion
.pid
== 0 and hregion
.tid
== 0:
2208 region_attributes
[hregion
.key
] = GraphRegionAttribute(QColor(0, 0, 0))
2210 region_attributes
[hregion
.key
] = GraphRegionAttribute(colours
[i
])
2213 # Default to entire range
2214 xsubrange
= Subrange(0.0, float(collection
.xrangehi
- collection
.xrangelo
) + 1.0)
2215 ysubrange
= Subrange(0.0, float(collection
.yrangehi
- collection
.yrangelo
) + 1.0)
2216 subrange
= XY(xsubrange
, ysubrange
)
2218 scale
= self
.GetScaleForRange(subrange
)
2220 self
.attrs
= GraphAttributes(scale
, subrange
, region_attributes
, collection
.dp
)
2222 self
.item
= VertcalGraphSetGraphicsItem(collection
, self
.attrs
, self
, SwitchGraphGraphicsItem
)
2224 self
.scene
= QGraphicsScene()
2225 self
.scene
.addItem(self
.item
)
2227 self
.view
= QGraphicsView(self
.scene
)
2228 self
.view
.centerOn(0, 0)
2229 self
.view
.setAlignment(Qt
.AlignLeft | Qt
.AlignTop
)
2231 self
.legend
= SwitchGraphLegend(collection
, region_attributes
)
2233 self
.splitter
= SwitchGraphSplitter()
2234 self
.splitter
.addWidget(self
.view
)
2235 self
.splitter
.addWidget(self
.legend
)
2237 self
.point_label
= QLabel("")
2238 self
.point_label
.setSizePolicy(QSizePolicy
.Preferred
, QSizePolicy
.Fixed
)
2240 self
.back_button
= QToolButton()
2241 self
.back_button
.setIcon(self
.style().standardIcon(QStyle
.SP_ArrowLeft
))
2242 self
.back_button
.setDisabled(True)
2243 self
.back_button
.released
.connect(lambda: self
.Back())
2245 self
.forward_button
= QToolButton()
2246 self
.forward_button
.setIcon(self
.style().standardIcon(QStyle
.SP_ArrowRight
))
2247 self
.forward_button
.setDisabled(True)
2248 self
.forward_button
.released
.connect(lambda: self
.Forward())
2250 self
.zoom_button
= QToolButton()
2251 self
.zoom_button
.setText("Zoom")
2252 self
.zoom_button
.setDisabled(True)
2253 self
.zoom_button
.released
.connect(lambda: self
.Zoom())
2255 self
.hbox
= HBoxLayout(self
.back_button
, self
.forward_button
, self
.zoom_button
, self
.point_label
)
2257 self
.vbox
= VBoxLayout(self
.splitter
, self
.hbox
)
2259 self
.setLayout(self
.vbox
)
2261 def GetScaleForRangeX(self
, xsubrange
):
2262 # Default graph 1000 pixels wide
2264 r
= xsubrange
.hi
- xsubrange
.lo
2267 def GetScaleForRangeY(self
, ysubrange
):
2268 # Default graph 50 pixels high
2270 r
= ysubrange
.hi
- ysubrange
.lo
2273 def GetScaleForRange(self
, subrange
):
2274 # Default graph 1000 pixels wide, 50 pixels high
2275 xscale
= self
.GetScaleForRangeX(subrange
.x
)
2276 yscale
= self
.GetScaleForRangeY(subrange
.y
)
2277 return XY(xscale
, yscale
)
2279 def PointEvent(self
, cpu
, time_from
, time_to
, hregions
):
2280 text
= "CPU: " + str(cpu
)
2281 time_from
= time_from
.quantize(Decimal(1))
2282 rel_time_from
= time_from
- self
.glb
.StartTime(self
.collection
.machine_id
)
2283 text
= text
+ " Time: " + str(time_from
) + " (+" + ToTimeStr(rel_time_from
) + ")"
2284 self
.point_label
.setText(text
)
2285 self
.legend
.Highlight(hregions
)
2287 def RightClickEvent(self
, cpu
, hregion_times
, pos
):
2288 if not IsSelectable(self
.glb
.db
, "calls", "WHERE parent_id >= 0"):
2290 menu
= QMenu(self
.view
)
2291 for hregion
, time
in hregion_times
:
2292 thread_at_time
= (hregion
.exec_comm_id
, hregion
.thread_id
, time
)
2293 menu_text
= "Show Call Tree for {} {}:{} at {}".format(hregion
.comm
, hregion
.pid
, hregion
.tid
, time
)
2294 menu
.addAction(CreateAction(menu_text
, "Show Call Tree", lambda a
=None, args
=thread_at_time
: self
.RightClickSelect(args
), self
.view
))
2297 def RightClickSelect(self
, args
):
2298 CallTreeWindow(self
.glb
, self
.glb
.mainwindow
, thread_at_time
=args
)
2300 def NoPointEvent(self
):
2301 self
.point_label
.setText("")
2302 self
.legend
.Highlight({})
2304 def RangeEvent(self
, time_from
, time_to
):
2305 time_from
= time_from
.quantize(Decimal(1))
2306 time_to
= time_to
.quantize(Decimal(1))
2307 if time_to
<= time_from
:
2308 self
.point_label
.setText("")
2310 rel_time_from
= time_from
- self
.start_time
2311 rel_time_to
= time_to
- self
.start_time
2312 text
= " Time: " + str(time_from
) + " (+" + ToTimeStr(rel_time_from
) + ") to: " + str(time_to
) + " (+" + ToTimeStr(rel_time_to
) + ")"
2313 text
= text
+ " duration: " + ToTimeStr(time_to
- time_from
)
2314 self
.point_label
.setText(text
)
2316 def BackState(self
):
2317 return (self
.attrs
.subrange
, self
.attrs
.scale
, self
.selection_state
, self
.fwd_rect
)
2319 def PushBackState(self
):
2320 state
= copy
.deepcopy(self
.BackState())
2321 self
.back_state
.append(state
)
2322 self
.back_button
.setEnabled(True)
2324 def PopBackState(self
):
2325 self
.attrs
.subrange
, self
.attrs
.scale
, self
.selection_state
, self
.fwd_rect
= self
.back_state
.pop()
2327 if not self
.back_state
:
2328 self
.back_button
.setDisabled(True)
2330 def PushForwardState(self
):
2331 state
= copy
.deepcopy(self
.BackState())
2332 self
.forward_state
.append(state
)
2333 self
.forward_button
.setEnabled(True)
2335 def PopForwardState(self
):
2336 self
.attrs
.subrange
, self
.attrs
.scale
, self
.selection_state
, self
.fwd_rect
= self
.forward_state
.pop()
2338 if not self
.forward_state
:
2339 self
.forward_button
.setDisabled(True)
2342 time_from
= self
.collection
.xrangelo
+ Decimal(self
.attrs
.subrange
.x
.lo
)
2343 time_to
= self
.collection
.xrangelo
+ Decimal(self
.attrs
.subrange
.x
.hi
)
2344 rel_time_from
= time_from
- self
.start_time
2345 rel_time_to
= time_to
- self
.start_time
2346 title
= "+" + ToTimeStr(rel_time_from
) + " to +" + ToTimeStr(rel_time_to
)
2347 title
= title
+ " (" + ToTimeStr(time_to
- time_from
) + ")"
2351 selected_subrange
, selection_state
= self
.selection_state
2352 self
.item
.SetSelection(selection_state
)
2353 self
.item
.SetBracket(self
.fwd_rect
)
2354 self
.zoom_button
.setDisabled(selected_subrange
is None)
2355 self
.GraphTitleChanged(self
.Title())
2356 self
.item
.update(self
.item
.boundingRect())
2359 if not self
.back_state
:
2361 self
.PushForwardState()
2366 if not self
.forward_state
:
2368 self
.PushBackState()
2369 self
.PopForwardState()
2372 def SelectEvent(self
, x0
, x1
, selection_state
):
2373 if selection_state
is None:
2374 selected_subrange
= None
2378 selected_subrange
= Subrange(x0
, x1
)
2379 self
.selection_state
= (selected_subrange
, selection_state
)
2380 self
.zoom_button
.setDisabled(selected_subrange
is None)
2383 selected_subrange
, selection_state
= self
.selection_state
2384 if selected_subrange
is None:
2386 self
.fwd_rect
= selection_state
2387 self
.item
.SetSelection(None)
2388 self
.PushBackState()
2389 self
.attrs
.subrange
.x
= selected_subrange
2390 self
.forward_state
= []
2391 self
.forward_button
.setDisabled(True)
2392 self
.selection_state
= (None, None)
2393 self
.fwd_rect
= None
2394 self
.attrs
.scale
.x
= self
.GetScaleForRangeX(self
.attrs
.subrange
.x
)
2398 # Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting
2400 class SlowInitClass():
2402 def __init__(self
, glb
, title
, init_fn
):
2403 self
.init_fn
= init_fn
2407 self
.msg_box
= QMessageBox(glb
.mainwindow
)
2408 self
.msg_box
.setText("Initializing " + title
+ ". Please wait.")
2409 self
.msg_box
.setWindowTitle("Initializing " + title
)
2410 self
.msg_box
.setWindowIcon(glb
.mainwindow
.style().standardIcon(QStyle
.SP_MessageBoxInformation
))
2412 self
.init_thread
= Thread(self
.ThreadFn
, glb
)
2413 self
.init_thread
.done
.connect(lambda: self
.Done(), Qt
.QueuedConnection
)
2415 self
.init_thread
.start()
2418 self
.msg_box
.done(0)
2420 def ThreadFn(self
, glb
):
2421 conn_name
= "SlowInitClass" + str(os
.getpid())
2422 db
, dbname
= glb
.dbref
.Open(conn_name
)
2423 self
.result
= self
.init_fn(db
)
2428 while not self
.done
:
2429 self
.msg_box
.exec_()
2430 self
.init_thread
.wait()
2433 def SlowInit(glb
, title
, init_fn
):
2434 init
= SlowInitClass(glb
, title
, init_fn
)
2435 return init
.Result()
2437 # Time chart by CPU window
2439 class TimeChartByCPUWindow(QMdiSubWindow
):
2441 def __init__(self
, glb
, parent
=None):
2442 super(TimeChartByCPUWindow
, self
).__init
__(parent
)
2445 self
.machine_id
= glb
.HostMachineId()
2446 self
.collection_name
= "SwitchGraphDataCollection " + str(self
.machine_id
)
2448 collection
= LookupModel(self
.collection_name
)
2449 if collection
is None:
2450 collection
= SlowInit(glb
, "Time Chart", self
.Init
)
2452 self
.widget
= SwitchGraphWidget(glb
, collection
, self
)
2453 self
.view
= self
.widget
2455 self
.base_title
= "Time Chart by CPU"
2456 self
.setWindowTitle(self
.base_title
+ self
.widget
.Title())
2457 self
.widget
.graph_title_changed
.connect(self
.GraphTitleChanged
)
2459 self
.setWidget(self
.widget
)
2461 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, self
.windowTitle())
2464 return LookupCreateModel(self
.collection_name
, lambda : SwitchGraphDataCollection(self
.glb
, db
, self
.machine_id
))
2466 def GraphTitleChanged(self
, title
):
2467 self
.setWindowTitle(self
.base_title
+ " : " + title
)
2469 # Child data item finder
2471 class ChildDataItemFinder():
2473 def __init__(self
, root
):
2475 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= (None,) * 5
2479 def FindSelect(self
):
2482 pattern
= re
.compile(self
.value
)
2483 for child
in self
.root
.child_items
:
2484 for column_data
in child
.data
:
2485 if re
.search(pattern
, str(column_data
)) is not None:
2486 self
.rows
.append(child
.row
)
2489 for child
in self
.root
.child_items
:
2490 for column_data
in child
.data
:
2491 if self
.value
in str(column_data
):
2492 self
.rows
.append(child
.row
)
2495 def FindValue(self
):
2497 if self
.last_value
!= self
.value
or self
.pattern
!= self
.last_pattern
:
2499 if not len(self
.rows
):
2501 return self
.rows
[self
.pos
]
2503 def FindThread(self
):
2504 if self
.direction
== 0 or self
.value
!= self
.last_value
or self
.pattern
!= self
.last_pattern
:
2505 row
= self
.FindValue()
2506 elif len(self
.rows
):
2507 if self
.direction
> 0:
2509 if self
.pos
>= len(self
.rows
):
2514 self
.pos
= len(self
.rows
) - 1
2515 row
= self
.rows
[self
.pos
]
2520 def Find(self
, value
, direction
, pattern
, context
, callback
):
2521 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= (value
, direction
,pattern
, self
.value
, self
.pattern
)
2522 # Use a thread so the UI is not blocked
2523 thread
= Thread(self
.FindThread
)
2524 thread
.done
.connect(lambda row
, t
=thread
, c
=callback
: self
.FindDone(t
, c
, row
), Qt
.QueuedConnection
)
2527 def FindDone(self
, thread
, callback
, row
):
2530 # Number of database records to fetch in one go
2532 glb_chunk_sz
= 10000
2534 # Background process for SQL data fetcher
2536 class SQLFetcherProcess():
2538 def __init__(self
, dbref
, sql
, buffer, head
, tail
, fetch_count
, fetching_done
, process_target
, wait_event
, fetched_event
, prep
):
2539 # Need a unique connection name
2540 conn_name
= "SQLFetcher" + str(os
.getpid())
2541 self
.db
, dbname
= dbref
.Open(conn_name
)
2543 self
.buffer = buffer
2546 self
.fetch_count
= fetch_count
2547 self
.fetching_done
= fetching_done
2548 self
.process_target
= process_target
2549 self
.wait_event
= wait_event
2550 self
.fetched_event
= fetched_event
2552 self
.query
= QSqlQuery(self
.db
)
2553 self
.query_limit
= 0 if "$$last_id$$" in sql
else 2
2557 self
.local_head
= self
.head
.value
2558 self
.local_tail
= self
.tail
.value
2561 if self
.query_limit
:
2562 if self
.query_limit
== 1:
2564 self
.query_limit
-= 1
2565 stmt
= self
.sql
.replace("$$last_id$$", str(self
.last_id
))
2566 QueryExec(self
.query
, stmt
)
2569 if not self
.query
.next():
2571 if not self
.query
.next():
2573 self
.last_id
= self
.query
.value(0)
2574 return self
.prep(self
.query
)
2576 def WaitForTarget(self
):
2578 self
.wait_event
.clear()
2579 target
= self
.process_target
.value
2580 if target
> self
.fetched
or target
< 0:
2582 self
.wait_event
.wait()
2585 def HasSpace(self
, sz
):
2586 if self
.local_tail
<= self
.local_head
:
2587 space
= len(self
.buffer) - self
.local_head
2590 if space
>= glb_nsz
:
2591 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
2592 nd
= pickle
.dumps(0, pickle
.HIGHEST_PROTOCOL
)
2593 self
.buffer[self
.local_head
: self
.local_head
+ len(nd
)] = nd
2595 if self
.local_tail
- self
.local_head
> sz
:
2599 def WaitForSpace(self
, sz
):
2600 if self
.HasSpace(sz
):
2603 self
.wait_event
.clear()
2604 self
.local_tail
= self
.tail
.value
2605 if self
.HasSpace(sz
):
2607 self
.wait_event
.wait()
2609 def AddToBuffer(self
, obj
):
2610 d
= pickle
.dumps(obj
, pickle
.HIGHEST_PROTOCOL
)
2612 nd
= pickle
.dumps(n
, pickle
.HIGHEST_PROTOCOL
)
2614 self
.WaitForSpace(sz
)
2615 pos
= self
.local_head
2616 self
.buffer[pos
: pos
+ len(nd
)] = nd
2617 self
.buffer[pos
+ glb_nsz
: pos
+ sz
] = d
2618 self
.local_head
+= sz
2620 def FetchBatch(self
, batch_size
):
2622 while batch_size
> fetched
:
2627 self
.AddToBuffer(obj
)
2630 self
.fetched
+= fetched
2631 with self
.fetch_count
.get_lock():
2632 self
.fetch_count
.value
+= fetched
2633 self
.head
.value
= self
.local_head
2634 self
.fetched_event
.set()
2638 target
= self
.WaitForTarget()
2641 batch_size
= min(glb_chunk_sz
, target
- self
.fetched
)
2642 self
.FetchBatch(batch_size
)
2643 self
.fetching_done
.value
= True
2644 self
.fetched_event
.set()
2646 def SQLFetcherFn(*x
):
2647 process
= SQLFetcherProcess(*x
)
2652 class SQLFetcher(QObject
):
2654 done
= Signal(object)
2656 def __init__(self
, glb
, sql
, prep
, process_data
, parent
=None):
2657 super(SQLFetcher
, self
).__init
__(parent
)
2658 self
.process_data
= process_data
2661 self
.last_target
= 0
2663 self
.buffer_size
= 16 * 1024 * 1024
2664 self
.buffer = Array(c_char
, self
.buffer_size
, lock
=False)
2665 self
.head
= Value(c_longlong
)
2666 self
.tail
= Value(c_longlong
)
2668 self
.fetch_count
= Value(c_longlong
)
2669 self
.fetching_done
= Value(c_bool
)
2671 self
.process_target
= Value(c_longlong
)
2672 self
.wait_event
= Event()
2673 self
.fetched_event
= Event()
2674 glb
.AddInstanceToShutdownOnExit(self
)
2675 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
))
2676 self
.process
.start()
2677 self
.thread
= Thread(self
.Thread
)
2678 self
.thread
.done
.connect(self
.ProcessData
, Qt
.QueuedConnection
)
2682 # Tell the thread and process to exit
2683 self
.process_target
.value
= -1
2684 self
.wait_event
.set()
2686 self
.fetching_done
.value
= True
2687 self
.fetched_event
.set()
2693 self
.fetched_event
.clear()
2694 fetch_count
= self
.fetch_count
.value
2695 if fetch_count
!= self
.last_count
:
2697 if self
.fetching_done
.value
:
2700 self
.fetched_event
.wait()
2701 count
= fetch_count
- self
.last_count
2702 self
.last_count
= fetch_count
2703 self
.fetched
+= count
2706 def Fetch(self
, nr
):
2708 # -1 inidcates there are no more
2710 result
= self
.fetched
2711 extra
= result
+ nr
- self
.target
2713 self
.target
+= extra
2714 # process_target < 0 indicates shutting down
2715 if self
.process_target
.value
>= 0:
2716 self
.process_target
.value
= self
.target
2717 self
.wait_event
.set()
2720 def RemoveFromBuffer(self
):
2721 pos
= self
.local_tail
2722 if len(self
.buffer) - pos
< glb_nsz
:
2724 n
= pickle
.loads(self
.buffer[pos
: pos
+ glb_nsz
])
2727 n
= pickle
.loads(self
.buffer[0 : glb_nsz
])
2729 obj
= pickle
.loads(self
.buffer[pos
: pos
+ n
])
2730 self
.local_tail
= pos
+ n
2733 def ProcessData(self
, count
):
2734 for i
in xrange(count
):
2735 obj
= self
.RemoveFromBuffer()
2736 self
.process_data(obj
)
2737 self
.tail
.value
= self
.local_tail
2738 self
.wait_event
.set()
2739 self
.done
.emit(count
)
2741 # Fetch more records bar
2743 class FetchMoreRecordsBar():
2745 def __init__(self
, model
, parent
):
2748 self
.label
= QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz
) + ") to fetch:")
2749 self
.label
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
2751 self
.fetch_count
= QSpinBox()
2752 self
.fetch_count
.setRange(1, 1000000)
2753 self
.fetch_count
.setValue(10)
2754 self
.fetch_count
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
2756 self
.fetch
= QPushButton("Go!")
2757 self
.fetch
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
2758 self
.fetch
.released
.connect(self
.FetchMoreRecords
)
2760 self
.progress
= QProgressBar()
2761 self
.progress
.setRange(0, 100)
2762 self
.progress
.hide()
2764 self
.done_label
= QLabel("All records fetched")
2765 self
.done_label
.hide()
2767 self
.spacer
= QLabel("")
2769 self
.close_button
= QToolButton()
2770 self
.close_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_DockWidgetCloseButton
))
2771 self
.close_button
.released
.connect(self
.Deactivate
)
2773 self
.hbox
= QHBoxLayout()
2774 self
.hbox
.setContentsMargins(0, 0, 0, 0)
2776 self
.hbox
.addWidget(self
.label
)
2777 self
.hbox
.addWidget(self
.fetch_count
)
2778 self
.hbox
.addWidget(self
.fetch
)
2779 self
.hbox
.addWidget(self
.spacer
)
2780 self
.hbox
.addWidget(self
.progress
)
2781 self
.hbox
.addWidget(self
.done_label
)
2782 self
.hbox
.addWidget(self
.close_button
)
2784 self
.bar
= QWidget()
2785 self
.bar
.setLayout(self
.hbox
)
2788 self
.in_progress
= False
2789 self
.model
.progress
.connect(self
.Progress
)
2793 if not model
.HasMoreRecords():
2801 self
.fetch
.setFocus()
2803 def Deactivate(self
):
2806 def Enable(self
, enable
):
2807 self
.fetch
.setEnabled(enable
)
2808 self
.fetch_count
.setEnabled(enable
)
2814 self
.progress
.show()
2817 self
.in_progress
= False
2819 self
.progress
.hide()
2824 return self
.fetch_count
.value() * glb_chunk_sz
2830 self
.fetch_count
.hide()
2833 self
.done_label
.show()
2835 def Progress(self
, count
):
2836 if self
.in_progress
:
2838 percent
= ((count
- self
.start
) * 100) / self
.Target()
2842 self
.progress
.setValue(percent
)
2844 # Count value of zero means no more records
2847 def FetchMoreRecords(self
):
2850 self
.progress
.setValue(0)
2852 self
.in_progress
= True
2853 self
.start
= self
.model
.FetchMoreRecords(self
.Target())
2855 # Brance data model level two item
2857 class BranchLevelTwoItem():
2859 def __init__(self
, row
, col
, text
, parent_item
):
2861 self
.parent_item
= parent_item
2862 self
.data
= [""] * (col
+ 1)
2863 self
.data
[col
] = text
2866 def getParentItem(self
):
2867 return self
.parent_item
2872 def childCount(self
):
2875 def hasChildren(self
):
2878 def getData(self
, column
):
2879 return self
.data
[column
]
2881 # Brance data model level one item
2883 class BranchLevelOneItem():
2885 def __init__(self
, glb
, row
, data
, parent_item
):
2888 self
.parent_item
= parent_item
2889 self
.child_count
= 0
2890 self
.child_items
= []
2891 self
.data
= data
[1:]
2894 self
.query_done
= False
2895 self
.br_col
= len(self
.data
) - 1
2897 def getChildItem(self
, row
):
2898 return self
.child_items
[row
]
2900 def getParentItem(self
):
2901 return self
.parent_item
2907 self
.query_done
= True
2909 if not self
.glb
.have_disassembler
:
2912 query
= QSqlQuery(self
.glb
.db
)
2914 QueryExec(query
, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
2916 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
2917 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
2918 " WHERE samples.id = " + str(self
.dbid
))
2919 if not query
.next():
2921 cpu
= query
.value(0)
2922 dso
= query
.value(1)
2923 sym
= query
.value(2)
2924 if dso
== 0 or sym
== 0:
2926 off
= query
.value(3)
2927 short_name
= query
.value(4)
2928 long_name
= query
.value(5)
2929 build_id
= query
.value(6)
2930 sym_start
= query
.value(7)
2933 QueryExec(query
, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
2935 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
2936 " WHERE samples.id > " + str(self
.dbid
) + " AND cpu = " + str(cpu
) +
2937 " ORDER BY samples.id"
2939 if not query
.next():
2941 if query
.value(0) != dso
:
2942 # Cannot disassemble from one dso to another
2944 bsym
= query
.value(1)
2945 boff
= query
.value(2)
2946 bsym_start
= query
.value(3)
2949 tot
= bsym_start
+ boff
+ 1 - sym_start
- off
2950 if tot
<= 0 or tot
> 16384:
2953 inst
= self
.glb
.disassembler
.Instruction()
2954 f
= self
.glb
.FileFromNamesAndBuildId(short_name
, long_name
, build_id
)
2957 mode
= 0 if Is64Bit(f
) else 1
2958 self
.glb
.disassembler
.SetMode(inst
, mode
)
2961 buf
= create_string_buffer(tot
+ 16)
2962 f
.seek(sym_start
+ off
)
2963 buf
.value
= f
.read(buf_sz
)
2964 buf_ptr
= addressof(buf
)
2967 cnt
, text
= self
.glb
.disassembler
.DisassembleOne(inst
, buf_ptr
, buf_sz
, ip
)
2969 byte_str
= tohex(ip
).rjust(16)
2970 for k
in xrange(cnt
):
2971 byte_str
+= " %02x" % ord(buf
[i
])
2976 self
.child_items
.append(BranchLevelTwoItem(0, self
.br_col
, byte_str
+ " " + text
, self
))
2977 self
.child_count
+= 1
2985 def childCount(self
):
2986 if not self
.query_done
:
2988 if not self
.child_count
:
2990 return self
.child_count
2992 def hasChildren(self
):
2993 if not self
.query_done
:
2995 return self
.child_count
> 0
2997 def getData(self
, column
):
2998 return self
.data
[column
]
3000 # Brance data model root item
3002 class BranchRootItem():
3005 self
.child_count
= 0
3006 self
.child_items
= []
3009 def getChildItem(self
, row
):
3010 return self
.child_items
[row
]
3012 def getParentItem(self
):
3018 def childCount(self
):
3019 return self
.child_count
3021 def hasChildren(self
):
3022 return self
.child_count
> 0
3024 def getData(self
, column
):
3027 # Calculate instructions per cycle
3029 def CalcIPC(cyc_cnt
, insn_cnt
):
3030 if cyc_cnt
and insn_cnt
:
3031 ipc
= Decimal(float(insn_cnt
) / cyc_cnt
)
3032 ipc
= str(ipc
.quantize(Decimal(".01"), rounding
=ROUND_HALF_UP
))
3037 # Branch data preparation
3039 def BranchDataPrepBr(query
, data
):
3040 data
.append(tohex(query
.value(8)).rjust(16) + " " + query
.value(9) + offstr(query
.value(10)) +
3041 " (" + dsoname(query
.value(11)) + ")" + " -> " +
3042 tohex(query
.value(12)) + " " + query
.value(13) + offstr(query
.value(14)) +
3043 " (" + dsoname(query
.value(15)) + ")")
3045 def BranchDataPrepIPC(query
, data
):
3046 insn_cnt
= query
.value(16)
3047 cyc_cnt
= query
.value(17)
3048 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
3049 data
.append(insn_cnt
)
3050 data
.append(cyc_cnt
)
3053 def BranchDataPrep(query
):
3055 for i
in xrange(0, 8):
3056 data
.append(query
.value(i
))
3057 BranchDataPrepBr(query
, data
)
3060 def BranchDataPrepWA(query
):
3062 data
.append(query
.value(0))
3063 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3064 data
.append("{:>19}".format(query
.value(1)))
3065 for i
in xrange(2, 8):
3066 data
.append(query
.value(i
))
3067 BranchDataPrepBr(query
, data
)
3070 def BranchDataWithIPCPrep(query
):
3072 for i
in xrange(0, 8):
3073 data
.append(query
.value(i
))
3074 BranchDataPrepIPC(query
, data
)
3075 BranchDataPrepBr(query
, data
)
3078 def BranchDataWithIPCPrepWA(query
):
3080 data
.append(query
.value(0))
3081 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3082 data
.append("{:>19}".format(query
.value(1)))
3083 for i
in xrange(2, 8):
3084 data
.append(query
.value(i
))
3085 BranchDataPrepIPC(query
, data
)
3086 BranchDataPrepBr(query
, data
)
3091 class BranchModel(TreeModel
):
3093 progress
= Signal(object)
3095 def __init__(self
, glb
, event_id
, where_clause
, parent
=None):
3096 super(BranchModel
, self
).__init
__(glb
, None, parent
)
3097 self
.event_id
= event_id
3100 self
.have_ipc
= IsSelectable(glb
.db
, "samples", columns
= "insn_count, cyc_count")
3102 select_ipc
= ", insn_count, cyc_count"
3103 prep_fn
= BranchDataWithIPCPrep
3104 prep_wa_fn
= BranchDataWithIPCPrepWA
3107 prep_fn
= BranchDataPrep
3108 prep_wa_fn
= BranchDataPrepWA
3109 sql
= ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
3110 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
3111 " ip, symbols.name, sym_offset, dsos.short_name,"
3112 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
3115 " INNER JOIN comms ON comm_id = comms.id"
3116 " INNER JOIN threads ON thread_id = threads.id"
3117 " INNER JOIN branch_types ON branch_type = branch_types.id"
3118 " INNER JOIN symbols ON symbol_id = symbols.id"
3119 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
3120 " INNER JOIN dsos ON samples.dso_id = dsos.id"
3121 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
3122 " WHERE samples.id > $$last_id$$" + where_clause
+
3123 " AND evsel_id = " + str(self
.event_id
) +
3124 " ORDER BY samples.id"
3125 " LIMIT " + str(glb_chunk_sz
))
3126 if pyside_version_1
and sys
.version_info
[0] == 3:
3130 self
.fetcher
= SQLFetcher(glb
, sql
, prep
, self
.AddSample
)
3131 self
.fetcher
.done
.connect(self
.Update
)
3132 self
.fetcher
.Fetch(glb_chunk_sz
)
3135 return BranchRootItem()
3137 def columnCount(self
, parent
=None):
3143 def columnHeader(self
, column
):
3145 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column
]
3147 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column
]
3149 def columnFont(self
, column
):
3154 if column
!= br_col
:
3156 return QFont("Monospace")
3158 def DisplayData(self
, item
, index
):
3160 self
.FetchIfNeeded(item
.row
)
3161 return item
.getData(index
.column())
3163 def AddSample(self
, data
):
3164 child
= BranchLevelOneItem(self
.glb
, self
.populated
, data
, self
.root
)
3165 self
.root
.child_items
.append(child
)
3168 def Update(self
, fetched
):
3171 self
.progress
.emit(0)
3172 child_count
= self
.root
.child_count
3173 count
= self
.populated
- child_count
3175 parent
= QModelIndex()
3176 self
.beginInsertRows(parent
, child_count
, child_count
+ count
- 1)
3177 self
.insertRows(child_count
, count
, parent
)
3178 self
.root
.child_count
+= count
3179 self
.endInsertRows()
3180 self
.progress
.emit(self
.root
.child_count
)
3182 def FetchMoreRecords(self
, count
):
3183 current
= self
.root
.child_count
3185 self
.fetcher
.Fetch(count
)
3187 self
.progress
.emit(0)
3190 def HasMoreRecords(self
):
3197 def __init__(self
, name
= "", where_clause
= "", limit
= ""):
3199 self
.where_clause
= where_clause
3203 return str(self
.where_clause
+ ";" + self
.limit
)
3207 class BranchWindow(QMdiSubWindow
):
3209 def __init__(self
, glb
, event_id
, report_vars
, parent
=None):
3210 super(BranchWindow
, self
).__init
__(parent
)
3212 model_name
= "Branch Events " + str(event_id
) + " " + report_vars
.UniqueId()
3214 self
.model
= LookupCreateModel(model_name
, lambda: BranchModel(glb
, event_id
, report_vars
.where_clause
))
3216 self
.view
= QTreeView()
3217 self
.view
.setUniformRowHeights(True)
3218 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
3219 self
.view
.CopyCellsToClipboard
= CopyTreeCellsToClipboard
3220 self
.view
.setModel(self
.model
)
3222 self
.ResizeColumnsToContents()
3224 self
.context_menu
= TreeContextMenu(self
.view
)
3226 self
.find_bar
= FindBar(self
, self
, True)
3228 self
.finder
= ChildDataItemFinder(self
.model
.root
)
3230 self
.fetch_bar
= FetchMoreRecordsBar(self
.model
, self
)
3232 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
3234 self
.setWidget(self
.vbox
.Widget())
3236 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, report_vars
.name
+ " Branch Events")
3238 def ResizeColumnToContents(self
, column
, n
):
3239 # Using the view's resizeColumnToContents() here is extrememly slow
3240 # so implement a crude alternative
3241 mm
= "MM" if column
else "MMMM"
3242 font
= self
.view
.font()
3243 metrics
= QFontMetrics(font
)
3245 for row
in xrange(n
):
3246 val
= self
.model
.root
.child_items
[row
].data
[column
]
3247 len = metrics
.width(str(val
) + mm
)
3248 max = len if len > max else max
3249 val
= self
.model
.columnHeader(column
)
3250 len = metrics
.width(str(val
) + mm
)
3251 max = len if len > max else max
3252 self
.view
.setColumnWidth(column
, max)
3254 def ResizeColumnsToContents(self
):
3255 n
= min(self
.model
.root
.child_count
, 100)
3257 # No data yet, so connect a signal to notify when there is
3258 self
.model
.rowsInserted
.connect(self
.UpdateColumnWidths
)
3260 columns
= self
.model
.columnCount()
3261 for i
in xrange(columns
):
3262 self
.ResizeColumnToContents(i
, n
)
3264 def UpdateColumnWidths(self
, *x
):
3265 # This only needs to be done once, so disconnect the signal now
3266 self
.model
.rowsInserted
.disconnect(self
.UpdateColumnWidths
)
3267 self
.ResizeColumnsToContents()
3269 def Find(self
, value
, direction
, pattern
, context
):
3270 self
.view
.setFocus()
3271 self
.find_bar
.Busy()
3272 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
3274 def FindDone(self
, row
):
3275 self
.find_bar
.Idle()
3277 self
.view
.setCurrentIndex(self
.model
.index(row
, 0, QModelIndex()))
3279 self
.find_bar
.NotFound()
3281 # Line edit data item
3283 class LineEditDataItem(object):
3285 def __init__(self
, glb
, label
, placeholder_text
, parent
, id = "", default
= ""):
3288 self
.placeholder_text
= placeholder_text
3289 self
.parent
= parent
3292 self
.value
= default
3294 self
.widget
= QLineEdit(default
)
3295 self
.widget
.editingFinished
.connect(self
.Validate
)
3296 self
.widget
.textChanged
.connect(self
.Invalidate
)
3299 self
.validated
= True
3301 if placeholder_text
:
3302 self
.widget
.setPlaceholderText(placeholder_text
)
3304 def TurnTextRed(self
):
3306 palette
= QPalette()
3307 palette
.setColor(QPalette
.Text
,Qt
.red
)
3308 self
.widget
.setPalette(palette
)
3311 def TurnTextNormal(self
):
3313 palette
= QPalette()
3314 self
.widget
.setPalette(palette
)
3317 def InvalidValue(self
, value
):
3320 self
.error
= self
.label
+ " invalid value '" + value
+ "'"
3321 self
.parent
.ShowMessage(self
.error
)
3323 def Invalidate(self
):
3324 self
.validated
= False
3326 def DoValidate(self
, input_string
):
3327 self
.value
= input_string
.strip()
3330 self
.validated
= True
3332 self
.TurnTextNormal()
3333 self
.parent
.ClearMessage()
3334 input_string
= self
.widget
.text()
3335 if not len(input_string
.strip()):
3338 self
.DoValidate(input_string
)
3341 if not self
.validated
:
3344 self
.parent
.ShowMessage(self
.error
)
3348 def IsNumber(self
, value
):
3353 return str(x
) == value
3355 # Non-negative integer ranges dialog data item
3357 class NonNegativeIntegerRangesDataItem(LineEditDataItem
):
3359 def __init__(self
, glb
, label
, placeholder_text
, column_name
, parent
):
3360 super(NonNegativeIntegerRangesDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
3362 self
.column_name
= column_name
3364 def DoValidate(self
, input_string
):
3367 for value
in [x
.strip() for x
in input_string
.split(",")]:
3369 vrange
= value
.split("-")
3370 if len(vrange
) != 2 or not self
.IsNumber(vrange
[0]) or not self
.IsNumber(vrange
[1]):
3371 return self
.InvalidValue(value
)
3372 ranges
.append(vrange
)
3374 if not self
.IsNumber(value
):
3375 return self
.InvalidValue(value
)
3376 singles
.append(value
)
3377 ranges
= [("(" + self
.column_name
+ " >= " + r
[0] + " AND " + self
.column_name
+ " <= " + r
[1] + ")") for r
in ranges
]
3379 ranges
.append(self
.column_name
+ " IN (" + ",".join(singles
) + ")")
3380 self
.value
= " OR ".join(ranges
)
3382 # Positive integer dialog data item
3384 class PositiveIntegerDataItem(LineEditDataItem
):
3386 def __init__(self
, glb
, label
, placeholder_text
, parent
, id = "", default
= ""):
3387 super(PositiveIntegerDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
, id, default
)
3389 def DoValidate(self
, input_string
):
3390 if not self
.IsNumber(input_string
.strip()):
3391 return self
.InvalidValue(input_string
)
3392 value
= int(input_string
.strip())
3394 return self
.InvalidValue(input_string
)
3395 self
.value
= str(value
)
3397 # Dialog data item converted and validated using a SQL table
3399 class SQLTableDataItem(LineEditDataItem
):
3401 def __init__(self
, glb
, label
, placeholder_text
, table_name
, match_column
, column_name1
, column_name2
, parent
):
3402 super(SQLTableDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
3404 self
.table_name
= table_name
3405 self
.match_column
= match_column
3406 self
.column_name1
= column_name1
3407 self
.column_name2
= column_name2
3409 def ValueToIds(self
, value
):
3411 query
= QSqlQuery(self
.glb
.db
)
3412 stmt
= "SELECT id FROM " + self
.table_name
+ " WHERE " + self
.match_column
+ " = '" + value
+ "'"
3413 ret
= query
.exec_(stmt
)
3416 ids
.append(str(query
.value(0)))
3419 def DoValidate(self
, input_string
):
3421 for value
in [x
.strip() for x
in input_string
.split(",")]:
3422 ids
= self
.ValueToIds(value
)
3426 return self
.InvalidValue(value
)
3427 self
.value
= self
.column_name1
+ " IN (" + ",".join(all_ids
) + ")"
3428 if self
.column_name2
:
3429 self
.value
= "( " + self
.value
+ " OR " + self
.column_name2
+ " IN (" + ",".join(all_ids
) + ") )"
3431 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
3433 class SampleTimeRangesDataItem(LineEditDataItem
):
3435 def __init__(self
, glb
, label
, placeholder_text
, column_name
, parent
):
3436 self
.column_name
= column_name
3440 self
.last_time
= 2 ** 64
3442 query
= QSqlQuery(glb
.db
)
3443 QueryExec(query
, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
3445 self
.last_id
= int(query
.value(0))
3446 self
.first_time
= int(glb
.HostStartTime())
3447 self
.last_time
= int(glb
.HostFinishTime())
3448 if placeholder_text
:
3449 placeholder_text
+= ", between " + str(self
.first_time
) + " and " + str(self
.last_time
)
3451 super(SampleTimeRangesDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
3453 def IdBetween(self
, query
, lower_id
, higher_id
, order
):
3454 QueryExec(query
, "SELECT id FROM samples WHERE id > " + str(lower_id
) + " AND id < " + str(higher_id
) + " ORDER BY id " + order
+ " LIMIT 1")
3456 return True, int(query
.value(0))
3460 def BinarySearchTime(self
, lower_id
, higher_id
, target_time
, get_floor
):
3461 query
= QSqlQuery(self
.glb
.db
)
3463 next_id
= int((lower_id
+ higher_id
) / 2)
3464 QueryExec(query
, "SELECT time FROM samples WHERE id = " + str(next_id
))
3465 if not query
.next():
3466 ok
, dbid
= self
.IdBetween(query
, lower_id
, next_id
, "DESC")
3468 ok
, dbid
= self
.IdBetween(query
, next_id
, higher_id
, "")
3470 return str(higher_id
)
3472 QueryExec(query
, "SELECT time FROM samples WHERE id = " + str(next_id
))
3473 next_time
= int(query
.value(0))
3475 if target_time
> next_time
:
3479 if higher_id
<= lower_id
+ 1:
3480 return str(higher_id
)
3482 if target_time
>= next_time
:
3486 if higher_id
<= lower_id
+ 1:
3487 return str(lower_id
)
3489 def ConvertRelativeTime(self
, val
):
3494 elif suffix
== "us":
3496 elif suffix
== "ns":
3500 val
= val
[:-2].strip()
3501 if not self
.IsNumber(val
):
3503 val
= int(val
) * mult
3505 val
+= self
.first_time
3507 val
+= self
.last_time
3510 def ConvertTimeRange(self
, vrange
):
3512 vrange
[0] = str(self
.first_time
)
3514 vrange
[1] = str(self
.last_time
)
3515 vrange
[0] = self
.ConvertRelativeTime(vrange
[0])
3516 vrange
[1] = self
.ConvertRelativeTime(vrange
[1])
3517 if not self
.IsNumber(vrange
[0]) or not self
.IsNumber(vrange
[1]):
3519 beg_range
= max(int(vrange
[0]), self
.first_time
)
3520 end_range
= min(int(vrange
[1]), self
.last_time
)
3521 if beg_range
> self
.last_time
or end_range
< self
.first_time
:
3523 vrange
[0] = self
.BinarySearchTime(0, self
.last_id
, beg_range
, True)
3524 vrange
[1] = self
.BinarySearchTime(1, self
.last_id
+ 1, end_range
, False)
3527 def AddTimeRange(self
, value
, ranges
):
3528 n
= value
.count("-")
3532 if value
.split("-")[1].strip() == "":
3538 pos
= findnth(value
, "-", n
)
3539 vrange
= [value
[:pos
].strip() ,value
[pos
+1:].strip()]
3540 if self
.ConvertTimeRange(vrange
):
3541 ranges
.append(vrange
)
3545 def DoValidate(self
, input_string
):
3547 for value
in [x
.strip() for x
in input_string
.split(",")]:
3548 if not self
.AddTimeRange(value
, ranges
):
3549 return self
.InvalidValue(value
)
3550 ranges
= [("(" + self
.column_name
+ " >= " + r
[0] + " AND " + self
.column_name
+ " <= " + r
[1] + ")") for r
in ranges
]
3551 self
.value
= " OR ".join(ranges
)
3553 # Report Dialog Base
3555 class ReportDialogBase(QDialog
):
3557 def __init__(self
, glb
, title
, items
, partial
, parent
=None):
3558 super(ReportDialogBase
, self
).__init
__(parent
)
3562 self
.report_vars
= ReportVars()
3564 self
.setWindowTitle(title
)
3565 self
.setMinimumWidth(600)
3567 self
.data_items
= [x(glb
, self
) for x
in items
]
3569 self
.partial
= partial
3571 self
.grid
= QGridLayout()
3573 for row
in xrange(len(self
.data_items
)):
3574 self
.grid
.addWidget(QLabel(self
.data_items
[row
].label
), row
, 0)
3575 self
.grid
.addWidget(self
.data_items
[row
].widget
, row
, 1)
3577 self
.status
= QLabel()
3579 self
.ok_button
= QPushButton("Ok", self
)
3580 self
.ok_button
.setDefault(True)
3581 self
.ok_button
.released
.connect(self
.Ok
)
3582 self
.ok_button
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
3584 self
.cancel_button
= QPushButton("Cancel", self
)
3585 self
.cancel_button
.released
.connect(self
.reject
)
3586 self
.cancel_button
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
3588 self
.hbox
= QHBoxLayout()
3589 #self.hbox.addStretch()
3590 self
.hbox
.addWidget(self
.status
)
3591 self
.hbox
.addWidget(self
.ok_button
)
3592 self
.hbox
.addWidget(self
.cancel_button
)
3594 self
.vbox
= QVBoxLayout()
3595 self
.vbox
.addLayout(self
.grid
)
3596 self
.vbox
.addLayout(self
.hbox
)
3598 self
.setLayout(self
.vbox
)
3601 vars = self
.report_vars
3602 for d
in self
.data_items
:
3603 if d
.id == "REPORTNAME":
3606 self
.ShowMessage("Report name is required")
3608 for d
in self
.data_items
:
3611 for d
in self
.data_items
[1:]:
3613 vars.limit
= d
.value
3615 if len(vars.where_clause
):
3616 vars.where_clause
+= " AND "
3617 vars.where_clause
+= d
.value
3618 if len(vars.where_clause
):
3620 vars.where_clause
= " AND ( " + vars.where_clause
+ " ) "
3622 vars.where_clause
= " WHERE " + vars.where_clause
+ " "
3625 def ShowMessage(self
, msg
):
3626 self
.status
.setText("<font color=#FF0000>" + msg
)
3628 def ClearMessage(self
):
3629 self
.status
.setText("")
3631 # Selected branch report creation dialog
3633 class SelectedBranchDialog(ReportDialogBase
):
3635 def __init__(self
, glb
, parent
=None):
3636 title
= "Selected Branches"
3637 items
= (lambda g
, p
: LineEditDataItem(g
, "Report name:", "Enter a name to appear in the window title bar", p
, "REPORTNAME"),
3638 lambda g
, p
: SampleTimeRangesDataItem(g
, "Time ranges:", "Enter time ranges", "samples.id", p
),
3639 lambda g
, p
: NonNegativeIntegerRangesDataItem(g
, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p
),
3640 lambda g
, p
: SQLTableDataItem(g
, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p
),
3641 lambda g
, p
: SQLTableDataItem(g
, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p
),
3642 lambda g
, p
: SQLTableDataItem(g
, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p
),
3643 lambda g
, p
: SQLTableDataItem(g
, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p
),
3644 lambda g
, p
: SQLTableDataItem(g
, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p
),
3645 lambda g
, p
: LineEditDataItem(g
, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p
))
3646 super(SelectedBranchDialog
, self
).__init
__(glb
, title
, items
, True, parent
)
3650 def GetEventList(db
):
3652 query
= QSqlQuery(db
)
3653 QueryExec(query
, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
3655 events
.append(query
.value(0))
3658 # Is a table selectable
3660 def IsSelectable(db
, table
, sql
= "", columns
= "*"):
3661 query
= QSqlQuery(db
)
3663 QueryExec(query
, "SELECT " + columns
+ " FROM " + table
+ " " + sql
+ " LIMIT 1")
3668 # SQL table data model item
3670 class SQLTableItem():
3672 def __init__(self
, row
, data
):
3676 def getData(self
, column
):
3677 return self
.data
[column
]
3679 # SQL table data model
3681 class SQLTableModel(TableModel
):
3683 progress
= Signal(object)
3685 def __init__(self
, glb
, sql
, column_headers
, parent
=None):
3686 super(SQLTableModel
, self
).__init
__(parent
)
3690 self
.column_headers
= column_headers
3691 self
.fetcher
= SQLFetcher(glb
, sql
, lambda x
, y
=len(column_headers
): self
.SQLTableDataPrep(x
, y
), self
.AddSample
)
3692 self
.fetcher
.done
.connect(self
.Update
)
3693 self
.fetcher
.Fetch(glb_chunk_sz
)
3695 def DisplayData(self
, item
, index
):
3696 self
.FetchIfNeeded(item
.row
)
3697 return item
.getData(index
.column())
3699 def AddSample(self
, data
):
3700 child
= SQLTableItem(self
.populated
, data
)
3701 self
.child_items
.append(child
)
3704 def Update(self
, fetched
):
3707 self
.progress
.emit(0)
3708 child_count
= self
.child_count
3709 count
= self
.populated
- child_count
3711 parent
= QModelIndex()
3712 self
.beginInsertRows(parent
, child_count
, child_count
+ count
- 1)
3713 self
.insertRows(child_count
, count
, parent
)
3714 self
.child_count
+= count
3715 self
.endInsertRows()
3716 self
.progress
.emit(self
.child_count
)
3718 def FetchMoreRecords(self
, count
):
3719 current
= self
.child_count
3721 self
.fetcher
.Fetch(count
)
3723 self
.progress
.emit(0)
3726 def HasMoreRecords(self
):
3729 def columnCount(self
, parent
=None):
3730 return len(self
.column_headers
)
3732 def columnHeader(self
, column
):
3733 return self
.column_headers
[column
]
3735 def SQLTableDataPrep(self
, query
, count
):
3737 for i
in xrange(count
):
3738 data
.append(query
.value(i
))
3741 # SQL automatic table data model
3743 class SQLAutoTableModel(SQLTableModel
):
3745 def __init__(self
, glb
, table_name
, parent
=None):
3746 sql
= "SELECT * FROM " + table_name
+ " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz
)
3747 if table_name
== "comm_threads_view":
3748 # For now, comm_threads_view has no id column
3749 sql
= "SELECT * FROM " + table_name
+ " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz
)
3751 query
= QSqlQuery(glb
.db
)
3752 if glb
.dbref
.is_sqlite3
:
3753 QueryExec(query
, "PRAGMA table_info(" + table_name
+ ")")
3755 column_headers
.append(query
.value(1))
3756 if table_name
== "sqlite_master":
3757 sql
= "SELECT * FROM " + table_name
3759 if table_name
[:19] == "information_schema.":
3760 sql
= "SELECT * FROM " + table_name
3761 select_table_name
= table_name
[19:]
3762 schema
= "information_schema"
3764 select_table_name
= table_name
3766 QueryExec(query
, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema
+ "' and table_name = '" + select_table_name
+ "'")
3768 column_headers
.append(query
.value(0))
3769 if pyside_version_1
and sys
.version_info
[0] == 3:
3770 if table_name
== "samples_view":
3771 self
.SQLTableDataPrep
= self
.samples_view_DataPrep
3772 if table_name
== "samples":
3773 self
.SQLTableDataPrep
= self
.samples_DataPrep
3774 super(SQLAutoTableModel
, self
).__init
__(glb
, sql
, column_headers
, parent
)
3776 def samples_view_DataPrep(self
, query
, count
):
3778 data
.append(query
.value(0))
3779 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3780 data
.append("{:>19}".format(query
.value(1)))
3781 for i
in xrange(2, count
):
3782 data
.append(query
.value(i
))
3785 def samples_DataPrep(self
, query
, count
):
3788 data
.append(query
.value(i
))
3789 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3790 data
.append("{:>19}".format(query
.value(9)))
3791 for i
in xrange(10, count
):
3792 data
.append(query
.value(i
))
3795 # Base class for custom ResizeColumnsToContents
3797 class ResizeColumnsToContentsBase(QObject
):
3799 def __init__(self
, parent
=None):
3800 super(ResizeColumnsToContentsBase
, self
).__init
__(parent
)
3802 def ResizeColumnToContents(self
, column
, n
):
3803 # Using the view's resizeColumnToContents() here is extrememly slow
3804 # so implement a crude alternative
3805 font
= self
.view
.font()
3806 metrics
= QFontMetrics(font
)
3808 for row
in xrange(n
):
3809 val
= self
.data_model
.child_items
[row
].data
[column
]
3810 len = metrics
.width(str(val
) + "MM")
3811 max = len if len > max else max
3812 val
= self
.data_model
.columnHeader(column
)
3813 len = metrics
.width(str(val
) + "MM")
3814 max = len if len > max else max
3815 self
.view
.setColumnWidth(column
, max)
3817 def ResizeColumnsToContents(self
):
3818 n
= min(self
.data_model
.child_count
, 100)
3820 # No data yet, so connect a signal to notify when there is
3821 self
.data_model
.rowsInserted
.connect(self
.UpdateColumnWidths
)
3823 columns
= self
.data_model
.columnCount()
3824 for i
in xrange(columns
):
3825 self
.ResizeColumnToContents(i
, n
)
3827 def UpdateColumnWidths(self
, *x
):
3828 # This only needs to be done once, so disconnect the signal now
3829 self
.data_model
.rowsInserted
.disconnect(self
.UpdateColumnWidths
)
3830 self
.ResizeColumnsToContents()
3832 # Convert value to CSV
3836 val
= val
.replace('"', '""')
3837 if "," in val
or '"' in val
:
3838 val
= '"' + val
+ '"'
3841 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
3845 def RowColumnKey(a
):
3846 return a
.row() * glb_max_cols
+ a
.column()
3848 # Copy selected table cells to clipboard
3850 def CopyTableCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
3851 indexes
= sorted(view
.selectedIndexes(), key
=RowColumnKey
)
3852 idx_cnt
= len(indexes
)
3857 min_row
= indexes
[0].row()
3858 max_row
= indexes
[0].row()
3859 min_col
= indexes
[0].column()
3860 max_col
= indexes
[0].column()
3862 min_row
= min(min_row
, i
.row())
3863 max_row
= max(max_row
, i
.row())
3864 min_col
= min(min_col
, i
.column())
3865 max_col
= max(max_col
, i
.column())
3866 if max_col
> glb_max_cols
:
3867 raise RuntimeError("glb_max_cols is too low")
3868 max_width
= [0] * (1 + max_col
- min_col
)
3870 c
= i
.column() - min_col
3871 max_width
[c
] = max(max_width
[c
], len(str(i
.data())))
3876 model
= indexes
[0].model()
3877 for col
in range(min_col
, max_col
+ 1):
3878 val
= model
.headerData(col
, Qt
.Horizontal
, Qt
.DisplayRole
)
3880 text
+= sep
+ ToCSValue(val
)
3884 max_width
[c
] = max(max_width
[c
], len(val
))
3885 width
= max_width
[c
]
3886 align
= model
.headerData(col
, Qt
.Horizontal
, Qt
.TextAlignmentRole
)
3887 if align
& Qt
.AlignRight
:
3888 val
= val
.rjust(width
)
3889 text
+= pad
+ sep
+ val
3890 pad
= " " * (width
- len(val
))
3897 if i
.row() > last_row
:
3903 text
+= sep
+ ToCSValue(str(i
.data()))
3906 width
= max_width
[i
.column() - min_col
]
3907 if i
.data(Qt
.TextAlignmentRole
) & Qt
.AlignRight
:
3908 val
= str(i
.data()).rjust(width
)
3911 text
+= pad
+ sep
+ val
3912 pad
= " " * (width
- len(val
))
3914 QApplication
.clipboard().setText(text
)
3916 def CopyTreeCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
3917 indexes
= view
.selectedIndexes()
3918 if not len(indexes
):
3921 selection
= view
.selectionModel()
3925 above
= view
.indexAbove(i
)
3926 if not selection
.isSelected(above
):
3931 raise RuntimeError("CopyTreeCellsToClipboard internal error")
3933 model
= first
.model()
3935 col_cnt
= model
.columnCount(first
)
3936 max_width
= [0] * col_cnt
3939 indent_str
= " " * indent_sz
3941 expanded_mark_sz
= 2
3942 if sys
.version_info
[0] == 3:
3943 expanded_mark
= "\u25BC "
3944 not_expanded_mark
= "\u25B6 "
3946 expanded_mark
= unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
3947 not_expanded_mark
= unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
3955 for c
in range(col_cnt
):
3956 i
= pos
.sibling(row
, c
)
3958 n
= len(str(i
.data()))
3960 n
= len(str(i
.data()).strip())
3961 n
+= (i
.internalPointer().level
- 1) * indent_sz
3962 n
+= expanded_mark_sz
3963 max_width
[c
] = max(max_width
[c
], n
)
3964 pos
= view
.indexBelow(pos
)
3965 if not selection
.isSelected(pos
):
3972 for c
in range(col_cnt
):
3973 val
= model
.headerData(c
, Qt
.Horizontal
, Qt
.DisplayRole
).strip()
3975 text
+= sep
+ ToCSValue(val
)
3978 max_width
[c
] = max(max_width
[c
], len(val
))
3979 width
= max_width
[c
]
3980 align
= model
.headerData(c
, Qt
.Horizontal
, Qt
.TextAlignmentRole
)
3981 if align
& Qt
.AlignRight
:
3982 val
= val
.rjust(width
)
3983 text
+= pad
+ sep
+ val
3984 pad
= " " * (width
- len(val
))
3993 for c
in range(col_cnt
):
3994 i
= pos
.sibling(row
, c
)
3997 if model
.hasChildren(i
):
3998 if view
.isExpanded(i
):
3999 mark
= expanded_mark
4001 mark
= not_expanded_mark
4004 val
= indent_str
* (i
.internalPointer().level
- 1) + mark
+ val
.strip()
4006 text
+= sep
+ ToCSValue(val
)
4009 width
= max_width
[c
]
4010 if c
and i
.data(Qt
.TextAlignmentRole
) & Qt
.AlignRight
:
4011 val
= val
.rjust(width
)
4012 text
+= pad
+ sep
+ val
4013 pad
= " " * (width
- len(val
))
4015 pos
= view
.indexBelow(pos
)
4016 if not selection
.isSelected(pos
):
4018 text
= text
.rstrip() + "\n"
4022 QApplication
.clipboard().setText(text
)
4024 def CopyCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
4025 view
.CopyCellsToClipboard(view
, as_csv
, with_hdr
)
4027 def CopyCellsToClipboardHdr(view
):
4028 CopyCellsToClipboard(view
, False, True)
4030 def CopyCellsToClipboardCSV(view
):
4031 CopyCellsToClipboard(view
, True, True)
4035 class ContextMenu(object):
4037 def __init__(self
, view
):
4039 self
.view
.setContextMenuPolicy(Qt
.CustomContextMenu
)
4040 self
.view
.customContextMenuRequested
.connect(self
.ShowContextMenu
)
4042 def ShowContextMenu(self
, pos
):
4043 menu
= QMenu(self
.view
)
4044 self
.AddActions(menu
)
4045 menu
.exec_(self
.view
.mapToGlobal(pos
))
4047 def AddCopy(self
, menu
):
4048 menu
.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self
.view
), self
.view
))
4049 menu
.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self
.view
), self
.view
))
4051 def AddActions(self
, menu
):
4054 class TreeContextMenu(ContextMenu
):
4056 def __init__(self
, view
):
4057 super(TreeContextMenu
, self
).__init
__(view
)
4059 def AddActions(self
, menu
):
4060 i
= self
.view
.currentIndex()
4061 text
= str(i
.data()).strip()
4063 menu
.addAction(CreateAction('Copy "' + text
+ '"', "Copy to clipboard", lambda: QApplication
.clipboard().setText(text
), self
.view
))
4068 class TableWindow(QMdiSubWindow
, ResizeColumnsToContentsBase
):
4070 def __init__(self
, glb
, table_name
, parent
=None):
4071 super(TableWindow
, self
).__init
__(parent
)
4073 self
.data_model
= LookupCreateModel(table_name
+ " Table", lambda: SQLAutoTableModel(glb
, table_name
))
4075 self
.model
= QSortFilterProxyModel()
4076 self
.model
.setSourceModel(self
.data_model
)
4078 self
.view
= QTableView()
4079 self
.view
.setModel(self
.model
)
4080 self
.view
.setEditTriggers(QAbstractItemView
.NoEditTriggers
)
4081 self
.view
.verticalHeader().setVisible(False)
4082 self
.view
.sortByColumn(-1, Qt
.AscendingOrder
)
4083 self
.view
.setSortingEnabled(True)
4084 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
4085 self
.view
.CopyCellsToClipboard
= CopyTableCellsToClipboard
4087 self
.ResizeColumnsToContents()
4089 self
.context_menu
= ContextMenu(self
.view
)
4091 self
.find_bar
= FindBar(self
, self
, True)
4093 self
.finder
= ChildDataItemFinder(self
.data_model
)
4095 self
.fetch_bar
= FetchMoreRecordsBar(self
.data_model
, self
)
4097 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
4099 self
.setWidget(self
.vbox
.Widget())
4101 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, table_name
+ " Table")
4103 def Find(self
, value
, direction
, pattern
, context
):
4104 self
.view
.setFocus()
4105 self
.find_bar
.Busy()
4106 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
4108 def FindDone(self
, row
):
4109 self
.find_bar
.Idle()
4111 self
.view
.setCurrentIndex(self
.model
.mapFromSource(self
.data_model
.index(row
, 0, QModelIndex())))
4113 self
.find_bar
.NotFound()
4117 def GetTableList(glb
):
4119 query
= QSqlQuery(glb
.db
)
4120 if glb
.dbref
.is_sqlite3
:
4121 QueryExec(query
, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
4123 QueryExec(query
, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
4125 tables
.append(query
.value(0))
4126 if glb
.dbref
.is_sqlite3
:
4127 tables
.append("sqlite_master")
4129 tables
.append("information_schema.tables")
4130 tables
.append("information_schema.views")
4131 tables
.append("information_schema.columns")
4134 # Top Calls data model
4136 class TopCallsModel(SQLTableModel
):
4138 def __init__(self
, glb
, report_vars
, parent
=None):
4140 if not glb
.dbref
.is_sqlite3
:
4143 if len(report_vars
.limit
):
4144 limit
= " LIMIT " + report_vars
.limit
4145 sql
= ("SELECT comm, pid, tid, name,"
4147 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text
+
4150 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
4152 " WHEN (calls.flags = 1) THEN 'no call'" + text
+
4153 " WHEN (calls.flags = 2) THEN 'no return'" + text
+
4154 " WHEN (calls.flags = 3) THEN 'no call/return'" + text
+
4158 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
4159 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
4160 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
4161 " INNER JOIN comms ON calls.comm_id = comms.id"
4162 " INNER JOIN threads ON calls.thread_id = threads.id" +
4163 report_vars
.where_clause
+
4164 " ORDER BY elapsed_time DESC" +
4167 column_headers
= ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
4168 self
.alignment
= (Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignLeft
)
4169 super(TopCallsModel
, self
).__init
__(glb
, sql
, column_headers
, parent
)
4171 def columnAlignment(self
, column
):
4172 return self
.alignment
[column
]
4174 # Top Calls report creation dialog
4176 class TopCallsDialog(ReportDialogBase
):
4178 def __init__(self
, glb
, parent
=None):
4179 title
= "Top Calls by Elapsed Time"
4180 items
= (lambda g
, p
: LineEditDataItem(g
, "Report name:", "Enter a name to appear in the window title bar", p
, "REPORTNAME"),
4181 lambda g
, p
: SQLTableDataItem(g
, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p
),
4182 lambda g
, p
: SQLTableDataItem(g
, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p
),
4183 lambda g
, p
: SQLTableDataItem(g
, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p
),
4184 lambda g
, p
: SQLTableDataItem(g
, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p
),
4185 lambda g
, p
: SQLTableDataItem(g
, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p
),
4186 lambda g
, p
: LineEditDataItem(g
, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p
),
4187 lambda g
, p
: PositiveIntegerDataItem(g
, "Record limit:", "Limit selection to this number of records", p
, "LIMIT", "100"))
4188 super(TopCallsDialog
, self
).__init
__(glb
, title
, items
, False, parent
)
4192 class TopCallsWindow(QMdiSubWindow
, ResizeColumnsToContentsBase
):
4194 def __init__(self
, glb
, report_vars
, parent
=None):
4195 super(TopCallsWindow
, self
).__init
__(parent
)
4197 self
.data_model
= LookupCreateModel("Top Calls " + report_vars
.UniqueId(), lambda: TopCallsModel(glb
, report_vars
))
4198 self
.model
= self
.data_model
4200 self
.view
= QTableView()
4201 self
.view
.setModel(self
.model
)
4202 self
.view
.setEditTriggers(QAbstractItemView
.NoEditTriggers
)
4203 self
.view
.verticalHeader().setVisible(False)
4204 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
4205 self
.view
.CopyCellsToClipboard
= CopyTableCellsToClipboard
4207 self
.context_menu
= ContextMenu(self
.view
)
4209 self
.ResizeColumnsToContents()
4211 self
.find_bar
= FindBar(self
, self
, True)
4213 self
.finder
= ChildDataItemFinder(self
.model
)
4215 self
.fetch_bar
= FetchMoreRecordsBar(self
.data_model
, self
)
4217 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
4219 self
.setWidget(self
.vbox
.Widget())
4221 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, report_vars
.name
)
4223 def Find(self
, value
, direction
, pattern
, context
):
4224 self
.view
.setFocus()
4225 self
.find_bar
.Busy()
4226 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
4228 def FindDone(self
, row
):
4229 self
.find_bar
.Idle()
4231 self
.view
.setCurrentIndex(self
.model
.index(row
, 0, QModelIndex()))
4233 self
.find_bar
.NotFound()
4237 def CreateAction(label
, tip
, callback
, parent
=None, shortcut
=None):
4238 action
= QAction(label
, parent
)
4239 if shortcut
!= None:
4240 action
.setShortcuts(shortcut
)
4241 action
.setStatusTip(tip
)
4242 action
.triggered
.connect(callback
)
4245 # Typical application actions
4247 def CreateExitAction(app
, parent
=None):
4248 return CreateAction("&Quit", "Exit the application", app
.closeAllWindows
, parent
, QKeySequence
.Quit
)
4250 # Typical MDI actions
4252 def CreateCloseActiveWindowAction(mdi_area
):
4253 return CreateAction("Cl&ose", "Close the active window", mdi_area
.closeActiveSubWindow
, mdi_area
)
4255 def CreateCloseAllWindowsAction(mdi_area
):
4256 return CreateAction("Close &All", "Close all the windows", mdi_area
.closeAllSubWindows
, mdi_area
)
4258 def CreateTileWindowsAction(mdi_area
):
4259 return CreateAction("&Tile", "Tile the windows", mdi_area
.tileSubWindows
, mdi_area
)
4261 def CreateCascadeWindowsAction(mdi_area
):
4262 return CreateAction("&Cascade", "Cascade the windows", mdi_area
.cascadeSubWindows
, mdi_area
)
4264 def CreateNextWindowAction(mdi_area
):
4265 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area
.activateNextSubWindow
, mdi_area
, QKeySequence
.NextChild
)
4267 def CreatePreviousWindowAction(mdi_area
):
4268 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area
.activatePreviousSubWindow
, mdi_area
, QKeySequence
.PreviousChild
)
4270 # Typical MDI window menu
4274 def __init__(self
, mdi_area
, menu
):
4275 self
.mdi_area
= mdi_area
4276 self
.window_menu
= menu
.addMenu("&Windows")
4277 self
.close_active_window
= CreateCloseActiveWindowAction(mdi_area
)
4278 self
.close_all_windows
= CreateCloseAllWindowsAction(mdi_area
)
4279 self
.tile_windows
= CreateTileWindowsAction(mdi_area
)
4280 self
.cascade_windows
= CreateCascadeWindowsAction(mdi_area
)
4281 self
.next_window
= CreateNextWindowAction(mdi_area
)
4282 self
.previous_window
= CreatePreviousWindowAction(mdi_area
)
4283 self
.window_menu
.aboutToShow
.connect(self
.Update
)
4286 self
.window_menu
.clear()
4287 sub_window_count
= len(self
.mdi_area
.subWindowList())
4288 have_sub_windows
= sub_window_count
!= 0
4289 self
.close_active_window
.setEnabled(have_sub_windows
)
4290 self
.close_all_windows
.setEnabled(have_sub_windows
)
4291 self
.tile_windows
.setEnabled(have_sub_windows
)
4292 self
.cascade_windows
.setEnabled(have_sub_windows
)
4293 self
.next_window
.setEnabled(have_sub_windows
)
4294 self
.previous_window
.setEnabled(have_sub_windows
)
4295 self
.window_menu
.addAction(self
.close_active_window
)
4296 self
.window_menu
.addAction(self
.close_all_windows
)
4297 self
.window_menu
.addSeparator()
4298 self
.window_menu
.addAction(self
.tile_windows
)
4299 self
.window_menu
.addAction(self
.cascade_windows
)
4300 self
.window_menu
.addSeparator()
4301 self
.window_menu
.addAction(self
.next_window
)
4302 self
.window_menu
.addAction(self
.previous_window
)
4303 if sub_window_count
== 0:
4305 self
.window_menu
.addSeparator()
4307 for sub_window
in self
.mdi_area
.subWindowList():
4308 label
= str(nr
) + " " + sub_window
.name
4311 action
= self
.window_menu
.addAction(label
)
4312 action
.setCheckable(True)
4313 action
.setChecked(sub_window
== self
.mdi_area
.activeSubWindow())
4314 action
.triggered
.connect(lambda a
=None,x
=nr
: self
.setActiveSubWindow(x
))
4315 self
.window_menu
.addAction(action
)
4318 def setActiveSubWindow(self
, nr
):
4319 self
.mdi_area
.setActiveSubWindow(self
.mdi_area
.subWindowList()[nr
- 1])
4334 <p class=c1><a href=#reports>1. Reports</a></p>
4335 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
4336 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
4337 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
4338 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
4339 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
4340 <p class=c1><a href=#charts>2. Charts</a></p>
4341 <p class=c2><a href=#timechartbycpu>2.1 Time chart by CPU</a></p>
4342 <p class=c1><a href=#tables>3. Tables</a></p>
4343 <h1 id=reports>1. Reports</h1>
4344 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
4345 The result is a GUI window with a tree representing a context-sensitive
4346 call-graph. Expanding a couple of levels of the tree and adjusting column
4347 widths to suit will display something like:
4349 Call Graph: pt_example
4350 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
4353 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
4354 |- unknown unknown 1 13198 0.1 1 0.0
4355 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
4356 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
4357 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
4358 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
4359 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
4360 >- __libc_csu_init ls 1 10354 0.1 10 0.0
4361 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
4362 v- main ls 1 8182043 99.6 180254 99.9
4364 <h3>Points to note:</h3>
4366 <li>The top level is a command name (comm)</li>
4367 <li>The next level is a thread (pid:tid)</li>
4368 <li>Subsequent levels are functions</li>
4369 <li>'Count' is the number of calls</li>
4370 <li>'Time' is the elapsed time until the function returns</li>
4371 <li>Percentages are relative to the level above</li>
4372 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
4375 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
4376 The pattern matching symbols are ? for any character and * for zero or more characters.
4377 <h2 id=calltree>1.2 Call Tree</h2>
4378 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
4379 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
4380 <h2 id=allbranches>1.3 All branches</h2>
4381 The All branches report displays all branches in chronological order.
4382 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
4383 <h3>Disassembly</h3>
4384 Open a branch to display disassembly. This only works if:
4386 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
4387 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
4388 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
4389 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
4390 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
4392 <h4 id=xed>Intel XED Setup</h4>
4393 To use Intel XED, libxed.so must be present. To build and install libxed.so:
4395 git clone https://github.com/intelxed/mbuild.git mbuild
4396 git clone https://github.com/intelxed/xed
4399 sudo ./mfile.py --prefix=/usr/local install
4402 <h3>Instructions per Cycle (IPC)</h3>
4403 If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
4404 <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
4405 Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
4406 In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
4407 since the previous displayed 'IPC'.
4409 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
4410 Refer to Python documentation for the regular expression syntax.
4411 All columns are searched, but only currently fetched rows are searched.
4412 <h2 id=selectedbranches>1.4 Selected branches</h2>
4413 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
4414 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
4415 <h3>1.4.1 Time ranges</h3>
4416 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
4417 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
4419 81073085947329-81073085958238 From 81073085947329 to 81073085958238
4420 100us-200us From 100us to 200us
4421 10ms- From 10ms to the end
4422 -100ns The first 100ns
4423 -10ms- The last 10ms
4425 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
4426 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
4427 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.
4428 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
4429 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
4430 <h1 id=charts>2. Charts</h1>
4431 <h2 id=timechartbycpu>2.1 Time chart by CPU</h2>
4432 This chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu.
4435 <li>Mouse over to highight the task and show the time</li>
4436 <li>Drag the mouse to select a region and zoom by pushing the Zoom button</li>
4437 <li>Go back and forward by pressing the arrow buttons</li>
4438 <li>If call information is available, right-click to show a call tree opened to that task and time.
4439 Note, the call tree may take some time to appear, and there may not be call information for the task or time selected.
4443 The graph can be misleading in the following respects:
4445 <li>The graph shows the first task on each CPU as running from the beginning of the time range.
4446 Because tracing might start on different CPUs at different times, that is not necessarily the case.
4447 Refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4448 <li>Similarly, the last task on each CPU can be showing running longer than it really was.
4449 Again, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4450 <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>
4452 <h1 id=tables>3. Tables</h1>
4453 The Tables menu shows all tables and views in the database. Most tables have an associated view
4454 which displays the information in a more friendly way. Not all data for large tables is fetched
4455 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
4456 but that can be slow for large tables.
4457 <p>There are also tables of database meta-information.
4458 For SQLite3 databases, the sqlite_master table is included.
4459 For PostgreSQL databases, information_schema.tables/views/columns are included.
4461 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
4462 Refer to Python documentation for the regular expression syntax.
4463 All columns are searched, but only currently fetched rows are searched.
4464 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
4465 will go to the next/previous result in id order, instead of display order.
4470 class HelpWindow(QMdiSubWindow
):
4472 def __init__(self
, glb
, parent
=None):
4473 super(HelpWindow
, self
).__init
__(parent
)
4475 self
.text
= QTextBrowser()
4476 self
.text
.setHtml(glb_help_text
)
4477 self
.text
.setReadOnly(True)
4478 self
.text
.setOpenExternalLinks(True)
4480 self
.setWidget(self
.text
)
4482 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Exported SQL Viewer Help")
4484 # Main window that only displays the help text
4486 class HelpOnlyWindow(QMainWindow
):
4488 def __init__(self
, parent
=None):
4489 super(HelpOnlyWindow
, self
).__init
__(parent
)
4491 self
.setMinimumSize(200, 100)
4492 self
.resize(800, 600)
4493 self
.setWindowTitle("Exported SQL Viewer Help")
4494 self
.setWindowIcon(self
.style().standardIcon(QStyle
.SP_MessageBoxInformation
))
4496 self
.text
= QTextBrowser()
4497 self
.text
.setHtml(glb_help_text
)
4498 self
.text
.setReadOnly(True)
4499 self
.text
.setOpenExternalLinks(True)
4501 self
.setCentralWidget(self
.text
)
4503 # PostqreSQL server version
4505 def PostqreSQLServerVersion(db
):
4506 query
= QSqlQuery(db
)
4507 QueryExec(query
, "SELECT VERSION()")
4509 v_str
= query
.value(0)
4510 v_list
= v_str
.strip().split(" ")
4511 if v_list
[0] == "PostgreSQL" and v_list
[2] == "on":
4518 def SQLiteVersion(db
):
4519 query
= QSqlQuery(db
)
4520 QueryExec(query
, "SELECT sqlite_version()")
4522 return query
.value(0)
4527 class AboutDialog(QDialog
):
4529 def __init__(self
, glb
, parent
=None):
4530 super(AboutDialog
, self
).__init
__(parent
)
4532 self
.setWindowTitle("About Exported SQL Viewer")
4533 self
.setMinimumWidth(300)
4535 pyside_version
= "1" if pyside_version_1
else "2"
4538 text
+= "Python version: " + sys
.version
.split(" ")[0] + "\n"
4539 text
+= "PySide version: " + pyside_version
+ "\n"
4540 text
+= "Qt version: " + qVersion() + "\n"
4541 if glb
.dbref
.is_sqlite3
:
4542 text
+= "SQLite version: " + SQLiteVersion(glb
.db
) + "\n"
4544 text
+= "PostqreSQL version: " + PostqreSQLServerVersion(glb
.db
) + "\n"
4547 self
.text
= QTextBrowser()
4548 self
.text
.setHtml(text
)
4549 self
.text
.setReadOnly(True)
4550 self
.text
.setOpenExternalLinks(True)
4552 self
.vbox
= QVBoxLayout()
4553 self
.vbox
.addWidget(self
.text
)
4555 self
.setLayout(self
.vbox
)
4559 def ResizeFont(widget
, diff
):
4560 font
= widget
.font()
4561 sz
= font
.pointSize()
4562 font
.setPointSize(sz
+ diff
)
4563 widget
.setFont(font
)
4565 def ShrinkFont(widget
):
4566 ResizeFont(widget
, -1)
4568 def EnlargeFont(widget
):
4569 ResizeFont(widget
, 1)
4571 # Unique name for sub-windows
4573 def NumberedWindowName(name
, nr
):
4575 name
+= " <" + str(nr
) + ">"
4578 def UniqueSubWindowName(mdi_area
, name
):
4581 unique_name
= NumberedWindowName(name
, nr
)
4583 for sub_window
in mdi_area
.subWindowList():
4584 if sub_window
.name
== unique_name
:
4593 def AddSubWindow(mdi_area
, sub_window
, name
):
4594 unique_name
= UniqueSubWindowName(mdi_area
, name
)
4595 sub_window
.setMinimumSize(200, 100)
4596 sub_window
.resize(800, 600)
4597 sub_window
.setWindowTitle(unique_name
)
4598 sub_window
.setAttribute(Qt
.WA_DeleteOnClose
)
4599 sub_window
.setWindowIcon(sub_window
.style().standardIcon(QStyle
.SP_FileIcon
))
4600 sub_window
.name
= unique_name
4601 mdi_area
.addSubWindow(sub_window
)
4606 class MainWindow(QMainWindow
):
4608 def __init__(self
, glb
, parent
=None):
4609 super(MainWindow
, self
).__init
__(parent
)
4613 self
.setWindowTitle("Exported SQL Viewer: " + glb
.dbname
)
4614 self
.setWindowIcon(self
.style().standardIcon(QStyle
.SP_ComputerIcon
))
4615 self
.setMinimumSize(200, 100)
4617 self
.mdi_area
= QMdiArea()
4618 self
.mdi_area
.setHorizontalScrollBarPolicy(Qt
.ScrollBarAsNeeded
)
4619 self
.mdi_area
.setVerticalScrollBarPolicy(Qt
.ScrollBarAsNeeded
)
4621 self
.setCentralWidget(self
.mdi_area
)
4623 menu
= self
.menuBar()
4625 file_menu
= menu
.addMenu("&File")
4626 file_menu
.addAction(CreateExitAction(glb
.app
, self
))
4628 edit_menu
= menu
.addMenu("&Edit")
4629 edit_menu
.addAction(CreateAction("&Copy", "Copy to clipboard", self
.CopyToClipboard
, self
, QKeySequence
.Copy
))
4630 edit_menu
.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self
.CopyToClipboardCSV
, self
))
4631 edit_menu
.addAction(CreateAction("&Find...", "Find items", self
.Find
, self
, QKeySequence
.Find
))
4632 edit_menu
.addAction(CreateAction("Fetch &more records...", "Fetch more records", self
.FetchMoreRecords
, self
, [QKeySequence(Qt
.Key_F8
)]))
4633 edit_menu
.addAction(CreateAction("&Shrink Font", "Make text smaller", self
.ShrinkFont
, self
, [QKeySequence("Ctrl+-")]))
4634 edit_menu
.addAction(CreateAction("&Enlarge Font", "Make text bigger", self
.EnlargeFont
, self
, [QKeySequence("Ctrl++")]))
4636 reports_menu
= menu
.addMenu("&Reports")
4637 if IsSelectable(glb
.db
, "calls"):
4638 reports_menu
.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self
.NewCallGraph
, self
))
4640 if IsSelectable(glb
.db
, "calls", "WHERE parent_id >= 0"):
4641 reports_menu
.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self
.NewCallTree
, self
))
4643 self
.EventMenu(GetEventList(glb
.db
), reports_menu
)
4645 if IsSelectable(glb
.db
, "calls"):
4646 reports_menu
.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self
.NewTopCalls
, self
))
4648 if IsSelectable(glb
.db
, "context_switches"):
4649 charts_menu
= menu
.addMenu("&Charts")
4650 charts_menu
.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self
.TimeChartByCPU
, self
))
4652 self
.TableMenu(GetTableList(glb
), menu
)
4654 self
.window_menu
= WindowMenu(self
.mdi_area
, menu
)
4656 help_menu
= menu
.addMenu("&Help")
4657 help_menu
.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self
.Help
, self
, QKeySequence
.HelpContents
))
4658 help_menu
.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self
.About
, self
))
4661 win
= self
.mdi_area
.activeSubWindow()
4668 def CopyToClipboard(self
):
4669 self
.Try(CopyCellsToClipboardHdr
)
4671 def CopyToClipboardCSV(self
):
4672 self
.Try(CopyCellsToClipboardCSV
)
4675 win
= self
.mdi_area
.activeSubWindow()
4678 win
.find_bar
.Activate()
4682 def FetchMoreRecords(self
):
4683 win
= self
.mdi_area
.activeSubWindow()
4686 win
.fetch_bar
.Activate()
4690 def ShrinkFont(self
):
4691 self
.Try(ShrinkFont
)
4693 def EnlargeFont(self
):
4694 self
.Try(EnlargeFont
)
4696 def EventMenu(self
, events
, reports_menu
):
4698 for event
in events
:
4699 event
= event
.split(":")[0]
4700 if event
== "branches":
4701 branches_events
+= 1
4703 for event
in events
:
4705 event
= event
.split(":")[0]
4706 if event
== "branches":
4707 label
= "All branches" if branches_events
== 1 else "All branches " + "(id=" + dbid
+ ")"
4708 reports_menu
.addAction(CreateAction(label
, "Create a new window displaying branch events", lambda a
=None,x
=dbid
: self
.NewBranchView(x
), self
))
4709 label
= "Selected branches" if branches_events
== 1 else "Selected branches " + "(id=" + dbid
+ ")"
4710 reports_menu
.addAction(CreateAction(label
, "Create a new window displaying branch events", lambda a
=None,x
=dbid
: self
.NewSelectedBranchView(x
), self
))
4712 def TimeChartByCPU(self
):
4713 TimeChartByCPUWindow(self
.glb
, self
)
4715 def TableMenu(self
, tables
, menu
):
4716 table_menu
= menu
.addMenu("&Tables")
4717 for table
in tables
:
4718 table_menu
.addAction(CreateAction(table
, "Create a new window containing a table view", lambda a
=None,t
=table
: self
.NewTableView(t
), self
))
4720 def NewCallGraph(self
):
4721 CallGraphWindow(self
.glb
, self
)
4723 def NewCallTree(self
):
4724 CallTreeWindow(self
.glb
, self
)
4726 def NewTopCalls(self
):
4727 dialog
= TopCallsDialog(self
.glb
, self
)
4728 ret
= dialog
.exec_()
4730 TopCallsWindow(self
.glb
, dialog
.report_vars
, self
)
4732 def NewBranchView(self
, event_id
):
4733 BranchWindow(self
.glb
, event_id
, ReportVars(), self
)
4735 def NewSelectedBranchView(self
, event_id
):
4736 dialog
= SelectedBranchDialog(self
.glb
, self
)
4737 ret
= dialog
.exec_()
4739 BranchWindow(self
.glb
, event_id
, dialog
.report_vars
, self
)
4741 def NewTableView(self
, table_name
):
4742 TableWindow(self
.glb
, table_name
, self
)
4745 HelpWindow(self
.glb
, self
)
4748 dialog
= AboutDialog(self
.glb
, self
)
4751 def TryOpen(file_name
):
4753 return open(file_name
, "rb")
4758 result
= sizeof(c_void_p
)
4765 if sys
.version_info
[0] == 2:
4766 eclass
= ord(header
[4])
4767 encoding
= ord(header
[5])
4768 version
= ord(header
[6])
4771 encoding
= header
[5]
4773 if magic
== chr(127) + "ELF" and eclass
> 0 and eclass
< 3 and encoding
> 0 and encoding
< 3 and version
== 1:
4774 result
= True if eclass
== 2 else False
4781 def __init__(self
, dbref
, db
, dbname
):
4784 self
.dbname
= dbname
4785 self
.home_dir
= os
.path
.expanduser("~")
4786 self
.buildid_dir
= os
.getenv("PERF_BUILDID_DIR")
4787 if self
.buildid_dir
:
4788 self
.buildid_dir
+= "/.build-id/"
4790 self
.buildid_dir
= self
.home_dir
+ "/.debug/.build-id/"
4792 self
.mainwindow
= None
4793 self
.instances_to_shutdown_on_exit
= weakref
.WeakSet()
4795 self
.disassembler
= LibXED()
4796 self
.have_disassembler
= True
4798 self
.have_disassembler
= False
4799 self
.host_machine_id
= 0
4800 self
.host_start_time
= 0
4801 self
.host_finish_time
= 0
4803 def FileFromBuildId(self
, build_id
):
4804 file_name
= self
.buildid_dir
+ build_id
[0:2] + "/" + build_id
[2:] + "/elf"
4805 return TryOpen(file_name
)
4807 def FileFromNamesAndBuildId(self
, short_name
, long_name
, build_id
):
4808 # Assume current machine i.e. no support for virtualization
4809 if short_name
[0:7] == "[kernel" and os
.path
.basename(long_name
) == "kcore":
4810 file_name
= os
.getenv("PERF_KCORE")
4811 f
= TryOpen(file_name
) if file_name
else None
4814 # For now, no special handling if long_name is /proc/kcore
4815 f
= TryOpen(long_name
)
4818 f
= self
.FileFromBuildId(build_id
)
4823 def AddInstanceToShutdownOnExit(self
, instance
):
4824 self
.instances_to_shutdown_on_exit
.add(instance
)
4826 # Shutdown any background processes or threads
4827 def ShutdownInstances(self
):
4828 for x
in self
.instances_to_shutdown_on_exit
:
4834 def GetHostMachineId(self
):
4835 query
= QSqlQuery(self
.db
)
4836 QueryExec(query
, "SELECT id FROM machines WHERE pid = -1")
4838 self
.host_machine_id
= query
.value(0)
4840 self
.host_machine_id
= 0
4841 return self
.host_machine_id
4843 def HostMachineId(self
):
4844 if self
.host_machine_id
:
4845 return self
.host_machine_id
4846 return self
.GetHostMachineId()
4848 def SelectValue(self
, sql
):
4849 query
= QSqlQuery(self
.db
)
4851 QueryExec(query
, sql
)
4855 return Decimal(query
.value(0))
4858 def SwitchesMinTime(self
, machine_id
):
4859 return self
.SelectValue("SELECT time"
4860 " FROM context_switches"
4861 " WHERE time != 0 AND machine_id = " + str(machine_id
) +
4862 " ORDER BY id LIMIT 1")
4864 def SwitchesMaxTime(self
, machine_id
):
4865 return self
.SelectValue("SELECT time"
4866 " FROM context_switches"
4867 " WHERE time != 0 AND machine_id = " + str(machine_id
) +
4868 " ORDER BY id DESC LIMIT 1")
4870 def SamplesMinTime(self
, machine_id
):
4871 return self
.SelectValue("SELECT time"
4873 " WHERE time != 0 AND machine_id = " + str(machine_id
) +
4874 " ORDER BY id LIMIT 1")
4876 def SamplesMaxTime(self
, machine_id
):
4877 return self
.SelectValue("SELECT time"
4879 " WHERE time != 0 AND machine_id = " + str(machine_id
) +
4880 " ORDER BY id DESC LIMIT 1")
4882 def CallsMinTime(self
, machine_id
):
4883 return self
.SelectValue("SELECT calls.call_time"
4885 " INNER JOIN threads ON threads.thread_id = calls.thread_id"
4886 " WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id
) +
4887 " ORDER BY calls.id LIMIT 1")
4889 def CallsMaxTime(self
, machine_id
):
4890 return self
.SelectValue("SELECT calls.return_time"
4892 " INNER JOIN threads ON threads.thread_id = calls.thread_id"
4893 " WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id
) +
4894 " ORDER BY calls.return_time DESC LIMIT 1")
4896 def GetStartTime(self
, machine_id
):
4897 t0
= self
.SwitchesMinTime(machine_id
)
4898 t1
= self
.SamplesMinTime(machine_id
)
4899 t2
= self
.CallsMinTime(machine_id
)
4900 if t0
is None or (not(t1
is None) and t1
< t0
):
4902 if t0
is None or (not(t2
is None) and t2
< t0
):
4906 def GetFinishTime(self
, machine_id
):
4907 t0
= self
.SwitchesMaxTime(machine_id
)
4908 t1
= self
.SamplesMaxTime(machine_id
)
4909 t2
= self
.CallsMaxTime(machine_id
)
4910 if t0
is None or (not(t1
is None) and t1
> t0
):
4912 if t0
is None or (not(t2
is None) and t2
> t0
):
4916 def HostStartTime(self
):
4917 if self
.host_start_time
:
4918 return self
.host_start_time
4919 self
.host_start_time
= self
.GetStartTime(self
.HostMachineId())
4920 return self
.host_start_time
4922 def HostFinishTime(self
):
4923 if self
.host_finish_time
:
4924 return self
.host_finish_time
4925 self
.host_finish_time
= self
.GetFinishTime(self
.HostMachineId())
4926 return self
.host_finish_time
4928 def StartTime(self
, machine_id
):
4929 if machine_id
== self
.HostMachineId():
4930 return self
.HostStartTime()
4931 return self
.GetStartTime(machine_id
)
4933 def FinishTime(self
, machine_id
):
4934 if machine_id
== self
.HostMachineId():
4935 return self
.HostFinishTime()
4936 return self
.GetFinishTime(machine_id
)
4938 # Database reference
4942 def __init__(self
, is_sqlite3
, dbname
):
4943 self
.is_sqlite3
= is_sqlite3
4944 self
.dbname
= dbname
4946 self
.FALSE
= "FALSE"
4947 # SQLite prior to version 3.23 does not support TRUE and FALSE
4952 def Open(self
, connection_name
):
4953 dbname
= self
.dbname
4955 db
= QSqlDatabase
.addDatabase("QSQLITE", connection_name
)
4957 db
= QSqlDatabase
.addDatabase("QPSQL", connection_name
)
4958 opts
= dbname
.split()
4961 opt
= opt
.split("=")
4962 if opt
[0] == "hostname":
4963 db
.setHostName(opt
[1])
4964 elif opt
[0] == "port":
4965 db
.setPort(int(opt
[1]))
4966 elif opt
[0] == "username":
4967 db
.setUserName(opt
[1])
4968 elif opt
[0] == "password":
4969 db
.setPassword(opt
[1])
4970 elif opt
[0] == "dbname":
4975 db
.setDatabaseName(dbname
)
4977 raise Exception("Failed to open database " + dbname
+ " error: " + db
.lastError().text())
4983 usage_str
= "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
4984 " or: exported-sql-viewer.py --help-only"
4985 ap
= argparse
.ArgumentParser(usage
= usage_str
, add_help
= False)
4986 ap
.add_argument("--pyside-version-1", action
='store_true')
4987 ap
.add_argument("dbname", nargs
="?")
4988 ap
.add_argument("--help-only", action
='store_true')
4989 args
= ap
.parse_args()
4992 app
= QApplication(sys
.argv
)
4993 mainwindow
= HelpOnlyWindow()
4998 dbname
= args
.dbname
5001 print("Too few arguments")
5006 f
= open(dbname
, "rb")
5007 if f
.read(15) == b
'SQLite format 3':
5013 dbref
= DBRef(is_sqlite3
, dbname
)
5014 db
, dbname
= dbref
.Open("main")
5015 glb
= Glb(dbref
, db
, dbname
)
5016 app
= QApplication(sys
.argv
)
5018 mainwindow
= MainWindow(glb
)
5019 glb
.mainwindow
= mainwindow
5022 glb
.ShutdownInstances()
5026 if __name__
== "__main__":