2 # SPDX-License-Identifier: GPL-2.0
3 # exported-sql-viewer.py: view data from sql database
4 # Copyright (c) 2014-2018, Intel Corporation.
6 # To use this script you will need to have exported data using either the
7 # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
10 # Following on from the example in the export scripts, a
11 # call-graph can be displayed for the pt_example database like this:
13 # python tools/perf/scripts/python/exported-sql-viewer.py pt_example
15 # Note that for PostgreSQL, this script supports connecting to remote databases
16 # by setting hostname, port, username, password, and dbname e.g.
18 # python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
20 # The result is a GUI window with a tree representing a context-sensitive
21 # call-graph. Expanding a couple of levels of the tree and adjusting column
22 # widths to suit will display something like:
24 # Call Graph: pt_example
25 # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
28 # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
29 # |- unknown unknown 1 13198 0.1 1 0.0
30 # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
31 # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
32 # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
33 # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
34 # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
35 # >- __libc_csu_init ls 1 10354 0.1 10 0.0
36 # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
37 # v- main ls 1 8182043 99.6 180254 99.9
40 # The top level is a command name (comm)
41 # The next level is a thread (pid:tid)
42 # Subsequent levels are functions
43 # 'Count' is the number of calls
44 # 'Time' is the elapsed time until the function returns
45 # Percentages are relative to the level above
46 # 'Branch Count' is the total number of branches for that function and all
47 # functions that it calls
49 # There is also a "All branches" report, which displays branches and
50 # possibly disassembly. However, presently, the only supported disassembler is
51 # Intel XED, and additionally the object code must be present in perf build ID
52 # cache. To use Intel XED, libxed.so must be present. To build and install
54 # git clone https://github.com/intelxed/mbuild.git mbuild
55 # git clone https://github.com/intelxed/xed
58 # sudo ./mfile.py --prefix=/usr/local install
63 # Time CPU Command PID TID Branch Type In Tx Branch
64 # 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
65 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
66 # 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
67 # 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
68 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
69 # 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930
70 # 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
71 # 7fab593ea930 55 pushq %rbp
72 # 7fab593ea931 48 89 e5 mov %rsp, %rbp
73 # 7fab593ea934 41 57 pushq %r15
74 # 7fab593ea936 41 56 pushq %r14
75 # 7fab593ea938 41 55 pushq %r13
76 # 7fab593ea93a 41 54 pushq %r12
77 # 7fab593ea93c 53 pushq %rbx
78 # 7fab593ea93d 48 89 fb mov %rdi, %rbx
79 # 7fab593ea940 48 83 ec 68 sub $0x68, %rsp
80 # 7fab593ea944 0f 31 rdtsc
81 # 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx
82 # 7fab593ea94a 89 c0 mov %eax, %eax
83 # 7fab593ea94c 48 09 c2 or %rax, %rdx
84 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
85 # 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
86 # 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
87 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
88 # 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip)
89 # 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
91 from __future__
import print_function
100 import cPickle
as pickle
101 # size of pickled integer big enough for record size
109 pyside_version_1
= True
110 if not "--pyside-version-1" in sys
.argv
:
112 from PySide2
.QtCore
import *
113 from PySide2
.QtGui
import *
114 from PySide2
.QtSql
import *
115 from PySide2
.QtWidgets
import *
116 pyside_version_1
= False
121 from PySide
.QtCore
import *
122 from PySide
.QtGui
import *
123 from PySide
.QtSql
import *
125 from decimal
import *
127 from multiprocessing
import Process
, Array
, Value
, Event
129 # xrange is range in Python3
135 def printerr(*args
, **keyword_args
):
136 print(*args
, file=sys
.stderr
, **keyword_args
)
138 # Data formatting helpers
147 return "+0x%x" % offset
151 if name
== "[kernel.kallsyms]":
155 def findnth(s
, sub
, n
, offs
=0):
161 return findnth(s
[pos
+ 1:], sub
, n
- 1, offs
+ pos
+ 1)
163 # Percent to one decimal place
165 def PercentToOneDP(n
, d
):
168 x
= (n
* Decimal(100)) / d
169 return str(x
.quantize(Decimal(".1"), rounding
=ROUND_HALF_UP
))
171 # Helper for queries that must not fail
173 def QueryExec(query
, stmt
):
174 ret
= query
.exec_(stmt
)
176 raise Exception("Query failed: " + query
.lastError().text())
180 class Thread(QThread
):
182 done
= Signal(object)
184 def __init__(self
, task
, param
=None, parent
=None):
185 super(Thread
, self
).__init
__(parent
)
191 if self
.param
is None:
192 done
, result
= self
.task()
194 done
, result
= self
.task(self
.param
)
195 self
.done
.emit(result
)
201 class TreeModel(QAbstractItemModel
):
203 def __init__(self
, glb
, params
, parent
=None):
204 super(TreeModel
, self
).__init
__(parent
)
207 self
.root
= self
.GetRoot()
208 self
.last_row_read
= 0
210 def Item(self
, parent
):
212 return parent
.internalPointer()
216 def rowCount(self
, parent
):
217 result
= self
.Item(parent
).childCount()
220 self
.dataChanged
.emit(parent
, parent
)
223 def hasChildren(self
, parent
):
224 return self
.Item(parent
).hasChildren()
226 def headerData(self
, section
, orientation
, role
):
227 if role
== Qt
.TextAlignmentRole
:
228 return self
.columnAlignment(section
)
229 if role
!= Qt
.DisplayRole
:
231 if orientation
!= Qt
.Horizontal
:
233 return self
.columnHeader(section
)
235 def parent(self
, child
):
236 child_item
= child
.internalPointer()
237 if child_item
is self
.root
:
239 parent_item
= child_item
.getParentItem()
240 return self
.createIndex(parent_item
.getRow(), 0, parent_item
)
242 def index(self
, row
, column
, parent
):
243 child_item
= self
.Item(parent
).getChildItem(row
)
244 return self
.createIndex(row
, column
, child_item
)
246 def DisplayData(self
, item
, index
):
247 return item
.getData(index
.column())
249 def FetchIfNeeded(self
, row
):
250 if row
> self
.last_row_read
:
251 self
.last_row_read
= row
252 if row
+ 10 >= self
.root
.child_count
:
253 self
.fetcher
.Fetch(glb_chunk_sz
)
255 def columnAlignment(self
, column
):
258 def columnFont(self
, column
):
261 def data(self
, index
, role
):
262 if role
== Qt
.TextAlignmentRole
:
263 return self
.columnAlignment(index
.column())
264 if role
== Qt
.FontRole
:
265 return self
.columnFont(index
.column())
266 if role
!= Qt
.DisplayRole
:
268 item
= index
.internalPointer()
269 return self
.DisplayData(item
, index
)
273 class TableModel(QAbstractTableModel
):
275 def __init__(self
, parent
=None):
276 super(TableModel
, self
).__init
__(parent
)
278 self
.child_items
= []
279 self
.last_row_read
= 0
281 def Item(self
, parent
):
283 return parent
.internalPointer()
287 def rowCount(self
, parent
):
288 return self
.child_count
290 def headerData(self
, section
, orientation
, role
):
291 if role
== Qt
.TextAlignmentRole
:
292 return self
.columnAlignment(section
)
293 if role
!= Qt
.DisplayRole
:
295 if orientation
!= Qt
.Horizontal
:
297 return self
.columnHeader(section
)
299 def index(self
, row
, column
, parent
):
300 return self
.createIndex(row
, column
, self
.child_items
[row
])
302 def DisplayData(self
, item
, index
):
303 return item
.getData(index
.column())
305 def FetchIfNeeded(self
, row
):
306 if row
> self
.last_row_read
:
307 self
.last_row_read
= row
308 if row
+ 10 >= self
.child_count
:
309 self
.fetcher
.Fetch(glb_chunk_sz
)
311 def columnAlignment(self
, column
):
314 def columnFont(self
, column
):
317 def data(self
, index
, role
):
318 if role
== Qt
.TextAlignmentRole
:
319 return self
.columnAlignment(index
.column())
320 if role
== Qt
.FontRole
:
321 return self
.columnFont(index
.column())
322 if role
!= Qt
.DisplayRole
:
324 item
= index
.internalPointer()
325 return self
.DisplayData(item
, index
)
329 model_cache
= weakref
.WeakValueDictionary()
330 model_cache_lock
= threading
.Lock()
332 def LookupCreateModel(model_name
, create_fn
):
333 model_cache_lock
.acquire()
335 model
= model_cache
[model_name
]
340 model_cache
[model_name
] = model
341 model_cache_lock
.release()
348 def __init__(self
, parent
, finder
, is_reg_expr
=False):
351 self
.last_value
= None
352 self
.last_pattern
= None
354 label
= QLabel("Find:")
355 label
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
357 self
.textbox
= QComboBox()
358 self
.textbox
.setEditable(True)
359 self
.textbox
.currentIndexChanged
.connect(self
.ValueChanged
)
361 self
.progress
= QProgressBar()
362 self
.progress
.setRange(0, 0)
366 self
.pattern
= QCheckBox("Regular Expression")
368 self
.pattern
= QCheckBox("Pattern")
369 self
.pattern
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
371 self
.next_button
= QToolButton()
372 self
.next_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_ArrowDown
))
373 self
.next_button
.released
.connect(lambda: self
.NextPrev(1))
375 self
.prev_button
= QToolButton()
376 self
.prev_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_ArrowUp
))
377 self
.prev_button
.released
.connect(lambda: self
.NextPrev(-1))
379 self
.close_button
= QToolButton()
380 self
.close_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_DockWidgetCloseButton
))
381 self
.close_button
.released
.connect(self
.Deactivate
)
383 self
.hbox
= QHBoxLayout()
384 self
.hbox
.setContentsMargins(0, 0, 0, 0)
386 self
.hbox
.addWidget(label
)
387 self
.hbox
.addWidget(self
.textbox
)
388 self
.hbox
.addWidget(self
.progress
)
389 self
.hbox
.addWidget(self
.pattern
)
390 self
.hbox
.addWidget(self
.next_button
)
391 self
.hbox
.addWidget(self
.prev_button
)
392 self
.hbox
.addWidget(self
.close_button
)
395 self
.bar
.setLayout(self
.hbox
)
403 self
.textbox
.lineEdit().selectAll()
404 self
.textbox
.setFocus()
406 def Deactivate(self
):
410 self
.textbox
.setEnabled(False)
412 self
.next_button
.hide()
413 self
.prev_button
.hide()
417 self
.textbox
.setEnabled(True)
420 self
.next_button
.show()
421 self
.prev_button
.show()
423 def Find(self
, direction
):
424 value
= self
.textbox
.currentText()
425 pattern
= self
.pattern
.isChecked()
426 self
.last_value
= value
427 self
.last_pattern
= pattern
428 self
.finder
.Find(value
, direction
, pattern
, self
.context
)
430 def ValueChanged(self
):
431 value
= self
.textbox
.currentText()
432 pattern
= self
.pattern
.isChecked()
433 index
= self
.textbox
.currentIndex()
434 data
= self
.textbox
.itemData(index
)
435 # Store the pattern in the combo box to keep it with the text value
437 self
.textbox
.setItemData(index
, pattern
)
439 self
.pattern
.setChecked(data
)
442 def NextPrev(self
, direction
):
443 value
= self
.textbox
.currentText()
444 pattern
= self
.pattern
.isChecked()
445 if value
!= self
.last_value
:
446 index
= self
.textbox
.findText(value
)
447 # Allow for a button press before the value has been added to the combo box
449 index
= self
.textbox
.count()
450 self
.textbox
.addItem(value
, pattern
)
451 self
.textbox
.setCurrentIndex(index
)
454 self
.textbox
.setItemData(index
, pattern
)
455 elif pattern
!= self
.last_pattern
:
456 # Keep the pattern recorded in the combo box up to date
457 index
= self
.textbox
.currentIndex()
458 self
.textbox
.setItemData(index
, pattern
)
462 QMessageBox
.information(self
.bar
, "Find", "'" + self
.textbox
.currentText() + "' not found")
464 # Context-sensitive call graph data model item base
466 class CallGraphLevelItemBase(object):
468 def __init__(self
, glb
, params
, row
, parent_item
):
472 self
.parent_item
= parent_item
473 self
.query_done
= False
475 self
.child_items
= []
477 self
.level
= parent_item
.level
+ 1
481 def getChildItem(self
, row
):
482 return self
.child_items
[row
]
484 def getParentItem(self
):
485 return self
.parent_item
490 def childCount(self
):
491 if not self
.query_done
:
493 if not self
.child_count
:
495 return self
.child_count
497 def hasChildren(self
):
498 if not self
.query_done
:
500 return self
.child_count
> 0
502 def getData(self
, column
):
503 return self
.data
[column
]
505 # Context-sensitive call graph data model level 2+ item base
507 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase
):
509 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, call_path_id
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
510 super(CallGraphLevelTwoPlusItemBase
, self
).__init
__(glb
, params
, row
, parent_item
)
511 self
.comm_id
= comm_id
512 self
.thread_id
= thread_id
513 self
.call_path_id
= call_path_id
514 self
.insn_cnt
= insn_cnt
515 self
.cyc_cnt
= cyc_cnt
516 self
.branch_count
= branch_count
520 self
.query_done
= True
521 query
= QSqlQuery(self
.glb
.db
)
522 if self
.params
.have_ipc
:
523 ipc_str
= ", SUM(insn_count), SUM(cyc_count)"
526 QueryExec(query
, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str
+ ", SUM(branch_count)"
528 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
529 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
530 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
531 " WHERE parent_call_path_id = " + str(self
.call_path_id
) +
532 " AND comm_id = " + str(self
.comm_id
) +
533 " AND thread_id = " + str(self
.thread_id
) +
534 " GROUP BY call_path_id, name, short_name"
535 " ORDER BY call_path_id")
537 if self
.params
.have_ipc
:
538 insn_cnt
= int(query
.value(5))
539 cyc_cnt
= int(query
.value(6))
540 branch_count
= int(query
.value(7))
544 branch_count
= int(query
.value(5))
545 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
)
546 self
.child_items
.append(child_item
)
547 self
.child_count
+= 1
549 # Context-sensitive call graph data model level three item
551 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase
):
553 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
):
554 super(CallGraphLevelThreeItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, call_path_id
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
)
556 if self
.params
.have_ipc
:
557 insn_pcnt
= PercentToOneDP(insn_cnt
, parent_item
.insn_cnt
)
558 cyc_pcnt
= PercentToOneDP(cyc_cnt
, parent_item
.cyc_cnt
)
559 br_pcnt
= PercentToOneDP(branch_count
, parent_item
.branch_count
)
560 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
561 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
]
563 self
.data
= [ name
, dso
, str(count
), str(time
), PercentToOneDP(time
, parent_item
.time
), str(branch_count
), PercentToOneDP(branch_count
, parent_item
.branch_count
) ]
564 self
.dbid
= call_path_id
566 # Context-sensitive call graph data model level two item
568 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase
):
570 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, pid
, tid
, parent_item
):
571 super(CallGraphLevelTwoItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, 1, 0, 0, 0, 0, parent_item
)
572 if self
.params
.have_ipc
:
573 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", "", "", "", "", "", ""]
575 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", ""]
576 self
.dbid
= thread_id
579 super(CallGraphLevelTwoItem
, self
).Select()
580 for child_item
in self
.child_items
:
581 self
.time
+= child_item
.time
582 self
.insn_cnt
+= child_item
.insn_cnt
583 self
.cyc_cnt
+= child_item
.cyc_cnt
584 self
.branch_count
+= child_item
.branch_count
585 for child_item
in self
.child_items
:
586 child_item
.data
[4] = PercentToOneDP(child_item
.time
, self
.time
)
587 if self
.params
.have_ipc
:
588 child_item
.data
[6] = PercentToOneDP(child_item
.insn_cnt
, self
.insn_cnt
)
589 child_item
.data
[8] = PercentToOneDP(child_item
.cyc_cnt
, self
.cyc_cnt
)
590 child_item
.data
[11] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
592 child_item
.data
[6] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
594 # Context-sensitive call graph data model level one item
596 class CallGraphLevelOneItem(CallGraphLevelItemBase
):
598 def __init__(self
, glb
, params
, row
, comm_id
, comm
, parent_item
):
599 super(CallGraphLevelOneItem
, self
).__init
__(glb
, params
, row
, parent_item
)
600 if self
.params
.have_ipc
:
601 self
.data
= [comm
, "", "", "", "", "", "", "", "", "", "", ""]
603 self
.data
= [comm
, "", "", "", "", "", ""]
607 self
.query_done
= True
608 query
= QSqlQuery(self
.glb
.db
)
609 QueryExec(query
, "SELECT thread_id, pid, tid"
611 " INNER JOIN threads ON thread_id = threads.id"
612 " WHERE comm_id = " + str(self
.dbid
))
614 child_item
= CallGraphLevelTwoItem(self
.glb
, self
.params
, self
.child_count
, self
.dbid
, query
.value(0), query
.value(1), query
.value(2), self
)
615 self
.child_items
.append(child_item
)
616 self
.child_count
+= 1
618 # Context-sensitive call graph data model root item
620 class CallGraphRootItem(CallGraphLevelItemBase
):
622 def __init__(self
, glb
, params
):
623 super(CallGraphRootItem
, self
).__init
__(glb
, params
, 0, None)
625 self
.query_done
= True
627 if IsSelectable(glb
.db
, "comms", columns
= "has_calls"):
628 if_has_calls
= " WHERE has_calls = TRUE"
629 query
= QSqlQuery(glb
.db
)
630 QueryExec(query
, "SELECT id, comm FROM comms" + if_has_calls
)
632 if not query
.value(0):
634 child_item
= CallGraphLevelOneItem(glb
, params
, self
.child_count
, query
.value(0), query
.value(1), self
)
635 self
.child_items
.append(child_item
)
636 self
.child_count
+= 1
638 # Call graph model parameters
640 class CallGraphModelParams():
642 def __init__(self
, glb
, parent
=None):
643 self
.have_ipc
= IsSelectable(glb
.db
, "calls", columns
= "insn_count, cyc_count")
645 # Context-sensitive call graph data model base
647 class CallGraphModelBase(TreeModel
):
649 def __init__(self
, glb
, parent
=None):
650 super(CallGraphModelBase
, self
).__init
__(glb
, CallGraphModelParams(glb
), parent
)
652 def FindSelect(self
, value
, pattern
, query
):
654 # postgresql and sqlite pattern patching differences:
655 # postgresql LIKE is case sensitive but sqlite LIKE is not
656 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
657 # postgresql supports ILIKE which is case insensitive
658 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
659 if not self
.glb
.dbref
.is_sqlite3
:
661 s
= value
.replace("%", "\%")
662 s
= s
.replace("_", "\_")
663 # Translate * and ? into SQL LIKE pattern characters % and _
664 trans
= string
.maketrans("*?", "%_")
665 match
= " LIKE '" + str(s
).translate(trans
) + "'"
667 match
= " GLOB '" + str(value
) + "'"
669 match
= " = '" + str(value
) + "'"
670 self
.DoFindSelect(query
, match
)
672 def Found(self
, query
, found
):
674 return self
.FindPath(query
)
677 def FindValue(self
, value
, pattern
, query
, last_value
, last_pattern
):
678 if last_value
== value
and pattern
== last_pattern
:
679 found
= query
.first()
681 self
.FindSelect(value
, pattern
, query
)
683 return self
.Found(query
, found
)
685 def FindNext(self
, query
):
688 found
= query
.first()
689 return self
.Found(query
, found
)
691 def FindPrev(self
, query
):
692 found
= query
.previous()
695 return self
.Found(query
, found
)
697 def FindThread(self
, c
):
698 if c
.direction
== 0 or c
.value
!= c
.last_value
or c
.pattern
!= c
.last_pattern
:
699 ids
= self
.FindValue(c
.value
, c
.pattern
, c
.query
, c
.last_value
, c
.last_pattern
)
700 elif c
.direction
> 0:
701 ids
= self
.FindNext(c
.query
)
703 ids
= self
.FindPrev(c
.query
)
706 def Find(self
, value
, direction
, pattern
, context
, callback
):
708 def __init__(self
, *x
):
709 self
.value
, self
.direction
, self
.pattern
, self
.query
, self
.last_value
, self
.last_pattern
= x
710 def Update(self
, *x
):
711 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= x
+ (self
.value
, self
.pattern
)
713 context
[0].Update(value
, direction
, pattern
)
715 context
.append(Context(value
, direction
, pattern
, QSqlQuery(self
.glb
.db
), None, None))
716 # Use a thread so the UI is not blocked during the SELECT
717 thread
= Thread(self
.FindThread
, context
[0])
718 thread
.done
.connect(lambda ids
, t
=thread
, c
=callback
: self
.FindDone(t
, c
, ids
), Qt
.QueuedConnection
)
721 def FindDone(self
, thread
, callback
, ids
):
724 # Context-sensitive call graph data model
726 class CallGraphModel(CallGraphModelBase
):
728 def __init__(self
, glb
, parent
=None):
729 super(CallGraphModel
, self
).__init
__(glb
, parent
)
732 return CallGraphRootItem(self
.glb
, self
.params
)
734 def columnCount(self
, parent
=None):
735 if self
.params
.have_ipc
:
740 def columnHeader(self
, column
):
741 if self
.params
.have_ipc
:
742 headers
= ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
744 headers
= ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
745 return headers
[column
]
747 def columnAlignment(self
, column
):
748 if self
.params
.have_ipc
:
749 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
]
751 alignment
= [ Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
]
752 return alignment
[column
]
754 def DoFindSelect(self
, query
, match
):
755 QueryExec(query
, "SELECT call_path_id, comm_id, thread_id"
757 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
758 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
759 " WHERE symbols.name" + match
+
760 " GROUP BY comm_id, thread_id, call_path_id"
761 " ORDER BY comm_id, thread_id, call_path_id")
763 def FindPath(self
, query
):
764 # Turn the query result into a list of ids that the tree view can walk
765 # to open the tree at the right place.
767 parent_id
= query
.value(0)
769 ids
.insert(0, parent_id
)
770 q2
= QSqlQuery(self
.glb
.db
)
771 QueryExec(q2
, "SELECT parent_id"
773 " WHERE id = " + str(parent_id
))
776 parent_id
= q2
.value(0)
777 # The call path root is not used
780 ids
.insert(0, query
.value(2))
781 ids
.insert(0, query
.value(1))
784 # Call tree data model level 2+ item base
786 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase
):
788 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, calls_id
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
789 super(CallTreeLevelTwoPlusItemBase
, self
).__init
__(glb
, params
, row
, parent_item
)
790 self
.comm_id
= comm_id
791 self
.thread_id
= thread_id
792 self
.calls_id
= calls_id
793 self
.insn_cnt
= insn_cnt
794 self
.cyc_cnt
= cyc_cnt
795 self
.branch_count
= branch_count
799 self
.query_done
= True
800 if self
.calls_id
== 0:
801 comm_thread
= " AND comm_id = " + str(self
.comm_id
) + " AND thread_id = " + str(self
.thread_id
)
804 if self
.params
.have_ipc
:
805 ipc_str
= ", insn_count, cyc_count"
808 query
= QSqlQuery(self
.glb
.db
)
809 QueryExec(query
, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str
+ ", branch_count"
811 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
812 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
813 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
814 " WHERE calls.parent_id = " + str(self
.calls_id
) + comm_thread
+
815 " ORDER BY call_time, calls.id")
817 if self
.params
.have_ipc
:
818 insn_cnt
= int(query
.value(5))
819 cyc_cnt
= int(query
.value(6))
820 branch_count
= int(query
.value(7))
824 branch_count
= int(query
.value(5))
825 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
)
826 self
.child_items
.append(child_item
)
827 self
.child_count
+= 1
829 # Call tree data model level three item
831 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase
):
833 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, calls_id
, name
, dso
, count
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
):
834 super(CallTreeLevelThreeItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, calls_id
, time
, insn_cnt
, cyc_cnt
, branch_count
, parent_item
)
836 if self
.params
.have_ipc
:
837 insn_pcnt
= PercentToOneDP(insn_cnt
, parent_item
.insn_cnt
)
838 cyc_pcnt
= PercentToOneDP(cyc_cnt
, parent_item
.cyc_cnt
)
839 br_pcnt
= PercentToOneDP(branch_count
, parent_item
.branch_count
)
840 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
841 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
]
843 self
.data
= [ name
, dso
, str(count
), str(time
), PercentToOneDP(time
, parent_item
.time
), str(branch_count
), PercentToOneDP(branch_count
, parent_item
.branch_count
) ]
846 # Call tree data model level two item
848 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase
):
850 def __init__(self
, glb
, params
, row
, comm_id
, thread_id
, pid
, tid
, parent_item
):
851 super(CallTreeLevelTwoItem
, self
).__init
__(glb
, params
, row
, comm_id
, thread_id
, 0, 0, 0, 0, 0, parent_item
)
852 if self
.params
.have_ipc
:
853 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", "", "", "", "", "", ""]
855 self
.data
= [str(pid
) + ":" + str(tid
), "", "", "", "", "", ""]
856 self
.dbid
= thread_id
859 super(CallTreeLevelTwoItem
, self
).Select()
860 for child_item
in self
.child_items
:
861 self
.time
+= child_item
.time
862 self
.insn_cnt
+= child_item
.insn_cnt
863 self
.cyc_cnt
+= child_item
.cyc_cnt
864 self
.branch_count
+= child_item
.branch_count
865 for child_item
in self
.child_items
:
866 child_item
.data
[4] = PercentToOneDP(child_item
.time
, self
.time
)
867 if self
.params
.have_ipc
:
868 child_item
.data
[6] = PercentToOneDP(child_item
.insn_cnt
, self
.insn_cnt
)
869 child_item
.data
[8] = PercentToOneDP(child_item
.cyc_cnt
, self
.cyc_cnt
)
870 child_item
.data
[11] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
872 child_item
.data
[6] = PercentToOneDP(child_item
.branch_count
, self
.branch_count
)
874 # Call tree data model level one item
876 class CallTreeLevelOneItem(CallGraphLevelItemBase
):
878 def __init__(self
, glb
, params
, row
, comm_id
, comm
, parent_item
):
879 super(CallTreeLevelOneItem
, self
).__init
__(glb
, params
, row
, parent_item
)
880 if self
.params
.have_ipc
:
881 self
.data
= [comm
, "", "", "", "", "", "", "", "", "", "", ""]
883 self
.data
= [comm
, "", "", "", "", "", ""]
887 self
.query_done
= True
888 query
= QSqlQuery(self
.glb
.db
)
889 QueryExec(query
, "SELECT thread_id, pid, tid"
891 " INNER JOIN threads ON thread_id = threads.id"
892 " WHERE comm_id = " + str(self
.dbid
))
894 child_item
= CallTreeLevelTwoItem(self
.glb
, self
.params
, self
.child_count
, self
.dbid
, query
.value(0), query
.value(1), query
.value(2), self
)
895 self
.child_items
.append(child_item
)
896 self
.child_count
+= 1
898 # Call tree data model root item
900 class CallTreeRootItem(CallGraphLevelItemBase
):
902 def __init__(self
, glb
, params
):
903 super(CallTreeRootItem
, self
).__init
__(glb
, params
, 0, None)
905 self
.query_done
= True
907 if IsSelectable(glb
.db
, "comms", columns
= "has_calls"):
908 if_has_calls
= " WHERE has_calls = TRUE"
909 query
= QSqlQuery(glb
.db
)
910 QueryExec(query
, "SELECT id, comm FROM comms" + if_has_calls
)
912 if not query
.value(0):
914 child_item
= CallTreeLevelOneItem(glb
, params
, self
.child_count
, query
.value(0), query
.value(1), self
)
915 self
.child_items
.append(child_item
)
916 self
.child_count
+= 1
918 # Call Tree data model
920 class CallTreeModel(CallGraphModelBase
):
922 def __init__(self
, glb
, parent
=None):
923 super(CallTreeModel
, self
).__init
__(glb
, parent
)
926 return CallTreeRootItem(self
.glb
, self
.params
)
928 def columnCount(self
, parent
=None):
929 if self
.params
.have_ipc
:
934 def columnHeader(self
, column
):
935 if self
.params
.have_ipc
:
936 headers
= ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
938 headers
= ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
939 return headers
[column
]
941 def columnAlignment(self
, column
):
942 if self
.params
.have_ipc
:
943 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
]
945 alignment
= [ Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignRight
]
946 return alignment
[column
]
948 def DoFindSelect(self
, query
, match
):
949 QueryExec(query
, "SELECT calls.id, comm_id, thread_id"
951 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
952 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
953 " WHERE symbols.name" + match
+
954 " ORDER BY comm_id, thread_id, call_time, calls.id")
956 def FindPath(self
, query
):
957 # Turn the query result into a list of ids that the tree view can walk
958 # to open the tree at the right place.
960 parent_id
= query
.value(0)
962 ids
.insert(0, parent_id
)
963 q2
= QSqlQuery(self
.glb
.db
)
964 QueryExec(q2
, "SELECT parent_id"
966 " WHERE id = " + str(parent_id
))
969 parent_id
= q2
.value(0)
970 ids
.insert(0, query
.value(2))
971 ids
.insert(0, query
.value(1))
974 # Vertical widget layout
978 def __init__(self
, w1
, w2
, w3
=None):
979 self
.vbox
= QWidget()
980 self
.vbox
.setLayout(QVBoxLayout())
982 self
.vbox
.layout().setContentsMargins(0, 0, 0, 0)
984 self
.vbox
.layout().addWidget(w1
)
985 self
.vbox
.layout().addWidget(w2
)
987 self
.vbox
.layout().addWidget(w3
)
994 class TreeWindowBase(QMdiSubWindow
):
996 def __init__(self
, parent
=None):
997 super(TreeWindowBase
, self
).__init
__(parent
)
1000 self
.find_bar
= None
1002 self
.view
= QTreeView()
1003 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
1004 self
.view
.CopyCellsToClipboard
= CopyTreeCellsToClipboard
1006 self
.context_menu
= TreeContextMenu(self
.view
)
1008 def DisplayFound(self
, ids
):
1011 parent
= QModelIndex()
1014 n
= self
.model
.rowCount(parent
)
1015 for row
in xrange(n
):
1016 child
= self
.model
.index(row
, 0, parent
)
1017 if child
.internalPointer().dbid
== dbid
:
1019 self
.view
.setCurrentIndex(child
)
1026 def Find(self
, value
, direction
, pattern
, context
):
1027 self
.view
.setFocus()
1028 self
.find_bar
.Busy()
1029 self
.model
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
1031 def FindDone(self
, ids
):
1033 if not self
.DisplayFound(ids
):
1035 self
.find_bar
.Idle()
1037 self
.find_bar
.NotFound()
1040 # Context-sensitive call graph window
1042 class CallGraphWindow(TreeWindowBase
):
1044 def __init__(self
, glb
, parent
=None):
1045 super(CallGraphWindow
, self
).__init
__(parent
)
1047 self
.model
= LookupCreateModel("Context-Sensitive Call Graph", lambda x
=glb
: CallGraphModel(x
))
1049 self
.view
.setModel(self
.model
)
1051 for c
, w
in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1052 self
.view
.setColumnWidth(c
, w
)
1054 self
.find_bar
= FindBar(self
, self
)
1056 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget())
1058 self
.setWidget(self
.vbox
.Widget())
1060 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Context-Sensitive Call Graph")
1064 class CallTreeWindow(TreeWindowBase
):
1066 def __init__(self
, glb
, parent
=None):
1067 super(CallTreeWindow
, self
).__init
__(parent
)
1069 self
.model
= LookupCreateModel("Call Tree", lambda x
=glb
: CallTreeModel(x
))
1071 self
.view
.setModel(self
.model
)
1073 for c
, w
in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1074 self
.view
.setColumnWidth(c
, w
)
1076 self
.find_bar
= FindBar(self
, self
)
1078 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget())
1080 self
.setWidget(self
.vbox
.Widget())
1082 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Call Tree")
1084 # Child data item finder
1086 class ChildDataItemFinder():
1088 def __init__(self
, root
):
1090 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= (None,) * 5
1094 def FindSelect(self
):
1097 pattern
= re
.compile(self
.value
)
1098 for child
in self
.root
.child_items
:
1099 for column_data
in child
.data
:
1100 if re
.search(pattern
, str(column_data
)) is not None:
1101 self
.rows
.append(child
.row
)
1104 for child
in self
.root
.child_items
:
1105 for column_data
in child
.data
:
1106 if self
.value
in str(column_data
):
1107 self
.rows
.append(child
.row
)
1110 def FindValue(self
):
1112 if self
.last_value
!= self
.value
or self
.pattern
!= self
.last_pattern
:
1114 if not len(self
.rows
):
1116 return self
.rows
[self
.pos
]
1118 def FindThread(self
):
1119 if self
.direction
== 0 or self
.value
!= self
.last_value
or self
.pattern
!= self
.last_pattern
:
1120 row
= self
.FindValue()
1121 elif len(self
.rows
):
1122 if self
.direction
> 0:
1124 if self
.pos
>= len(self
.rows
):
1129 self
.pos
= len(self
.rows
) - 1
1130 row
= self
.rows
[self
.pos
]
1135 def Find(self
, value
, direction
, pattern
, context
, callback
):
1136 self
.value
, self
.direction
, self
.pattern
, self
.last_value
, self
.last_pattern
= (value
, direction
,pattern
, self
.value
, self
.pattern
)
1137 # Use a thread so the UI is not blocked
1138 thread
= Thread(self
.FindThread
)
1139 thread
.done
.connect(lambda row
, t
=thread
, c
=callback
: self
.FindDone(t
, c
, row
), Qt
.QueuedConnection
)
1142 def FindDone(self
, thread
, callback
, row
):
1145 # Number of database records to fetch in one go
1147 glb_chunk_sz
= 10000
1149 # Background process for SQL data fetcher
1151 class SQLFetcherProcess():
1153 def __init__(self
, dbref
, sql
, buffer, head
, tail
, fetch_count
, fetching_done
, process_target
, wait_event
, fetched_event
, prep
):
1154 # Need a unique connection name
1155 conn_name
= "SQLFetcher" + str(os
.getpid())
1156 self
.db
, dbname
= dbref
.Open(conn_name
)
1158 self
.buffer = buffer
1161 self
.fetch_count
= fetch_count
1162 self
.fetching_done
= fetching_done
1163 self
.process_target
= process_target
1164 self
.wait_event
= wait_event
1165 self
.fetched_event
= fetched_event
1167 self
.query
= QSqlQuery(self
.db
)
1168 self
.query_limit
= 0 if "$$last_id$$" in sql
else 2
1172 self
.local_head
= self
.head
.value
1173 self
.local_tail
= self
.tail
.value
1176 if self
.query_limit
:
1177 if self
.query_limit
== 1:
1179 self
.query_limit
-= 1
1180 stmt
= self
.sql
.replace("$$last_id$$", str(self
.last_id
))
1181 QueryExec(self
.query
, stmt
)
1184 if not self
.query
.next():
1186 if not self
.query
.next():
1188 self
.last_id
= self
.query
.value(0)
1189 return self
.prep(self
.query
)
1191 def WaitForTarget(self
):
1193 self
.wait_event
.clear()
1194 target
= self
.process_target
.value
1195 if target
> self
.fetched
or target
< 0:
1197 self
.wait_event
.wait()
1200 def HasSpace(self
, sz
):
1201 if self
.local_tail
<= self
.local_head
:
1202 space
= len(self
.buffer) - self
.local_head
1205 if space
>= glb_nsz
:
1206 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1207 nd
= pickle
.dumps(0, pickle
.HIGHEST_PROTOCOL
)
1208 self
.buffer[self
.local_head
: self
.local_head
+ len(nd
)] = nd
1210 if self
.local_tail
- self
.local_head
> sz
:
1214 def WaitForSpace(self
, sz
):
1215 if self
.HasSpace(sz
):
1218 self
.wait_event
.clear()
1219 self
.local_tail
= self
.tail
.value
1220 if self
.HasSpace(sz
):
1222 self
.wait_event
.wait()
1224 def AddToBuffer(self
, obj
):
1225 d
= pickle
.dumps(obj
, pickle
.HIGHEST_PROTOCOL
)
1227 nd
= pickle
.dumps(n
, pickle
.HIGHEST_PROTOCOL
)
1229 self
.WaitForSpace(sz
)
1230 pos
= self
.local_head
1231 self
.buffer[pos
: pos
+ len(nd
)] = nd
1232 self
.buffer[pos
+ glb_nsz
: pos
+ sz
] = d
1233 self
.local_head
+= sz
1235 def FetchBatch(self
, batch_size
):
1237 while batch_size
> fetched
:
1242 self
.AddToBuffer(obj
)
1245 self
.fetched
+= fetched
1246 with self
.fetch_count
.get_lock():
1247 self
.fetch_count
.value
+= fetched
1248 self
.head
.value
= self
.local_head
1249 self
.fetched_event
.set()
1253 target
= self
.WaitForTarget()
1256 batch_size
= min(glb_chunk_sz
, target
- self
.fetched
)
1257 self
.FetchBatch(batch_size
)
1258 self
.fetching_done
.value
= True
1259 self
.fetched_event
.set()
1261 def SQLFetcherFn(*x
):
1262 process
= SQLFetcherProcess(*x
)
1267 class SQLFetcher(QObject
):
1269 done
= Signal(object)
1271 def __init__(self
, glb
, sql
, prep
, process_data
, parent
=None):
1272 super(SQLFetcher
, self
).__init
__(parent
)
1273 self
.process_data
= process_data
1276 self
.last_target
= 0
1278 self
.buffer_size
= 16 * 1024 * 1024
1279 self
.buffer = Array(c_char
, self
.buffer_size
, lock
=False)
1280 self
.head
= Value(c_longlong
)
1281 self
.tail
= Value(c_longlong
)
1283 self
.fetch_count
= Value(c_longlong
)
1284 self
.fetching_done
= Value(c_bool
)
1286 self
.process_target
= Value(c_longlong
)
1287 self
.wait_event
= Event()
1288 self
.fetched_event
= Event()
1289 glb
.AddInstanceToShutdownOnExit(self
)
1290 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
))
1291 self
.process
.start()
1292 self
.thread
= Thread(self
.Thread
)
1293 self
.thread
.done
.connect(self
.ProcessData
, Qt
.QueuedConnection
)
1297 # Tell the thread and process to exit
1298 self
.process_target
.value
= -1
1299 self
.wait_event
.set()
1301 self
.fetching_done
.value
= True
1302 self
.fetched_event
.set()
1308 self
.fetched_event
.clear()
1309 fetch_count
= self
.fetch_count
.value
1310 if fetch_count
!= self
.last_count
:
1312 if self
.fetching_done
.value
:
1315 self
.fetched_event
.wait()
1316 count
= fetch_count
- self
.last_count
1317 self
.last_count
= fetch_count
1318 self
.fetched
+= count
1321 def Fetch(self
, nr
):
1323 # -1 inidcates there are no more
1325 result
= self
.fetched
1326 extra
= result
+ nr
- self
.target
1328 self
.target
+= extra
1329 # process_target < 0 indicates shutting down
1330 if self
.process_target
.value
>= 0:
1331 self
.process_target
.value
= self
.target
1332 self
.wait_event
.set()
1335 def RemoveFromBuffer(self
):
1336 pos
= self
.local_tail
1337 if len(self
.buffer) - pos
< glb_nsz
:
1339 n
= pickle
.loads(self
.buffer[pos
: pos
+ glb_nsz
])
1342 n
= pickle
.loads(self
.buffer[0 : glb_nsz
])
1344 obj
= pickle
.loads(self
.buffer[pos
: pos
+ n
])
1345 self
.local_tail
= pos
+ n
1348 def ProcessData(self
, count
):
1349 for i
in xrange(count
):
1350 obj
= self
.RemoveFromBuffer()
1351 self
.process_data(obj
)
1352 self
.tail
.value
= self
.local_tail
1353 self
.wait_event
.set()
1354 self
.done
.emit(count
)
1356 # Fetch more records bar
1358 class FetchMoreRecordsBar():
1360 def __init__(self
, model
, parent
):
1363 self
.label
= QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz
) + ") to fetch:")
1364 self
.label
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
1366 self
.fetch_count
= QSpinBox()
1367 self
.fetch_count
.setRange(1, 1000000)
1368 self
.fetch_count
.setValue(10)
1369 self
.fetch_count
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
1371 self
.fetch
= QPushButton("Go!")
1372 self
.fetch
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
1373 self
.fetch
.released
.connect(self
.FetchMoreRecords
)
1375 self
.progress
= QProgressBar()
1376 self
.progress
.setRange(0, 100)
1377 self
.progress
.hide()
1379 self
.done_label
= QLabel("All records fetched")
1380 self
.done_label
.hide()
1382 self
.spacer
= QLabel("")
1384 self
.close_button
= QToolButton()
1385 self
.close_button
.setIcon(parent
.style().standardIcon(QStyle
.SP_DockWidgetCloseButton
))
1386 self
.close_button
.released
.connect(self
.Deactivate
)
1388 self
.hbox
= QHBoxLayout()
1389 self
.hbox
.setContentsMargins(0, 0, 0, 0)
1391 self
.hbox
.addWidget(self
.label
)
1392 self
.hbox
.addWidget(self
.fetch_count
)
1393 self
.hbox
.addWidget(self
.fetch
)
1394 self
.hbox
.addWidget(self
.spacer
)
1395 self
.hbox
.addWidget(self
.progress
)
1396 self
.hbox
.addWidget(self
.done_label
)
1397 self
.hbox
.addWidget(self
.close_button
)
1399 self
.bar
= QWidget()
1400 self
.bar
.setLayout(self
.hbox
)
1403 self
.in_progress
= False
1404 self
.model
.progress
.connect(self
.Progress
)
1408 if not model
.HasMoreRecords():
1416 self
.fetch
.setFocus()
1418 def Deactivate(self
):
1421 def Enable(self
, enable
):
1422 self
.fetch
.setEnabled(enable
)
1423 self
.fetch_count
.setEnabled(enable
)
1429 self
.progress
.show()
1432 self
.in_progress
= False
1434 self
.progress
.hide()
1439 return self
.fetch_count
.value() * glb_chunk_sz
1445 self
.fetch_count
.hide()
1448 self
.done_label
.show()
1450 def Progress(self
, count
):
1451 if self
.in_progress
:
1453 percent
= ((count
- self
.start
) * 100) / self
.Target()
1457 self
.progress
.setValue(percent
)
1459 # Count value of zero means no more records
1462 def FetchMoreRecords(self
):
1465 self
.progress
.setValue(0)
1467 self
.in_progress
= True
1468 self
.start
= self
.model
.FetchMoreRecords(self
.Target())
1470 # Brance data model level two item
1472 class BranchLevelTwoItem():
1474 def __init__(self
, row
, col
, text
, parent_item
):
1476 self
.parent_item
= parent_item
1477 self
.data
= [""] * (col
+ 1)
1478 self
.data
[col
] = text
1481 def getParentItem(self
):
1482 return self
.parent_item
1487 def childCount(self
):
1490 def hasChildren(self
):
1493 def getData(self
, column
):
1494 return self
.data
[column
]
1496 # Brance data model level one item
1498 class BranchLevelOneItem():
1500 def __init__(self
, glb
, row
, data
, parent_item
):
1503 self
.parent_item
= parent_item
1504 self
.child_count
= 0
1505 self
.child_items
= []
1506 self
.data
= data
[1:]
1509 self
.query_done
= False
1510 self
.br_col
= len(self
.data
) - 1
1512 def getChildItem(self
, row
):
1513 return self
.child_items
[row
]
1515 def getParentItem(self
):
1516 return self
.parent_item
1522 self
.query_done
= True
1524 if not self
.glb
.have_disassembler
:
1527 query
= QSqlQuery(self
.glb
.db
)
1529 QueryExec(query
, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1531 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1532 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1533 " WHERE samples.id = " + str(self
.dbid
))
1534 if not query
.next():
1536 cpu
= query
.value(0)
1537 dso
= query
.value(1)
1538 sym
= query
.value(2)
1539 if dso
== 0 or sym
== 0:
1541 off
= query
.value(3)
1542 short_name
= query
.value(4)
1543 long_name
= query
.value(5)
1544 build_id
= query
.value(6)
1545 sym_start
= query
.value(7)
1548 QueryExec(query
, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1550 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1551 " WHERE samples.id > " + str(self
.dbid
) + " AND cpu = " + str(cpu
) +
1552 " ORDER BY samples.id"
1554 if not query
.next():
1556 if query
.value(0) != dso
:
1557 # Cannot disassemble from one dso to another
1559 bsym
= query
.value(1)
1560 boff
= query
.value(2)
1561 bsym_start
= query
.value(3)
1564 tot
= bsym_start
+ boff
+ 1 - sym_start
- off
1565 if tot
<= 0 or tot
> 16384:
1568 inst
= self
.glb
.disassembler
.Instruction()
1569 f
= self
.glb
.FileFromNamesAndBuildId(short_name
, long_name
, build_id
)
1572 mode
= 0 if Is64Bit(f
) else 1
1573 self
.glb
.disassembler
.SetMode(inst
, mode
)
1576 buf
= create_string_buffer(tot
+ 16)
1577 f
.seek(sym_start
+ off
)
1578 buf
.value
= f
.read(buf_sz
)
1579 buf_ptr
= addressof(buf
)
1582 cnt
, text
= self
.glb
.disassembler
.DisassembleOne(inst
, buf_ptr
, buf_sz
, ip
)
1584 byte_str
= tohex(ip
).rjust(16)
1585 for k
in xrange(cnt
):
1586 byte_str
+= " %02x" % ord(buf
[i
])
1591 self
.child_items
.append(BranchLevelTwoItem(0, self
.br_col
, byte_str
+ " " + text
, self
))
1592 self
.child_count
+= 1
1600 def childCount(self
):
1601 if not self
.query_done
:
1603 if not self
.child_count
:
1605 return self
.child_count
1607 def hasChildren(self
):
1608 if not self
.query_done
:
1610 return self
.child_count
> 0
1612 def getData(self
, column
):
1613 return self
.data
[column
]
1615 # Brance data model root item
1617 class BranchRootItem():
1620 self
.child_count
= 0
1621 self
.child_items
= []
1624 def getChildItem(self
, row
):
1625 return self
.child_items
[row
]
1627 def getParentItem(self
):
1633 def childCount(self
):
1634 return self
.child_count
1636 def hasChildren(self
):
1637 return self
.child_count
> 0
1639 def getData(self
, column
):
1642 # Calculate instructions per cycle
1644 def CalcIPC(cyc_cnt
, insn_cnt
):
1645 if cyc_cnt
and insn_cnt
:
1646 ipc
= Decimal(float(insn_cnt
) / cyc_cnt
)
1647 ipc
= str(ipc
.quantize(Decimal(".01"), rounding
=ROUND_HALF_UP
))
1652 # Branch data preparation
1654 def BranchDataPrepBr(query
, data
):
1655 data
.append(tohex(query
.value(8)).rjust(16) + " " + query
.value(9) + offstr(query
.value(10)) +
1656 " (" + dsoname(query
.value(11)) + ")" + " -> " +
1657 tohex(query
.value(12)) + " " + query
.value(13) + offstr(query
.value(14)) +
1658 " (" + dsoname(query
.value(15)) + ")")
1660 def BranchDataPrepIPC(query
, data
):
1661 insn_cnt
= query
.value(16)
1662 cyc_cnt
= query
.value(17)
1663 ipc
= CalcIPC(cyc_cnt
, insn_cnt
)
1664 data
.append(insn_cnt
)
1665 data
.append(cyc_cnt
)
1668 def BranchDataPrep(query
):
1670 for i
in xrange(0, 8):
1671 data
.append(query
.value(i
))
1672 BranchDataPrepBr(query
, data
)
1675 def BranchDataPrepWA(query
):
1677 data
.append(query
.value(0))
1678 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1679 data
.append("{:>19}".format(query
.value(1)))
1680 for i
in xrange(2, 8):
1681 data
.append(query
.value(i
))
1682 BranchDataPrepBr(query
, data
)
1685 def BranchDataWithIPCPrep(query
):
1687 for i
in xrange(0, 8):
1688 data
.append(query
.value(i
))
1689 BranchDataPrepIPC(query
, data
)
1690 BranchDataPrepBr(query
, data
)
1693 def BranchDataWithIPCPrepWA(query
):
1695 data
.append(query
.value(0))
1696 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1697 data
.append("{:>19}".format(query
.value(1)))
1698 for i
in xrange(2, 8):
1699 data
.append(query
.value(i
))
1700 BranchDataPrepIPC(query
, data
)
1701 BranchDataPrepBr(query
, data
)
1706 class BranchModel(TreeModel
):
1708 progress
= Signal(object)
1710 def __init__(self
, glb
, event_id
, where_clause
, parent
=None):
1711 super(BranchModel
, self
).__init
__(glb
, None, parent
)
1712 self
.event_id
= event_id
1715 self
.have_ipc
= IsSelectable(glb
.db
, "samples", columns
= "insn_count, cyc_count")
1717 select_ipc
= ", insn_count, cyc_count"
1718 prep_fn
= BranchDataWithIPCPrep
1719 prep_wa_fn
= BranchDataWithIPCPrepWA
1722 prep_fn
= BranchDataPrep
1723 prep_wa_fn
= BranchDataPrepWA
1724 sql
= ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1725 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1726 " ip, symbols.name, sym_offset, dsos.short_name,"
1727 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1730 " INNER JOIN comms ON comm_id = comms.id"
1731 " INNER JOIN threads ON thread_id = threads.id"
1732 " INNER JOIN branch_types ON branch_type = branch_types.id"
1733 " INNER JOIN symbols ON symbol_id = symbols.id"
1734 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1735 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1736 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1737 " WHERE samples.id > $$last_id$$" + where_clause
+
1738 " AND evsel_id = " + str(self
.event_id
) +
1739 " ORDER BY samples.id"
1740 " LIMIT " + str(glb_chunk_sz
))
1741 if pyside_version_1
and sys
.version_info
[0] == 3:
1745 self
.fetcher
= SQLFetcher(glb
, sql
, prep
, self
.AddSample
)
1746 self
.fetcher
.done
.connect(self
.Update
)
1747 self
.fetcher
.Fetch(glb_chunk_sz
)
1750 return BranchRootItem()
1752 def columnCount(self
, parent
=None):
1758 def columnHeader(self
, column
):
1760 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column
]
1762 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column
]
1764 def columnFont(self
, column
):
1769 if column
!= br_col
:
1771 return QFont("Monospace")
1773 def DisplayData(self
, item
, index
):
1775 self
.FetchIfNeeded(item
.row
)
1776 return item
.getData(index
.column())
1778 def AddSample(self
, data
):
1779 child
= BranchLevelOneItem(self
.glb
, self
.populated
, data
, self
.root
)
1780 self
.root
.child_items
.append(child
)
1783 def Update(self
, fetched
):
1786 self
.progress
.emit(0)
1787 child_count
= self
.root
.child_count
1788 count
= self
.populated
- child_count
1790 parent
= QModelIndex()
1791 self
.beginInsertRows(parent
, child_count
, child_count
+ count
- 1)
1792 self
.insertRows(child_count
, count
, parent
)
1793 self
.root
.child_count
+= count
1794 self
.endInsertRows()
1795 self
.progress
.emit(self
.root
.child_count
)
1797 def FetchMoreRecords(self
, count
):
1798 current
= self
.root
.child_count
1800 self
.fetcher
.Fetch(count
)
1802 self
.progress
.emit(0)
1805 def HasMoreRecords(self
):
1812 def __init__(self
, name
= "", where_clause
= "", limit
= ""):
1814 self
.where_clause
= where_clause
1818 return str(self
.where_clause
+ ";" + self
.limit
)
1822 class BranchWindow(QMdiSubWindow
):
1824 def __init__(self
, glb
, event_id
, report_vars
, parent
=None):
1825 super(BranchWindow
, self
).__init
__(parent
)
1827 model_name
= "Branch Events " + str(event_id
) + " " + report_vars
.UniqueId()
1829 self
.model
= LookupCreateModel(model_name
, lambda: BranchModel(glb
, event_id
, report_vars
.where_clause
))
1831 self
.view
= QTreeView()
1832 self
.view
.setUniformRowHeights(True)
1833 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
1834 self
.view
.CopyCellsToClipboard
= CopyTreeCellsToClipboard
1835 self
.view
.setModel(self
.model
)
1837 self
.ResizeColumnsToContents()
1839 self
.context_menu
= TreeContextMenu(self
.view
)
1841 self
.find_bar
= FindBar(self
, self
, True)
1843 self
.finder
= ChildDataItemFinder(self
.model
.root
)
1845 self
.fetch_bar
= FetchMoreRecordsBar(self
.model
, self
)
1847 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
1849 self
.setWidget(self
.vbox
.Widget())
1851 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, report_vars
.name
+ " Branch Events")
1853 def ResizeColumnToContents(self
, column
, n
):
1854 # Using the view's resizeColumnToContents() here is extrememly slow
1855 # so implement a crude alternative
1856 mm
= "MM" if column
else "MMMM"
1857 font
= self
.view
.font()
1858 metrics
= QFontMetrics(font
)
1860 for row
in xrange(n
):
1861 val
= self
.model
.root
.child_items
[row
].data
[column
]
1862 len = metrics
.width(str(val
) + mm
)
1863 max = len if len > max else max
1864 val
= self
.model
.columnHeader(column
)
1865 len = metrics
.width(str(val
) + mm
)
1866 max = len if len > max else max
1867 self
.view
.setColumnWidth(column
, max)
1869 def ResizeColumnsToContents(self
):
1870 n
= min(self
.model
.root
.child_count
, 100)
1872 # No data yet, so connect a signal to notify when there is
1873 self
.model
.rowsInserted
.connect(self
.UpdateColumnWidths
)
1875 columns
= self
.model
.columnCount()
1876 for i
in xrange(columns
):
1877 self
.ResizeColumnToContents(i
, n
)
1879 def UpdateColumnWidths(self
, *x
):
1880 # This only needs to be done once, so disconnect the signal now
1881 self
.model
.rowsInserted
.disconnect(self
.UpdateColumnWidths
)
1882 self
.ResizeColumnsToContents()
1884 def Find(self
, value
, direction
, pattern
, context
):
1885 self
.view
.setFocus()
1886 self
.find_bar
.Busy()
1887 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
1889 def FindDone(self
, row
):
1890 self
.find_bar
.Idle()
1892 self
.view
.setCurrentIndex(self
.model
.index(row
, 0, QModelIndex()))
1894 self
.find_bar
.NotFound()
1896 # Line edit data item
1898 class LineEditDataItem(object):
1900 def __init__(self
, glb
, label
, placeholder_text
, parent
, id = "", default
= ""):
1903 self
.placeholder_text
= placeholder_text
1904 self
.parent
= parent
1907 self
.value
= default
1909 self
.widget
= QLineEdit(default
)
1910 self
.widget
.editingFinished
.connect(self
.Validate
)
1911 self
.widget
.textChanged
.connect(self
.Invalidate
)
1914 self
.validated
= True
1916 if placeholder_text
:
1917 self
.widget
.setPlaceholderText(placeholder_text
)
1919 def TurnTextRed(self
):
1921 palette
= QPalette()
1922 palette
.setColor(QPalette
.Text
,Qt
.red
)
1923 self
.widget
.setPalette(palette
)
1926 def TurnTextNormal(self
):
1928 palette
= QPalette()
1929 self
.widget
.setPalette(palette
)
1932 def InvalidValue(self
, value
):
1935 self
.error
= self
.label
+ " invalid value '" + value
+ "'"
1936 self
.parent
.ShowMessage(self
.error
)
1938 def Invalidate(self
):
1939 self
.validated
= False
1941 def DoValidate(self
, input_string
):
1942 self
.value
= input_string
.strip()
1945 self
.validated
= True
1947 self
.TurnTextNormal()
1948 self
.parent
.ClearMessage()
1949 input_string
= self
.widget
.text()
1950 if not len(input_string
.strip()):
1953 self
.DoValidate(input_string
)
1956 if not self
.validated
:
1959 self
.parent
.ShowMessage(self
.error
)
1963 def IsNumber(self
, value
):
1968 return str(x
) == value
1970 # Non-negative integer ranges dialog data item
1972 class NonNegativeIntegerRangesDataItem(LineEditDataItem
):
1974 def __init__(self
, glb
, label
, placeholder_text
, column_name
, parent
):
1975 super(NonNegativeIntegerRangesDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
1977 self
.column_name
= column_name
1979 def DoValidate(self
, input_string
):
1982 for value
in [x
.strip() for x
in input_string
.split(",")]:
1984 vrange
= value
.split("-")
1985 if len(vrange
) != 2 or not self
.IsNumber(vrange
[0]) or not self
.IsNumber(vrange
[1]):
1986 return self
.InvalidValue(value
)
1987 ranges
.append(vrange
)
1989 if not self
.IsNumber(value
):
1990 return self
.InvalidValue(value
)
1991 singles
.append(value
)
1992 ranges
= [("(" + self
.column_name
+ " >= " + r
[0] + " AND " + self
.column_name
+ " <= " + r
[1] + ")") for r
in ranges
]
1994 ranges
.append(self
.column_name
+ " IN (" + ",".join(singles
) + ")")
1995 self
.value
= " OR ".join(ranges
)
1997 # Positive integer dialog data item
1999 class PositiveIntegerDataItem(LineEditDataItem
):
2001 def __init__(self
, glb
, label
, placeholder_text
, parent
, id = "", default
= ""):
2002 super(PositiveIntegerDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
, id, default
)
2004 def DoValidate(self
, input_string
):
2005 if not self
.IsNumber(input_string
.strip()):
2006 return self
.InvalidValue(input_string
)
2007 value
= int(input_string
.strip())
2009 return self
.InvalidValue(input_string
)
2010 self
.value
= str(value
)
2012 # Dialog data item converted and validated using a SQL table
2014 class SQLTableDataItem(LineEditDataItem
):
2016 def __init__(self
, glb
, label
, placeholder_text
, table_name
, match_column
, column_name1
, column_name2
, parent
):
2017 super(SQLTableDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
2019 self
.table_name
= table_name
2020 self
.match_column
= match_column
2021 self
.column_name1
= column_name1
2022 self
.column_name2
= column_name2
2024 def ValueToIds(self
, value
):
2026 query
= QSqlQuery(self
.glb
.db
)
2027 stmt
= "SELECT id FROM " + self
.table_name
+ " WHERE " + self
.match_column
+ " = '" + value
+ "'"
2028 ret
= query
.exec_(stmt
)
2031 ids
.append(str(query
.value(0)))
2034 def DoValidate(self
, input_string
):
2036 for value
in [x
.strip() for x
in input_string
.split(",")]:
2037 ids
= self
.ValueToIds(value
)
2041 return self
.InvalidValue(value
)
2042 self
.value
= self
.column_name1
+ " IN (" + ",".join(all_ids
) + ")"
2043 if self
.column_name2
:
2044 self
.value
= "( " + self
.value
+ " OR " + self
.column_name2
+ " IN (" + ",".join(all_ids
) + ") )"
2046 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
2048 class SampleTimeRangesDataItem(LineEditDataItem
):
2050 def __init__(self
, glb
, label
, placeholder_text
, column_name
, parent
):
2051 self
.column_name
= column_name
2055 self
.last_time
= 2 ** 64
2057 query
= QSqlQuery(glb
.db
)
2058 QueryExec(query
, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
2060 self
.last_id
= int(query
.value(0))
2061 self
.last_time
= int(query
.value(1))
2062 QueryExec(query
, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
2064 self
.first_time
= int(query
.value(0))
2065 if placeholder_text
:
2066 placeholder_text
+= ", between " + str(self
.first_time
) + " and " + str(self
.last_time
)
2068 super(SampleTimeRangesDataItem
, self
).__init
__(glb
, label
, placeholder_text
, parent
)
2070 def IdBetween(self
, query
, lower_id
, higher_id
, order
):
2071 QueryExec(query
, "SELECT id FROM samples WHERE id > " + str(lower_id
) + " AND id < " + str(higher_id
) + " ORDER BY id " + order
+ " LIMIT 1")
2073 return True, int(query
.value(0))
2077 def BinarySearchTime(self
, lower_id
, higher_id
, target_time
, get_floor
):
2078 query
= QSqlQuery(self
.glb
.db
)
2080 next_id
= int((lower_id
+ higher_id
) / 2)
2081 QueryExec(query
, "SELECT time FROM samples WHERE id = " + str(next_id
))
2082 if not query
.next():
2083 ok
, dbid
= self
.IdBetween(query
, lower_id
, next_id
, "DESC")
2085 ok
, dbid
= self
.IdBetween(query
, next_id
, higher_id
, "")
2087 return str(higher_id
)
2089 QueryExec(query
, "SELECT time FROM samples WHERE id = " + str(next_id
))
2090 next_time
= int(query
.value(0))
2092 if target_time
> next_time
:
2096 if higher_id
<= lower_id
+ 1:
2097 return str(higher_id
)
2099 if target_time
>= next_time
:
2103 if higher_id
<= lower_id
+ 1:
2104 return str(lower_id
)
2106 def ConvertRelativeTime(self
, val
):
2111 elif suffix
== "us":
2113 elif suffix
== "ns":
2117 val
= val
[:-2].strip()
2118 if not self
.IsNumber(val
):
2120 val
= int(val
) * mult
2122 val
+= self
.first_time
2124 val
+= self
.last_time
2127 def ConvertTimeRange(self
, vrange
):
2129 vrange
[0] = str(self
.first_time
)
2131 vrange
[1] = str(self
.last_time
)
2132 vrange
[0] = self
.ConvertRelativeTime(vrange
[0])
2133 vrange
[1] = self
.ConvertRelativeTime(vrange
[1])
2134 if not self
.IsNumber(vrange
[0]) or not self
.IsNumber(vrange
[1]):
2136 beg_range
= max(int(vrange
[0]), self
.first_time
)
2137 end_range
= min(int(vrange
[1]), self
.last_time
)
2138 if beg_range
> self
.last_time
or end_range
< self
.first_time
:
2140 vrange
[0] = self
.BinarySearchTime(0, self
.last_id
, beg_range
, True)
2141 vrange
[1] = self
.BinarySearchTime(1, self
.last_id
+ 1, end_range
, False)
2144 def AddTimeRange(self
, value
, ranges
):
2145 n
= value
.count("-")
2149 if value
.split("-")[1].strip() == "":
2155 pos
= findnth(value
, "-", n
)
2156 vrange
= [value
[:pos
].strip() ,value
[pos
+1:].strip()]
2157 if self
.ConvertTimeRange(vrange
):
2158 ranges
.append(vrange
)
2162 def DoValidate(self
, input_string
):
2164 for value
in [x
.strip() for x
in input_string
.split(",")]:
2165 if not self
.AddTimeRange(value
, ranges
):
2166 return self
.InvalidValue(value
)
2167 ranges
= [("(" + self
.column_name
+ " >= " + r
[0] + " AND " + self
.column_name
+ " <= " + r
[1] + ")") for r
in ranges
]
2168 self
.value
= " OR ".join(ranges
)
2170 # Report Dialog Base
2172 class ReportDialogBase(QDialog
):
2174 def __init__(self
, glb
, title
, items
, partial
, parent
=None):
2175 super(ReportDialogBase
, self
).__init
__(parent
)
2179 self
.report_vars
= ReportVars()
2181 self
.setWindowTitle(title
)
2182 self
.setMinimumWidth(600)
2184 self
.data_items
= [x(glb
, self
) for x
in items
]
2186 self
.partial
= partial
2188 self
.grid
= QGridLayout()
2190 for row
in xrange(len(self
.data_items
)):
2191 self
.grid
.addWidget(QLabel(self
.data_items
[row
].label
), row
, 0)
2192 self
.grid
.addWidget(self
.data_items
[row
].widget
, row
, 1)
2194 self
.status
= QLabel()
2196 self
.ok_button
= QPushButton("Ok", self
)
2197 self
.ok_button
.setDefault(True)
2198 self
.ok_button
.released
.connect(self
.Ok
)
2199 self
.ok_button
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
2201 self
.cancel_button
= QPushButton("Cancel", self
)
2202 self
.cancel_button
.released
.connect(self
.reject
)
2203 self
.cancel_button
.setSizePolicy(QSizePolicy
.Fixed
, QSizePolicy
.Fixed
)
2205 self
.hbox
= QHBoxLayout()
2206 #self.hbox.addStretch()
2207 self
.hbox
.addWidget(self
.status
)
2208 self
.hbox
.addWidget(self
.ok_button
)
2209 self
.hbox
.addWidget(self
.cancel_button
)
2211 self
.vbox
= QVBoxLayout()
2212 self
.vbox
.addLayout(self
.grid
)
2213 self
.vbox
.addLayout(self
.hbox
)
2215 self
.setLayout(self
.vbox
)
2218 vars = self
.report_vars
2219 for d
in self
.data_items
:
2220 if d
.id == "REPORTNAME":
2223 self
.ShowMessage("Report name is required")
2225 for d
in self
.data_items
:
2228 for d
in self
.data_items
[1:]:
2230 vars.limit
= d
.value
2232 if len(vars.where_clause
):
2233 vars.where_clause
+= " AND "
2234 vars.where_clause
+= d
.value
2235 if len(vars.where_clause
):
2237 vars.where_clause
= " AND ( " + vars.where_clause
+ " ) "
2239 vars.where_clause
= " WHERE " + vars.where_clause
+ " "
2242 def ShowMessage(self
, msg
):
2243 self
.status
.setText("<font color=#FF0000>" + msg
)
2245 def ClearMessage(self
):
2246 self
.status
.setText("")
2248 # Selected branch report creation dialog
2250 class SelectedBranchDialog(ReportDialogBase
):
2252 def __init__(self
, glb
, parent
=None):
2253 title
= "Selected Branches"
2254 items
= (lambda g
, p
: LineEditDataItem(g
, "Report name:", "Enter a name to appear in the window title bar", p
, "REPORTNAME"),
2255 lambda g
, p
: SampleTimeRangesDataItem(g
, "Time ranges:", "Enter time ranges", "samples.id", p
),
2256 lambda g
, p
: NonNegativeIntegerRangesDataItem(g
, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p
),
2257 lambda g
, p
: SQLTableDataItem(g
, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p
),
2258 lambda g
, p
: SQLTableDataItem(g
, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p
),
2259 lambda g
, p
: SQLTableDataItem(g
, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p
),
2260 lambda g
, p
: SQLTableDataItem(g
, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p
),
2261 lambda g
, p
: SQLTableDataItem(g
, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p
),
2262 lambda g
, p
: LineEditDataItem(g
, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p
))
2263 super(SelectedBranchDialog
, self
).__init
__(glb
, title
, items
, True, parent
)
2267 def GetEventList(db
):
2269 query
= QSqlQuery(db
)
2270 QueryExec(query
, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2272 events
.append(query
.value(0))
2275 # Is a table selectable
2277 def IsSelectable(db
, table
, sql
= "", columns
= "*"):
2278 query
= QSqlQuery(db
)
2280 QueryExec(query
, "SELECT " + columns
+ " FROM " + table
+ " " + sql
+ " LIMIT 1")
2285 # SQL table data model item
2287 class SQLTableItem():
2289 def __init__(self
, row
, data
):
2293 def getData(self
, column
):
2294 return self
.data
[column
]
2296 # SQL table data model
2298 class SQLTableModel(TableModel
):
2300 progress
= Signal(object)
2302 def __init__(self
, glb
, sql
, column_headers
, parent
=None):
2303 super(SQLTableModel
, self
).__init
__(parent
)
2307 self
.column_headers
= column_headers
2308 self
.fetcher
= SQLFetcher(glb
, sql
, lambda x
, y
=len(column_headers
): self
.SQLTableDataPrep(x
, y
), self
.AddSample
)
2309 self
.fetcher
.done
.connect(self
.Update
)
2310 self
.fetcher
.Fetch(glb_chunk_sz
)
2312 def DisplayData(self
, item
, index
):
2313 self
.FetchIfNeeded(item
.row
)
2314 return item
.getData(index
.column())
2316 def AddSample(self
, data
):
2317 child
= SQLTableItem(self
.populated
, data
)
2318 self
.child_items
.append(child
)
2321 def Update(self
, fetched
):
2324 self
.progress
.emit(0)
2325 child_count
= self
.child_count
2326 count
= self
.populated
- child_count
2328 parent
= QModelIndex()
2329 self
.beginInsertRows(parent
, child_count
, child_count
+ count
- 1)
2330 self
.insertRows(child_count
, count
, parent
)
2331 self
.child_count
+= count
2332 self
.endInsertRows()
2333 self
.progress
.emit(self
.child_count
)
2335 def FetchMoreRecords(self
, count
):
2336 current
= self
.child_count
2338 self
.fetcher
.Fetch(count
)
2340 self
.progress
.emit(0)
2343 def HasMoreRecords(self
):
2346 def columnCount(self
, parent
=None):
2347 return len(self
.column_headers
)
2349 def columnHeader(self
, column
):
2350 return self
.column_headers
[column
]
2352 def SQLTableDataPrep(self
, query
, count
):
2354 for i
in xrange(count
):
2355 data
.append(query
.value(i
))
2358 # SQL automatic table data model
2360 class SQLAutoTableModel(SQLTableModel
):
2362 def __init__(self
, glb
, table_name
, parent
=None):
2363 sql
= "SELECT * FROM " + table_name
+ " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz
)
2364 if table_name
== "comm_threads_view":
2365 # For now, comm_threads_view has no id column
2366 sql
= "SELECT * FROM " + table_name
+ " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz
)
2368 query
= QSqlQuery(glb
.db
)
2369 if glb
.dbref
.is_sqlite3
:
2370 QueryExec(query
, "PRAGMA table_info(" + table_name
+ ")")
2372 column_headers
.append(query
.value(1))
2373 if table_name
== "sqlite_master":
2374 sql
= "SELECT * FROM " + table_name
2376 if table_name
[:19] == "information_schema.":
2377 sql
= "SELECT * FROM " + table_name
2378 select_table_name
= table_name
[19:]
2379 schema
= "information_schema"
2381 select_table_name
= table_name
2383 QueryExec(query
, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema
+ "' and table_name = '" + select_table_name
+ "'")
2385 column_headers
.append(query
.value(0))
2386 if pyside_version_1
and sys
.version_info
[0] == 3:
2387 if table_name
== "samples_view":
2388 self
.SQLTableDataPrep
= self
.samples_view_DataPrep
2389 if table_name
== "samples":
2390 self
.SQLTableDataPrep
= self
.samples_DataPrep
2391 super(SQLAutoTableModel
, self
).__init
__(glb
, sql
, column_headers
, parent
)
2393 def samples_view_DataPrep(self
, query
, count
):
2395 data
.append(query
.value(0))
2396 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2397 data
.append("{:>19}".format(query
.value(1)))
2398 for i
in xrange(2, count
):
2399 data
.append(query
.value(i
))
2402 def samples_DataPrep(self
, query
, count
):
2405 data
.append(query
.value(i
))
2406 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2407 data
.append("{:>19}".format(query
.value(9)))
2408 for i
in xrange(10, count
):
2409 data
.append(query
.value(i
))
2412 # Base class for custom ResizeColumnsToContents
2414 class ResizeColumnsToContentsBase(QObject
):
2416 def __init__(self
, parent
=None):
2417 super(ResizeColumnsToContentsBase
, self
).__init
__(parent
)
2419 def ResizeColumnToContents(self
, column
, n
):
2420 # Using the view's resizeColumnToContents() here is extrememly slow
2421 # so implement a crude alternative
2422 font
= self
.view
.font()
2423 metrics
= QFontMetrics(font
)
2425 for row
in xrange(n
):
2426 val
= self
.data_model
.child_items
[row
].data
[column
]
2427 len = metrics
.width(str(val
) + "MM")
2428 max = len if len > max else max
2429 val
= self
.data_model
.columnHeader(column
)
2430 len = metrics
.width(str(val
) + "MM")
2431 max = len if len > max else max
2432 self
.view
.setColumnWidth(column
, max)
2434 def ResizeColumnsToContents(self
):
2435 n
= min(self
.data_model
.child_count
, 100)
2437 # No data yet, so connect a signal to notify when there is
2438 self
.data_model
.rowsInserted
.connect(self
.UpdateColumnWidths
)
2440 columns
= self
.data_model
.columnCount()
2441 for i
in xrange(columns
):
2442 self
.ResizeColumnToContents(i
, n
)
2444 def UpdateColumnWidths(self
, *x
):
2445 # This only needs to be done once, so disconnect the signal now
2446 self
.data_model
.rowsInserted
.disconnect(self
.UpdateColumnWidths
)
2447 self
.ResizeColumnsToContents()
2449 # Convert value to CSV
2453 val
= val
.replace('"', '""')
2454 if "," in val
or '"' in val
:
2455 val
= '"' + val
+ '"'
2458 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2462 def RowColumnKey(a
):
2463 return a
.row() * glb_max_cols
+ a
.column()
2465 # Copy selected table cells to clipboard
2467 def CopyTableCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
2468 indexes
= sorted(view
.selectedIndexes(), key
=RowColumnKey
)
2469 idx_cnt
= len(indexes
)
2474 min_row
= indexes
[0].row()
2475 max_row
= indexes
[0].row()
2476 min_col
= indexes
[0].column()
2477 max_col
= indexes
[0].column()
2479 min_row
= min(min_row
, i
.row())
2480 max_row
= max(max_row
, i
.row())
2481 min_col
= min(min_col
, i
.column())
2482 max_col
= max(max_col
, i
.column())
2483 if max_col
> glb_max_cols
:
2484 raise RuntimeError("glb_max_cols is too low")
2485 max_width
= [0] * (1 + max_col
- min_col
)
2487 c
= i
.column() - min_col
2488 max_width
[c
] = max(max_width
[c
], len(str(i
.data())))
2493 model
= indexes
[0].model()
2494 for col
in range(min_col
, max_col
+ 1):
2495 val
= model
.headerData(col
, Qt
.Horizontal
)
2497 text
+= sep
+ ToCSValue(val
)
2501 max_width
[c
] = max(max_width
[c
], len(val
))
2502 width
= max_width
[c
]
2503 align
= model
.headerData(col
, Qt
.Horizontal
, Qt
.TextAlignmentRole
)
2504 if align
& Qt
.AlignRight
:
2505 val
= val
.rjust(width
)
2506 text
+= pad
+ sep
+ val
2507 pad
= " " * (width
- len(val
))
2514 if i
.row() > last_row
:
2520 text
+= sep
+ ToCSValue(str(i
.data()))
2523 width
= max_width
[i
.column() - min_col
]
2524 if i
.data(Qt
.TextAlignmentRole
) & Qt
.AlignRight
:
2525 val
= str(i
.data()).rjust(width
)
2528 text
+= pad
+ sep
+ val
2529 pad
= " " * (width
- len(val
))
2531 QApplication
.clipboard().setText(text
)
2533 def CopyTreeCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
2534 indexes
= view
.selectedIndexes()
2535 if not len(indexes
):
2538 selection
= view
.selectionModel()
2542 above
= view
.indexAbove(i
)
2543 if not selection
.isSelected(above
):
2548 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2550 model
= first
.model()
2552 col_cnt
= model
.columnCount(first
)
2553 max_width
= [0] * col_cnt
2556 indent_str
= " " * indent_sz
2558 expanded_mark_sz
= 2
2559 if sys
.version_info
[0] == 3:
2560 expanded_mark
= "\u25BC "
2561 not_expanded_mark
= "\u25B6 "
2563 expanded_mark
= unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2564 not_expanded_mark
= unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2572 for c
in range(col_cnt
):
2573 i
= pos
.sibling(row
, c
)
2575 n
= len(str(i
.data()))
2577 n
= len(str(i
.data()).strip())
2578 n
+= (i
.internalPointer().level
- 1) * indent_sz
2579 n
+= expanded_mark_sz
2580 max_width
[c
] = max(max_width
[c
], n
)
2581 pos
= view
.indexBelow(pos
)
2582 if not selection
.isSelected(pos
):
2589 for c
in range(col_cnt
):
2590 val
= model
.headerData(c
, Qt
.Horizontal
, Qt
.DisplayRole
).strip()
2592 text
+= sep
+ ToCSValue(val
)
2595 max_width
[c
] = max(max_width
[c
], len(val
))
2596 width
= max_width
[c
]
2597 align
= model
.headerData(c
, Qt
.Horizontal
, Qt
.TextAlignmentRole
)
2598 if align
& Qt
.AlignRight
:
2599 val
= val
.rjust(width
)
2600 text
+= pad
+ sep
+ val
2601 pad
= " " * (width
- len(val
))
2610 for c
in range(col_cnt
):
2611 i
= pos
.sibling(row
, c
)
2614 if model
.hasChildren(i
):
2615 if view
.isExpanded(i
):
2616 mark
= expanded_mark
2618 mark
= not_expanded_mark
2621 val
= indent_str
* (i
.internalPointer().level
- 1) + mark
+ val
.strip()
2623 text
+= sep
+ ToCSValue(val
)
2626 width
= max_width
[c
]
2627 if c
and i
.data(Qt
.TextAlignmentRole
) & Qt
.AlignRight
:
2628 val
= val
.rjust(width
)
2629 text
+= pad
+ sep
+ val
2630 pad
= " " * (width
- len(val
))
2632 pos
= view
.indexBelow(pos
)
2633 if not selection
.isSelected(pos
):
2635 text
= text
.rstrip() + "\n"
2639 QApplication
.clipboard().setText(text
)
2641 def CopyCellsToClipboard(view
, as_csv
=False, with_hdr
=False):
2642 view
.CopyCellsToClipboard(view
, as_csv
, with_hdr
)
2644 def CopyCellsToClipboardHdr(view
):
2645 CopyCellsToClipboard(view
, False, True)
2647 def CopyCellsToClipboardCSV(view
):
2648 CopyCellsToClipboard(view
, True, True)
2652 class ContextMenu(object):
2654 def __init__(self
, view
):
2656 self
.view
.setContextMenuPolicy(Qt
.CustomContextMenu
)
2657 self
.view
.customContextMenuRequested
.connect(self
.ShowContextMenu
)
2659 def ShowContextMenu(self
, pos
):
2660 menu
= QMenu(self
.view
)
2661 self
.AddActions(menu
)
2662 menu
.exec_(self
.view
.mapToGlobal(pos
))
2664 def AddCopy(self
, menu
):
2665 menu
.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self
.view
), self
.view
))
2666 menu
.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self
.view
), self
.view
))
2668 def AddActions(self
, menu
):
2671 class TreeContextMenu(ContextMenu
):
2673 def __init__(self
, view
):
2674 super(TreeContextMenu
, self
).__init
__(view
)
2676 def AddActions(self
, menu
):
2677 i
= self
.view
.currentIndex()
2678 text
= str(i
.data()).strip()
2680 menu
.addAction(CreateAction('Copy "' + text
+ '"', "Copy to clipboard", lambda: QApplication
.clipboard().setText(text
), self
.view
))
2685 class TableWindow(QMdiSubWindow
, ResizeColumnsToContentsBase
):
2687 def __init__(self
, glb
, table_name
, parent
=None):
2688 super(TableWindow
, self
).__init
__(parent
)
2690 self
.data_model
= LookupCreateModel(table_name
+ " Table", lambda: SQLAutoTableModel(glb
, table_name
))
2692 self
.model
= QSortFilterProxyModel()
2693 self
.model
.setSourceModel(self
.data_model
)
2695 self
.view
= QTableView()
2696 self
.view
.setModel(self
.model
)
2697 self
.view
.setEditTriggers(QAbstractItemView
.NoEditTriggers
)
2698 self
.view
.verticalHeader().setVisible(False)
2699 self
.view
.sortByColumn(-1, Qt
.AscendingOrder
)
2700 self
.view
.setSortingEnabled(True)
2701 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
2702 self
.view
.CopyCellsToClipboard
= CopyTableCellsToClipboard
2704 self
.ResizeColumnsToContents()
2706 self
.context_menu
= ContextMenu(self
.view
)
2708 self
.find_bar
= FindBar(self
, self
, True)
2710 self
.finder
= ChildDataItemFinder(self
.data_model
)
2712 self
.fetch_bar
= FetchMoreRecordsBar(self
.data_model
, self
)
2714 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
2716 self
.setWidget(self
.vbox
.Widget())
2718 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, table_name
+ " Table")
2720 def Find(self
, value
, direction
, pattern
, context
):
2721 self
.view
.setFocus()
2722 self
.find_bar
.Busy()
2723 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
2725 def FindDone(self
, row
):
2726 self
.find_bar
.Idle()
2728 self
.view
.setCurrentIndex(self
.model
.mapFromSource(self
.data_model
.index(row
, 0, QModelIndex())))
2730 self
.find_bar
.NotFound()
2734 def GetTableList(glb
):
2736 query
= QSqlQuery(glb
.db
)
2737 if glb
.dbref
.is_sqlite3
:
2738 QueryExec(query
, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2740 QueryExec(query
, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2742 tables
.append(query
.value(0))
2743 if glb
.dbref
.is_sqlite3
:
2744 tables
.append("sqlite_master")
2746 tables
.append("information_schema.tables")
2747 tables
.append("information_schema.views")
2748 tables
.append("information_schema.columns")
2751 # Top Calls data model
2753 class TopCallsModel(SQLTableModel
):
2755 def __init__(self
, glb
, report_vars
, parent
=None):
2757 if not glb
.dbref
.is_sqlite3
:
2760 if len(report_vars
.limit
):
2761 limit
= " LIMIT " + report_vars
.limit
2762 sql
= ("SELECT comm, pid, tid, name,"
2764 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text
+
2767 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2769 " WHEN (calls.flags = 1) THEN 'no call'" + text
+
2770 " WHEN (calls.flags = 2) THEN 'no return'" + text
+
2771 " WHEN (calls.flags = 3) THEN 'no call/return'" + text
+
2775 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2776 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2777 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2778 " INNER JOIN comms ON calls.comm_id = comms.id"
2779 " INNER JOIN threads ON calls.thread_id = threads.id" +
2780 report_vars
.where_clause
+
2781 " ORDER BY elapsed_time DESC" +
2784 column_headers
= ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2785 self
.alignment
= (Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignLeft
, Qt
.AlignRight
, Qt
.AlignRight
, Qt
.AlignLeft
)
2786 super(TopCallsModel
, self
).__init
__(glb
, sql
, column_headers
, parent
)
2788 def columnAlignment(self
, column
):
2789 return self
.alignment
[column
]
2791 # Top Calls report creation dialog
2793 class TopCallsDialog(ReportDialogBase
):
2795 def __init__(self
, glb
, parent
=None):
2796 title
= "Top Calls by Elapsed Time"
2797 items
= (lambda g
, p
: LineEditDataItem(g
, "Report name:", "Enter a name to appear in the window title bar", p
, "REPORTNAME"),
2798 lambda g
, p
: SQLTableDataItem(g
, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p
),
2799 lambda g
, p
: SQLTableDataItem(g
, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p
),
2800 lambda g
, p
: SQLTableDataItem(g
, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p
),
2801 lambda g
, p
: SQLTableDataItem(g
, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p
),
2802 lambda g
, p
: SQLTableDataItem(g
, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p
),
2803 lambda g
, p
: LineEditDataItem(g
, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p
),
2804 lambda g
, p
: PositiveIntegerDataItem(g
, "Record limit:", "Limit selection to this number of records", p
, "LIMIT", "100"))
2805 super(TopCallsDialog
, self
).__init
__(glb
, title
, items
, False, parent
)
2809 class TopCallsWindow(QMdiSubWindow
, ResizeColumnsToContentsBase
):
2811 def __init__(self
, glb
, report_vars
, parent
=None):
2812 super(TopCallsWindow
, self
).__init
__(parent
)
2814 self
.data_model
= LookupCreateModel("Top Calls " + report_vars
.UniqueId(), lambda: TopCallsModel(glb
, report_vars
))
2815 self
.model
= self
.data_model
2817 self
.view
= QTableView()
2818 self
.view
.setModel(self
.model
)
2819 self
.view
.setEditTriggers(QAbstractItemView
.NoEditTriggers
)
2820 self
.view
.verticalHeader().setVisible(False)
2821 self
.view
.setSelectionMode(QAbstractItemView
.ContiguousSelection
)
2822 self
.view
.CopyCellsToClipboard
= CopyTableCellsToClipboard
2824 self
.context_menu
= ContextMenu(self
.view
)
2826 self
.ResizeColumnsToContents()
2828 self
.find_bar
= FindBar(self
, self
, True)
2830 self
.finder
= ChildDataItemFinder(self
.model
)
2832 self
.fetch_bar
= FetchMoreRecordsBar(self
.data_model
, self
)
2834 self
.vbox
= VBox(self
.view
, self
.find_bar
.Widget(), self
.fetch_bar
.Widget())
2836 self
.setWidget(self
.vbox
.Widget())
2838 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, report_vars
.name
)
2840 def Find(self
, value
, direction
, pattern
, context
):
2841 self
.view
.setFocus()
2842 self
.find_bar
.Busy()
2843 self
.finder
.Find(value
, direction
, pattern
, context
, self
.FindDone
)
2845 def FindDone(self
, row
):
2846 self
.find_bar
.Idle()
2848 self
.view
.setCurrentIndex(self
.model
.index(row
, 0, QModelIndex()))
2850 self
.find_bar
.NotFound()
2854 def CreateAction(label
, tip
, callback
, parent
=None, shortcut
=None):
2855 action
= QAction(label
, parent
)
2856 if shortcut
!= None:
2857 action
.setShortcuts(shortcut
)
2858 action
.setStatusTip(tip
)
2859 action
.triggered
.connect(callback
)
2862 # Typical application actions
2864 def CreateExitAction(app
, parent
=None):
2865 return CreateAction("&Quit", "Exit the application", app
.closeAllWindows
, parent
, QKeySequence
.Quit
)
2867 # Typical MDI actions
2869 def CreateCloseActiveWindowAction(mdi_area
):
2870 return CreateAction("Cl&ose", "Close the active window", mdi_area
.closeActiveSubWindow
, mdi_area
)
2872 def CreateCloseAllWindowsAction(mdi_area
):
2873 return CreateAction("Close &All", "Close all the windows", mdi_area
.closeAllSubWindows
, mdi_area
)
2875 def CreateTileWindowsAction(mdi_area
):
2876 return CreateAction("&Tile", "Tile the windows", mdi_area
.tileSubWindows
, mdi_area
)
2878 def CreateCascadeWindowsAction(mdi_area
):
2879 return CreateAction("&Cascade", "Cascade the windows", mdi_area
.cascadeSubWindows
, mdi_area
)
2881 def CreateNextWindowAction(mdi_area
):
2882 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area
.activateNextSubWindow
, mdi_area
, QKeySequence
.NextChild
)
2884 def CreatePreviousWindowAction(mdi_area
):
2885 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area
.activatePreviousSubWindow
, mdi_area
, QKeySequence
.PreviousChild
)
2887 # Typical MDI window menu
2891 def __init__(self
, mdi_area
, menu
):
2892 self
.mdi_area
= mdi_area
2893 self
.window_menu
= menu
.addMenu("&Windows")
2894 self
.close_active_window
= CreateCloseActiveWindowAction(mdi_area
)
2895 self
.close_all_windows
= CreateCloseAllWindowsAction(mdi_area
)
2896 self
.tile_windows
= CreateTileWindowsAction(mdi_area
)
2897 self
.cascade_windows
= CreateCascadeWindowsAction(mdi_area
)
2898 self
.next_window
= CreateNextWindowAction(mdi_area
)
2899 self
.previous_window
= CreatePreviousWindowAction(mdi_area
)
2900 self
.window_menu
.aboutToShow
.connect(self
.Update
)
2903 self
.window_menu
.clear()
2904 sub_window_count
= len(self
.mdi_area
.subWindowList())
2905 have_sub_windows
= sub_window_count
!= 0
2906 self
.close_active_window
.setEnabled(have_sub_windows
)
2907 self
.close_all_windows
.setEnabled(have_sub_windows
)
2908 self
.tile_windows
.setEnabled(have_sub_windows
)
2909 self
.cascade_windows
.setEnabled(have_sub_windows
)
2910 self
.next_window
.setEnabled(have_sub_windows
)
2911 self
.previous_window
.setEnabled(have_sub_windows
)
2912 self
.window_menu
.addAction(self
.close_active_window
)
2913 self
.window_menu
.addAction(self
.close_all_windows
)
2914 self
.window_menu
.addSeparator()
2915 self
.window_menu
.addAction(self
.tile_windows
)
2916 self
.window_menu
.addAction(self
.cascade_windows
)
2917 self
.window_menu
.addSeparator()
2918 self
.window_menu
.addAction(self
.next_window
)
2919 self
.window_menu
.addAction(self
.previous_window
)
2920 if sub_window_count
== 0:
2922 self
.window_menu
.addSeparator()
2924 for sub_window
in self
.mdi_area
.subWindowList():
2925 label
= str(nr
) + " " + sub_window
.name
2928 action
= self
.window_menu
.addAction(label
)
2929 action
.setCheckable(True)
2930 action
.setChecked(sub_window
== self
.mdi_area
.activeSubWindow())
2931 action
.triggered
.connect(lambda a
=None,x
=nr
: self
.setActiveSubWindow(x
))
2932 self
.window_menu
.addAction(action
)
2935 def setActiveSubWindow(self
, nr
):
2936 self
.mdi_area
.setActiveSubWindow(self
.mdi_area
.subWindowList()[nr
- 1])
2951 <p class=c1><a href=#reports>1. Reports</a></p>
2952 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2953 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2954 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
2955 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2956 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2957 <p class=c1><a href=#tables>2. Tables</a></p>
2958 <h1 id=reports>1. Reports</h1>
2959 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2960 The result is a GUI window with a tree representing a context-sensitive
2961 call-graph. Expanding a couple of levels of the tree and adjusting column
2962 widths to suit will display something like:
2964 Call Graph: pt_example
2965 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2968 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2969 |- unknown unknown 1 13198 0.1 1 0.0
2970 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2971 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2972 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2973 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2974 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2975 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2976 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2977 v- main ls 1 8182043 99.6 180254 99.9
2979 <h3>Points to note:</h3>
2981 <li>The top level is a command name (comm)</li>
2982 <li>The next level is a thread (pid:tid)</li>
2983 <li>Subsequent levels are functions</li>
2984 <li>'Count' is the number of calls</li>
2985 <li>'Time' is the elapsed time until the function returns</li>
2986 <li>Percentages are relative to the level above</li>
2987 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2990 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2991 The pattern matching symbols are ? for any character and * for zero or more characters.
2992 <h2 id=calltree>1.2 Call Tree</h2>
2993 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2994 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2995 <h2 id=allbranches>1.3 All branches</h2>
2996 The All branches report displays all branches in chronological order.
2997 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2998 <h3>Disassembly</h3>
2999 Open a branch to display disassembly. This only works if:
3001 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
3002 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
3003 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
3004 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
3005 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
3007 <h4 id=xed>Intel XED Setup</h4>
3008 To use Intel XED, libxed.so must be present. To build and install libxed.so:
3010 git clone https://github.com/intelxed/mbuild.git mbuild
3011 git clone https://github.com/intelxed/xed
3014 sudo ./mfile.py --prefix=/usr/local install
3017 <h3>Instructions per Cycle (IPC)</h3>
3018 If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
3019 <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
3020 Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
3021 In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
3022 since the previous displayed 'IPC'.
3024 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3025 Refer to Python documentation for the regular expression syntax.
3026 All columns are searched, but only currently fetched rows are searched.
3027 <h2 id=selectedbranches>1.4 Selected branches</h2>
3028 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
3029 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3030 <h3>1.4.1 Time ranges</h3>
3031 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
3032 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
3034 81073085947329-81073085958238 From 81073085947329 to 81073085958238
3035 100us-200us From 100us to 200us
3036 10ms- From 10ms to the end
3037 -100ns The first 100ns
3038 -10ms- The last 10ms
3040 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
3041 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
3042 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.
3043 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3044 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
3045 <h1 id=tables>2. Tables</h1>
3046 The Tables menu shows all tables and views in the database. Most tables have an associated view
3047 which displays the information in a more friendly way. Not all data for large tables is fetched
3048 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
3049 but that can be slow for large tables.
3050 <p>There are also tables of database meta-information.
3051 For SQLite3 databases, the sqlite_master table is included.
3052 For PostgreSQL databases, information_schema.tables/views/columns are included.
3054 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3055 Refer to Python documentation for the regular expression syntax.
3056 All columns are searched, but only currently fetched rows are searched.
3057 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
3058 will go to the next/previous result in id order, instead of display order.
3063 class HelpWindow(QMdiSubWindow
):
3065 def __init__(self
, glb
, parent
=None):
3066 super(HelpWindow
, self
).__init
__(parent
)
3068 self
.text
= QTextBrowser()
3069 self
.text
.setHtml(glb_help_text
)
3070 self
.text
.setReadOnly(True)
3071 self
.text
.setOpenExternalLinks(True)
3073 self
.setWidget(self
.text
)
3075 AddSubWindow(glb
.mainwindow
.mdi_area
, self
, "Exported SQL Viewer Help")
3077 # Main window that only displays the help text
3079 class HelpOnlyWindow(QMainWindow
):
3081 def __init__(self
, parent
=None):
3082 super(HelpOnlyWindow
, self
).__init
__(parent
)
3084 self
.setMinimumSize(200, 100)
3085 self
.resize(800, 600)
3086 self
.setWindowTitle("Exported SQL Viewer Help")
3087 self
.setWindowIcon(self
.style().standardIcon(QStyle
.SP_MessageBoxInformation
))
3089 self
.text
= QTextBrowser()
3090 self
.text
.setHtml(glb_help_text
)
3091 self
.text
.setReadOnly(True)
3092 self
.text
.setOpenExternalLinks(True)
3094 self
.setCentralWidget(self
.text
)
3096 # PostqreSQL server version
3098 def PostqreSQLServerVersion(db
):
3099 query
= QSqlQuery(db
)
3100 QueryExec(query
, "SELECT VERSION()")
3102 v_str
= query
.value(0)
3103 v_list
= v_str
.strip().split(" ")
3104 if v_list
[0] == "PostgreSQL" and v_list
[2] == "on":
3111 def SQLiteVersion(db
):
3112 query
= QSqlQuery(db
)
3113 QueryExec(query
, "SELECT sqlite_version()")
3115 return query
.value(0)
3120 class AboutDialog(QDialog
):
3122 def __init__(self
, glb
, parent
=None):
3123 super(AboutDialog
, self
).__init
__(parent
)
3125 self
.setWindowTitle("About Exported SQL Viewer")
3126 self
.setMinimumWidth(300)
3128 pyside_version
= "1" if pyside_version_1
else "2"
3131 text
+= "Python version: " + sys
.version
.split(" ")[0] + "\n"
3132 text
+= "PySide version: " + pyside_version
+ "\n"
3133 text
+= "Qt version: " + qVersion() + "\n"
3134 if glb
.dbref
.is_sqlite3
:
3135 text
+= "SQLite version: " + SQLiteVersion(glb
.db
) + "\n"
3137 text
+= "PostqreSQL version: " + PostqreSQLServerVersion(glb
.db
) + "\n"
3140 self
.text
= QTextBrowser()
3141 self
.text
.setHtml(text
)
3142 self
.text
.setReadOnly(True)
3143 self
.text
.setOpenExternalLinks(True)
3145 self
.vbox
= QVBoxLayout()
3146 self
.vbox
.addWidget(self
.text
)
3148 self
.setLayout(self
.vbox
)
3152 def ResizeFont(widget
, diff
):
3153 font
= widget
.font()
3154 sz
= font
.pointSize()
3155 font
.setPointSize(sz
+ diff
)
3156 widget
.setFont(font
)
3158 def ShrinkFont(widget
):
3159 ResizeFont(widget
, -1)
3161 def EnlargeFont(widget
):
3162 ResizeFont(widget
, 1)
3164 # Unique name for sub-windows
3166 def NumberedWindowName(name
, nr
):
3168 name
+= " <" + str(nr
) + ">"
3171 def UniqueSubWindowName(mdi_area
, name
):
3174 unique_name
= NumberedWindowName(name
, nr
)
3176 for sub_window
in mdi_area
.subWindowList():
3177 if sub_window
.name
== unique_name
:
3186 def AddSubWindow(mdi_area
, sub_window
, name
):
3187 unique_name
= UniqueSubWindowName(mdi_area
, name
)
3188 sub_window
.setMinimumSize(200, 100)
3189 sub_window
.resize(800, 600)
3190 sub_window
.setWindowTitle(unique_name
)
3191 sub_window
.setAttribute(Qt
.WA_DeleteOnClose
)
3192 sub_window
.setWindowIcon(sub_window
.style().standardIcon(QStyle
.SP_FileIcon
))
3193 sub_window
.name
= unique_name
3194 mdi_area
.addSubWindow(sub_window
)
3199 class MainWindow(QMainWindow
):
3201 def __init__(self
, glb
, parent
=None):
3202 super(MainWindow
, self
).__init
__(parent
)
3206 self
.setWindowTitle("Exported SQL Viewer: " + glb
.dbname
)
3207 self
.setWindowIcon(self
.style().standardIcon(QStyle
.SP_ComputerIcon
))
3208 self
.setMinimumSize(200, 100)
3210 self
.mdi_area
= QMdiArea()
3211 self
.mdi_area
.setHorizontalScrollBarPolicy(Qt
.ScrollBarAsNeeded
)
3212 self
.mdi_area
.setVerticalScrollBarPolicy(Qt
.ScrollBarAsNeeded
)
3214 self
.setCentralWidget(self
.mdi_area
)
3216 menu
= self
.menuBar()
3218 file_menu
= menu
.addMenu("&File")
3219 file_menu
.addAction(CreateExitAction(glb
.app
, self
))
3221 edit_menu
= menu
.addMenu("&Edit")
3222 edit_menu
.addAction(CreateAction("&Copy", "Copy to clipboard", self
.CopyToClipboard
, self
, QKeySequence
.Copy
))
3223 edit_menu
.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self
.CopyToClipboardCSV
, self
))
3224 edit_menu
.addAction(CreateAction("&Find...", "Find items", self
.Find
, self
, QKeySequence
.Find
))
3225 edit_menu
.addAction(CreateAction("Fetch &more records...", "Fetch more records", self
.FetchMoreRecords
, self
, [QKeySequence(Qt
.Key_F8
)]))
3226 edit_menu
.addAction(CreateAction("&Shrink Font", "Make text smaller", self
.ShrinkFont
, self
, [QKeySequence("Ctrl+-")]))
3227 edit_menu
.addAction(CreateAction("&Enlarge Font", "Make text bigger", self
.EnlargeFont
, self
, [QKeySequence("Ctrl++")]))
3229 reports_menu
= menu
.addMenu("&Reports")
3230 if IsSelectable(glb
.db
, "calls"):
3231 reports_menu
.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self
.NewCallGraph
, self
))
3233 if IsSelectable(glb
.db
, "calls", "WHERE parent_id >= 0"):
3234 reports_menu
.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self
.NewCallTree
, self
))
3236 self
.EventMenu(GetEventList(glb
.db
), reports_menu
)
3238 if IsSelectable(glb
.db
, "calls"):
3239 reports_menu
.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self
.NewTopCalls
, self
))
3241 self
.TableMenu(GetTableList(glb
), menu
)
3243 self
.window_menu
= WindowMenu(self
.mdi_area
, menu
)
3245 help_menu
= menu
.addMenu("&Help")
3246 help_menu
.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self
.Help
, self
, QKeySequence
.HelpContents
))
3247 help_menu
.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self
.About
, self
))
3250 win
= self
.mdi_area
.activeSubWindow()
3257 def CopyToClipboard(self
):
3258 self
.Try(CopyCellsToClipboardHdr
)
3260 def CopyToClipboardCSV(self
):
3261 self
.Try(CopyCellsToClipboardCSV
)
3264 win
= self
.mdi_area
.activeSubWindow()
3267 win
.find_bar
.Activate()
3271 def FetchMoreRecords(self
):
3272 win
= self
.mdi_area
.activeSubWindow()
3275 win
.fetch_bar
.Activate()
3279 def ShrinkFont(self
):
3280 self
.Try(ShrinkFont
)
3282 def EnlargeFont(self
):
3283 self
.Try(EnlargeFont
)
3285 def EventMenu(self
, events
, reports_menu
):
3287 for event
in events
:
3288 event
= event
.split(":")[0]
3289 if event
== "branches":
3290 branches_events
+= 1
3292 for event
in events
:
3294 event
= event
.split(":")[0]
3295 if event
== "branches":
3296 label
= "All branches" if branches_events
== 1 else "All branches " + "(id=" + dbid
+ ")"
3297 reports_menu
.addAction(CreateAction(label
, "Create a new window displaying branch events", lambda a
=None,x
=dbid
: self
.NewBranchView(x
), self
))
3298 label
= "Selected branches" if branches_events
== 1 else "Selected branches " + "(id=" + dbid
+ ")"
3299 reports_menu
.addAction(CreateAction(label
, "Create a new window displaying branch events", lambda a
=None,x
=dbid
: self
.NewSelectedBranchView(x
), self
))
3301 def TableMenu(self
, tables
, menu
):
3302 table_menu
= menu
.addMenu("&Tables")
3303 for table
in tables
:
3304 table_menu
.addAction(CreateAction(table
, "Create a new window containing a table view", lambda a
=None,t
=table
: self
.NewTableView(t
), self
))
3306 def NewCallGraph(self
):
3307 CallGraphWindow(self
.glb
, self
)
3309 def NewCallTree(self
):
3310 CallTreeWindow(self
.glb
, self
)
3312 def NewTopCalls(self
):
3313 dialog
= TopCallsDialog(self
.glb
, self
)
3314 ret
= dialog
.exec_()
3316 TopCallsWindow(self
.glb
, dialog
.report_vars
, self
)
3318 def NewBranchView(self
, event_id
):
3319 BranchWindow(self
.glb
, event_id
, ReportVars(), self
)
3321 def NewSelectedBranchView(self
, event_id
):
3322 dialog
= SelectedBranchDialog(self
.glb
, self
)
3323 ret
= dialog
.exec_()
3325 BranchWindow(self
.glb
, event_id
, dialog
.report_vars
, self
)
3327 def NewTableView(self
, table_name
):
3328 TableWindow(self
.glb
, table_name
, self
)
3331 HelpWindow(self
.glb
, self
)
3334 dialog
= AboutDialog(self
.glb
, self
)
3339 class xed_state_t(Structure
):
3346 class XEDInstruction():
3348 def __init__(self
, libxed
):
3349 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3350 xedd_t
= c_byte
* 512
3351 self
.xedd
= xedd_t()
3352 self
.xedp
= addressof(self
.xedd
)
3353 libxed
.xed_decoded_inst_zero(self
.xedp
)
3354 self
.state
= xed_state_t()
3355 self
.statep
= addressof(self
.state
)
3356 # Buffer for disassembled instruction text
3357 self
.buffer = create_string_buffer(256)
3358 self
.bufferp
= addressof(self
.buffer)
3364 self
.libxed
= CDLL("libxed.so")
3368 self
.libxed
= CDLL("/usr/local/lib/libxed.so")
3370 self
.xed_tables_init
= self
.libxed
.xed_tables_init
3371 self
.xed_tables_init
.restype
= None
3372 self
.xed_tables_init
.argtypes
= []
3374 self
.xed_decoded_inst_zero
= self
.libxed
.xed_decoded_inst_zero
3375 self
.xed_decoded_inst_zero
.restype
= None
3376 self
.xed_decoded_inst_zero
.argtypes
= [ c_void_p
]
3378 self
.xed_operand_values_set_mode
= self
.libxed
.xed_operand_values_set_mode
3379 self
.xed_operand_values_set_mode
.restype
= None
3380 self
.xed_operand_values_set_mode
.argtypes
= [ c_void_p
, c_void_p
]
3382 self
.xed_decoded_inst_zero_keep_mode
= self
.libxed
.xed_decoded_inst_zero_keep_mode
3383 self
.xed_decoded_inst_zero_keep_mode
.restype
= None
3384 self
.xed_decoded_inst_zero_keep_mode
.argtypes
= [ c_void_p
]
3386 self
.xed_decode
= self
.libxed
.xed_decode
3387 self
.xed_decode
.restype
= c_int
3388 self
.xed_decode
.argtypes
= [ c_void_p
, c_void_p
, c_uint
]
3390 self
.xed_format_context
= self
.libxed
.xed_format_context
3391 self
.xed_format_context
.restype
= c_uint
3392 self
.xed_format_context
.argtypes
= [ c_int
, c_void_p
, c_void_p
, c_int
, c_ulonglong
, c_void_p
, c_void_p
]
3394 self
.xed_tables_init()
3396 def Instruction(self
):
3397 return XEDInstruction(self
)
3399 def SetMode(self
, inst
, mode
):
3401 inst
.state
.mode
= 4 # 32-bit
3402 inst
.state
.width
= 4 # 4 bytes
3404 inst
.state
.mode
= 1 # 64-bit
3405 inst
.state
.width
= 8 # 8 bytes
3406 self
.xed_operand_values_set_mode(inst
.xedp
, inst
.statep
)
3408 def DisassembleOne(self
, inst
, bytes_ptr
, bytes_cnt
, ip
):
3409 self
.xed_decoded_inst_zero_keep_mode(inst
.xedp
)
3410 err
= self
.xed_decode(inst
.xedp
, bytes_ptr
, bytes_cnt
)
3413 # Use AT&T mode (2), alternative is Intel (3)
3414 ok
= self
.xed_format_context(2, inst
.xedp
, inst
.bufferp
, sizeof(inst
.buffer), ip
, 0, 0)
3417 if sys
.version_info
[0] == 2:
3418 result
= inst
.buffer.value
3420 result
= inst
.buffer.value
.decode()
3421 # Return instruction length and the disassembled instruction text
3422 # For now, assume the length is in byte 166
3423 return inst
.xedd
[166], result
3425 def TryOpen(file_name
):
3427 return open(file_name
, "rb")
3432 result
= sizeof(c_void_p
)
3439 if sys
.version_info
[0] == 2:
3440 eclass
= ord(header
[4])
3441 encoding
= ord(header
[5])
3442 version
= ord(header
[6])
3445 encoding
= header
[5]
3447 if magic
== chr(127) + "ELF" and eclass
> 0 and eclass
< 3 and encoding
> 0 and encoding
< 3 and version
== 1:
3448 result
= True if eclass
== 2 else False
3455 def __init__(self
, dbref
, db
, dbname
):
3458 self
.dbname
= dbname
3459 self
.home_dir
= os
.path
.expanduser("~")
3460 self
.buildid_dir
= os
.getenv("PERF_BUILDID_DIR")
3461 if self
.buildid_dir
:
3462 self
.buildid_dir
+= "/.build-id/"
3464 self
.buildid_dir
= self
.home_dir
+ "/.debug/.build-id/"
3466 self
.mainwindow
= None
3467 self
.instances_to_shutdown_on_exit
= weakref
.WeakSet()
3469 self
.disassembler
= LibXED()
3470 self
.have_disassembler
= True
3472 self
.have_disassembler
= False
3474 def FileFromBuildId(self
, build_id
):
3475 file_name
= self
.buildid_dir
+ build_id
[0:2] + "/" + build_id
[2:] + "/elf"
3476 return TryOpen(file_name
)
3478 def FileFromNamesAndBuildId(self
, short_name
, long_name
, build_id
):
3479 # Assume current machine i.e. no support for virtualization
3480 if short_name
[0:7] == "[kernel" and os
.path
.basename(long_name
) == "kcore":
3481 file_name
= os
.getenv("PERF_KCORE")
3482 f
= TryOpen(file_name
) if file_name
else None
3485 # For now, no special handling if long_name is /proc/kcore
3486 f
= TryOpen(long_name
)
3489 f
= self
.FileFromBuildId(build_id
)
3494 def AddInstanceToShutdownOnExit(self
, instance
):
3495 self
.instances_to_shutdown_on_exit
.add(instance
)
3497 # Shutdown any background processes or threads
3498 def ShutdownInstances(self
):
3499 for x
in self
.instances_to_shutdown_on_exit
:
3505 # Database reference
3509 def __init__(self
, is_sqlite3
, dbname
):
3510 self
.is_sqlite3
= is_sqlite3
3511 self
.dbname
= dbname
3513 def Open(self
, connection_name
):
3514 dbname
= self
.dbname
3516 db
= QSqlDatabase
.addDatabase("QSQLITE", connection_name
)
3518 db
= QSqlDatabase
.addDatabase("QPSQL", connection_name
)
3519 opts
= dbname
.split()
3522 opt
= opt
.split("=")
3523 if opt
[0] == "hostname":
3524 db
.setHostName(opt
[1])
3525 elif opt
[0] == "port":
3526 db
.setPort(int(opt
[1]))
3527 elif opt
[0] == "username":
3528 db
.setUserName(opt
[1])
3529 elif opt
[0] == "password":
3530 db
.setPassword(opt
[1])
3531 elif opt
[0] == "dbname":
3536 db
.setDatabaseName(dbname
)
3538 raise Exception("Failed to open database " + dbname
+ " error: " + db
.lastError().text())
3544 usage_str
= "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
3545 " or: exported-sql-viewer.py --help-only"
3546 ap
= argparse
.ArgumentParser(usage
= usage_str
, add_help
= False)
3547 ap
.add_argument("--pyside-version-1", action
='store_true')
3548 ap
.add_argument("dbname", nargs
="?")
3549 ap
.add_argument("--help-only", action
='store_true')
3550 args
= ap
.parse_args()
3553 app
= QApplication(sys
.argv
)
3554 mainwindow
= HelpOnlyWindow()
3559 dbname
= args
.dbname
3562 print("Too few arguments")
3567 f
= open(dbname
, "rb")
3568 if f
.read(15) == b
'SQLite format 3':
3574 dbref
= DBRef(is_sqlite3
, dbname
)
3575 db
, dbname
= dbref
.Open("main")
3576 glb
= Glb(dbref
, db
, dbname
)
3577 app
= QApplication(sys
.argv
)
3579 mainwindow
= MainWindow(glb
)
3580 glb
.mainwindow
= mainwindow
3583 glb
.ShutdownInstances()
3587 if __name__
== "__main__":